# HG changeset patch # User Sebastien Jodogne # Date 1742301300 -3600 # Node ID ce002301ecea0758708eb6fb165e367bde50c765 # Parent 4ee84545fcbef957656ed134dc3cf65b8d4d7552# Parent dfc271554574cbf45f49324de92fd14130790c5c integration Orthanc-1.12.6->mainline diff -r dfc271554574 -r ce002301ecea INSTALL --- a/INSTALL Tue Mar 11 15:34:04 2025 +0100 +++ b/INSTALL Tue Mar 18 13:35:00 2025 +0100 @@ -76,7 +76,7 @@ # cd [...]\Orthanc\Build # cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \ - -DUSE_LEGACY_JSONCPP=ON -G "Visual Studio 9 2008" [...]\OrthancServer + -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_BOOST=ON -G "Visual Studio 9 2008" [...]\OrthancServer Then open the "[...]\Orthanc\Build\Orthanc.sln" with Visual Studio. @@ -145,17 +145,20 @@ Cross-Compilation for Windows under GNU/Linux --------------------------------------------- -Some versions of MinGW-W64 might have problems with C++11 (notably -those shipped in Ubuntu 16.04 LTS, in the "mingw-w64" package). Use -the following command to disable C++11: +Some versions of MinGW-W64 may have insufficient support C++11 to +compile recent versions of Boost or ICU (notably those shipped in +Ubuntu 22.04 LTS, in the "g++-mingw-w64-i686-win32" package). Use the +following command to disable C++11 in Boost and ICU: # cd ~/Orthanc/Build # cmake ../OrthancServer \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGW-W64-Toolchain32.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake \ -DSTANDALONE_BUILD=ON \ -DSTATIC_BUILD=ON \ - -DUSE_LEGACY_JSONCPP=ON + -DBOOST_LOCALE_BACKEND=icu \ + -DUSE_LEGACY_BOOST=ON \ + -DUSE_LEGACY_LIBICU=ON # make NB: Use the toolchain "MinGW-W64-Toolchain64.cmake" to produce 64bit @@ -170,10 +173,12 @@ # cd ~/Orthanc/Build # cmake ../OrthancServer \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=../OrthancFramework/Resources/Toolchains/MinGWToolchain.cmake \ -DSTANDALONE_BUILD=ON \ -DSTATIC_BUILD=ON \ -DDCMTK_STATIC_VERSION=3.6.0 \ - -DUSE_LEGACY_JSONCPP=ON + -DUSE_LEGACY_JSONCPP=ON \ + -DUSE_LEGACY_BOOST=ON \ + -DUSE_LEGACY_LIBICU=ON # make diff -r dfc271554574 -r ce002301ecea NEWS --- a/NEWS Tue Mar 11 15:34:04 2025 +0100 +++ b/NEWS Tue Mar 18 13:35:00 2025 +0100 @@ -1,6 +1,36 @@ Pending changes in the mainline =============================== +REST API +-------- + +* API version upgraded to 28 +* GET /studies/../archive and sibbling routes now all accept a 'filename' GET argument. +* POST /studies/../archive and sibbling routes now all accept a 'Filename' query argument. +* GET /instances/../file and sibbling ../attachments/../data routes now all accept a 'filename' GET argument. +* All routes accepting a "transcode" url argument or a "Transcode" field in the payload now also + accepts a "lossy-quality" url argument or a "LossyQuality" field to define the compression quality factor. + If not specified, the "DicomLossyTranscodingQuality" configuration is taken into account. + + +Maintenance +----------- + +* In the "ExtendedFind" mode: + - optimized "tools/find" when "StorageAccessMode" is set to "Never". + - "tools/find" is now returning results when e.g, ordering instances against a metadata they don't have +* Fixed interpretation of returnUnsupportedImage in /preview route. +* GET /series/../study now also contain LastUpdate field: + https://discourse.orthanc-server.org/t/lastupdate-coherency/5524 +* Recovered compatibility with Windows XP that was broken because of DCMTK 3.6.9 +* Enabled support of the 1.2.840.10008.1.2.1.99 transfer syntax + (Deflated Explicit VR Little Endian) in static builds + fix length of saved files. + https://discourse.orthanc-server.org/t/transcoding-to-deflated-transfer-syntax-fails/5489 +* Housekeeper plugin: + - When encountering an error, the housekeeper now skips the resource and continues processing. +* Orthanc Explorer: + - Allow '-' and '_' in labels. + Version 1.12.6 (2025-01-22) =========================== diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake --- a/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake Tue Mar 18 13:35:00 2025 +0100 @@ -286,6 +286,7 @@ find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic /usr/share/dcmtk /usr/share/dcmtk-3.6.8 + /usr/share/dcmtk-3.6.9 /usr/share/libdcmtk1 /usr/share/libdcmtk2 /usr/share/libdcmtk3 diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.9.cmake --- a/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.9.cmake Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/CMake/DcmtkConfigurationStatic-3.6.9.cmake Tue Mar 18 13:35:00 2025 +0100 @@ -203,6 +203,14 @@ message(FATAL_ERROR "Invalid value for DCMTK_LOCALE_BACKEND: ${DCMTK_LOCALE_BACKEND}") endif() + +# Enable support of the 1.2.840.10008.1.2.1.99 transfer syntax in +# static builds of Orthanc (Deflated Explicit VR Little +# Endian). Defining "WITH_ZLIB" is always OK, as zlib is part of the +# core dependencies of the Orthanc framework. +# https://discourse.orthanc-server.org/t/transcoding-to-deflated-transfer-syntax-fails/ +set(WITH_ZLIB ON) + CONFIGURE_FILE( ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h) diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake --- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake Tue Mar 18 13:35:00 2025 +0100 @@ -167,6 +167,8 @@ set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73") elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.5") set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3") + elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6") + set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6") # Below this point are development snapshots that were used to # release some plugin, before an official release of the Orthanc diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake --- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Tue Mar 18 13:35:00 2025 +0100 @@ -25,7 +25,7 @@ ##################################################################### # Version of the build, should always be "mainline" except in release branches -set(ORTHANC_VERSION "1.12.6") +set(ORTHANC_VERSION "mainline") # Version of the database schema. History: # * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/Patches/curl-8.9.0.patch --- a/OrthancFramework/Resources/Patches/curl-8.9.0.patch Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/Patches/curl-8.9.0.patch Tue Mar 18 13:35:00 2025 +0100 @@ -1,6 +1,6 @@ diff -urEb curl-8.9.0.orig/CMake/Macros.cmake curl-8.9.0/CMake/Macros.cmake ---- curl-8.9.0.orig/CMake/Macros.cmake 2024-07-26 18:47:52.920588300 +0200 -+++ curl-8.9.0/CMake/Macros.cmake 2024-07-26 18:48:08.345522100 +0200 +--- curl-8.9.0.orig/CMake/Macros.cmake 2025-02-18 16:04:59.818585107 +0100 ++++ curl-8.9.0/CMake/Macros.cmake 2025-02-18 16:05:16.867458366 +0100 @@ -48,7 +48,7 @@ message(STATUS "Performing Test ${CURL_TEST}") try_compile(${CURL_TEST} @@ -10,3 +10,15 @@ CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} "${CURL_TEST_ADD_LIBRARIES}" OUTPUT_VARIABLE OUTPUT) +diff -urEb curl-8.9.0.orig/lib/system_win32.c curl-8.9.0/lib/system_win32.c +--- curl-8.9.0.orig/lib/system_win32.c 2025-02-18 16:04:59.834584988 +0100 ++++ curl-8.9.0/lib/system_win32.c 2025-02-18 16:06:26.448941452 +0100 +@@ -273,7 +273,7 @@ + + bool Curl_win32_impersonating(void) + { +-#ifndef CURL_WINDOWS_APP ++#if !defined(CURL_WINDOWS_APP) && !defined(__MINGW32__) + HANDLE token = NULL; + if(OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token)) { + CloseHandle(token); diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/Patches/dcmtk-3.6.9-visual-studio.patch --- a/OrthancFramework/Resources/Patches/dcmtk-3.6.9-visual-studio.patch Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.9-visual-studio.patch Tue Mar 18 13:35:00 2025 +0100 @@ -1,6 +1,6 @@ diff -urEb dcmtk-3.6.9.orig/config/math.cc dcmtk-3.6.9/config/math.cc ---- dcmtk-3.6.9.orig/config/math.cc 2025-01-16 18:04:46.121846434 +0100 -+++ dcmtk-3.6.9/config/math.cc 2025-01-17 07:45:46.252026376 +0100 +--- dcmtk-3.6.9.orig/config/math.cc 2025-02-18 18:03:13.501406015 +0100 ++++ dcmtk-3.6.9/config/math.cc 2025-02-18 18:05:52.950086576 +0100 @@ -42,11 +42,17 @@ #include #endif @@ -54,8 +54,8 @@ #else return ::isinf( d ); diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg12/jccoefct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jccoefct.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jccoefct.c 2025-01-16 20:11:42.016717178 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jccoefct.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jccoefct.c 2025-02-18 18:05:52.950086576 +0100 @@ -343,7 +343,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -66,8 +66,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg12/jcdiffct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcdiffct.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jcdiffct.c 2025-01-16 20:11:42.016717178 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcdiffct.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jcdiffct.c 2025-02-18 18:05:52.951086562 +0100 @@ -302,7 +302,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -78,8 +78,8 @@ c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private; /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg12/jcpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcpred.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jcpred.c 2025-01-16 20:11:42.017717169 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jcpred.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jcpred.c 2025-02-18 18:05:52.951086562 +0100 @@ -213,7 +213,7 @@ const JSAMPROW input_buf, JSAMPROW prev_row, JDIFFROW diff_buf, JDIMENSION width) @@ -90,8 +90,8 @@ /* diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg12/jctrans.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jctrans.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jctrans.c 2025-01-16 20:11:42.017717169 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jctrans.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jctrans.c 2025-02-18 18:05:52.951086562 +0100 @@ -267,7 +267,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -102,8 +102,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg12/jdmerge.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdmerge.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jdmerge.c 2025-01-16 20:11:42.017717169 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdmerge.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdmerge.c 2025-02-18 18:05:52.951086562 +0100 @@ -148,7 +148,7 @@ JDIMENSION out_rows_avail) /* 2:1 vertical sampling case: may need a spare row. */ @@ -125,8 +125,8 @@ /* Just do the upsampling. */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg12/jdpostct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpostct.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpostct.c 2025-01-16 20:11:42.017717169 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpostct.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpostct.c 2025-02-18 18:05:52.952086549 +0100 @@ -161,8 +161,8 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -152,8 +152,8 @@ my_post_ptr post = (my_post_ptr) cinfo->post; JDIMENSION num_rows, max_rows; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg12/jdpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpred.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpred.c 2025-01-16 20:11:42.017717169 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdpred.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdpred.c 2025-02-18 18:05:52.952086549 +0100 @@ -101,8 +101,8 @@ const JDIFFROW diff_buf, const JDIFFROW prev_row, JDIFFROW undiff_buf, JDIMENSION width) @@ -241,8 +241,8 @@ UNDIFFERENCE_1D(INITIAL_PREDICTORx); diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg12/jdsample.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdsample.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jdsample.c 2025-01-16 20:11:42.018717160 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdsample.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdsample.c 2025-02-18 18:05:52.952086549 +0100 @@ -92,7 +92,7 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -295,8 +295,8 @@ register JSAMPROW inptr, outptr; register JSAMPLE invalue; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg12/jdscale.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdscale.c 2025-01-16 18:04:46.157846124 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jdscale.c 2025-01-16 20:11:42.018717160 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jdscale.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jdscale.c 2025-02-18 18:05:52.953086535 +0100 @@ -67,7 +67,7 @@ const JDIFFROW diff_buf, JSAMPROW output_buf, JDIMENSION width) @@ -307,8 +307,8 @@ for (xindex = 0; xindex < width; xindex++) diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jerror.c dcmtk-3.6.9/dcmjpeg/libijg12/jerror.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jerror.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jerror.c 2025-01-17 11:14:11.975382089 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jerror.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jerror.c 2025-02-18 18:05:52.953086535 +0100 @@ -34,6 +34,10 @@ #define EXIT_FAILURE 1 #endif @@ -321,8 +321,8 @@ /* * Create the message string table. diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg12/jquant1.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant1.c 2025-01-16 18:04:46.157846124 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant1.c 2025-01-16 20:11:42.018717160 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant1.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant1.c 2025-02-18 18:05:52.953086535 +0100 @@ -251,8 +251,8 @@ * (Forcing the upper and lower values to the limits ensures that * dithering can't produce a color outside the selected gamut.) @@ -364,8 +364,8 @@ } diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg12/jquant2.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant2.c 2025-01-16 18:04:46.157846124 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant2.c 2025-01-16 20:11:42.018717160 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg12/jquant2.c 2025-02-18 18:03:13.530405562 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg12/jquant2.c 2025-02-18 18:05:52.954086521 +0100 @@ -224,7 +224,7 @@ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) @@ -385,8 +385,8 @@ } diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg16/jccoefct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jccoefct.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jccoefct.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jccoefct.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jccoefct.c 2025-02-18 18:05:52.954086521 +0100 @@ -343,7 +343,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -397,8 +397,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg16/jcdiffct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcdiffct.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jcdiffct.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcdiffct.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jcdiffct.c 2025-02-18 18:05:52.954086521 +0100 @@ -302,7 +302,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -409,8 +409,8 @@ c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private; /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg16/jcpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcpred.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jcpred.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jcpred.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jcpred.c 2025-02-18 18:05:52.954086521 +0100 @@ -213,7 +213,7 @@ const JSAMPROW input_buf, JSAMPROW prev_row, JDIFFROW diff_buf, JDIMENSION width) @@ -421,8 +421,8 @@ /* diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg16/jctrans.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jctrans.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jctrans.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jctrans.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jctrans.c 2025-02-18 18:05:52.954086521 +0100 @@ -267,7 +267,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -433,8 +433,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg16/jdmerge.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdmerge.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jdmerge.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdmerge.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdmerge.c 2025-02-18 18:05:52.955086508 +0100 @@ -169,7 +169,7 @@ JDIMENSION out_rows_avail) /* 2:1 vertical sampling case: may need a spare row. */ @@ -456,8 +456,8 @@ /* Just do the upsampling. */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg16/jdpostct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpostct.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpostct.c 2025-01-16 20:11:42.019717150 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpostct.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpostct.c 2025-02-18 18:05:52.955086508 +0100 @@ -161,8 +161,8 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -483,8 +483,8 @@ JDIMENSION num_rows, max_rows; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg16/jdpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpred.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpred.c 2025-01-16 20:11:42.020717140 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdpred.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdpred.c 2025-02-18 18:05:52.955086508 +0100 @@ -101,8 +101,8 @@ const JDIFFROW diff_buf, const JDIFFROW prev_row, JDIFFROW undiff_buf, JDIMENSION width) @@ -616,8 +616,8 @@ UNDIFFERENCE_1D(INITIAL_PREDICTORx); diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg16/jdsample.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdsample.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jdsample.c 2025-01-16 20:11:42.020717140 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdsample.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdsample.c 2025-02-18 18:05:52.955086508 +0100 @@ -92,7 +92,7 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -670,8 +670,8 @@ register JSAMPROW inptr, outptr; register JSAMPLE invalue; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg16/jdscale.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdscale.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jdscale.c 2025-01-16 20:11:42.020717140 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jdscale.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jdscale.c 2025-02-18 18:05:52.955086508 +0100 @@ -67,7 +67,7 @@ const JDIFFROW diff_buf, JSAMPROW output_buf, JDIMENSION width) @@ -682,8 +682,8 @@ for (xindex = 0; xindex < width; xindex++) diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jerror.c dcmtk-3.6.9/dcmjpeg/libijg16/jerror.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jerror.c 2025-01-16 18:04:46.159846107 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jerror.c 2025-01-17 11:14:19.002328399 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jerror.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jerror.c 2025-02-18 18:05:52.955086508 +0100 @@ -34,6 +34,10 @@ #define EXIT_FAILURE 1 #endif @@ -696,8 +696,8 @@ /* * Create the message string table. diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg16/jquant1.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant1.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant1.c 2025-01-16 20:11:42.020717140 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant1.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant1.c 2025-02-18 18:05:52.956086494 +0100 @@ -251,8 +251,8 @@ * (Forcing the upper and lower values to the limits ensures that * dithering can't produce a color outside the selected gamut.) @@ -739,8 +739,8 @@ } diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg16/jquant2.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant2.c 2025-01-16 18:04:46.158846116 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant2.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg16/jquant2.c 2025-02-18 18:03:13.531405546 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg16/jquant2.c 2025-02-18 18:05:52.956086494 +0100 @@ -224,7 +224,7 @@ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) @@ -760,8 +760,8 @@ } diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jccoefct.c dcmtk-3.6.9/dcmjpeg/libijg8/jccoefct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jccoefct.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jccoefct.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jccoefct.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jccoefct.c 2025-02-18 18:05:52.956086494 +0100 @@ -343,7 +343,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -772,8 +772,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcdiffct.c dcmtk-3.6.9/dcmjpeg/libijg8/jcdiffct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcdiffct.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jcdiffct.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcdiffct.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jcdiffct.c 2025-02-18 18:05:52.956086494 +0100 @@ -302,7 +302,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -784,8 +784,8 @@ c_diff_ptr diff = (c_diff_ptr) losslsc->diff_private; /* JDIMENSION MCU_col_num; */ /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcpred.c dcmtk-3.6.9/dcmjpeg/libijg8/jcpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcpred.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jcpred.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jcpred.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jcpred.c 2025-02-18 18:05:52.957086481 +0100 @@ -213,7 +213,7 @@ const JSAMPROW input_buf, JSAMPROW prev_row, JDIFFROW diff_buf, JDIMENSION width) @@ -796,8 +796,8 @@ /* diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jctrans.c dcmtk-3.6.9/dcmjpeg/libijg8/jctrans.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jctrans.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jctrans.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jctrans.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jctrans.c 2025-02-18 18:05:52.957086481 +0100 @@ -267,7 +267,7 @@ METHODDEF(boolean) compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) @@ -808,8 +808,8 @@ c_coef_ptr coef = (c_coef_ptr) lossyc->coef_private; JDIMENSION MCU_col_num; /* index of current MCU within row */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdmerge.c dcmtk-3.6.9/dcmjpeg/libijg8/jdmerge.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdmerge.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jdmerge.c 2025-01-16 20:11:42.021717131 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdmerge.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdmerge.c 2025-02-18 18:05:52.957086481 +0100 @@ -148,7 +148,7 @@ JDIMENSION out_rows_avail) /* 2:1 vertical sampling case: may need a spare row. */ @@ -831,8 +831,8 @@ /* Just do the upsampling. */ diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpostct.c dcmtk-3.6.9/dcmjpeg/libijg8/jdpostct.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpostct.c 2025-01-16 18:04:46.160846099 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpostct.c 2025-01-16 20:11:42.022717122 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpostct.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpostct.c 2025-02-18 18:05:52.957086481 +0100 @@ -161,8 +161,8 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -858,8 +858,8 @@ JDIMENSION num_rows, max_rows; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpred.c dcmtk-3.6.9/dcmjpeg/libijg8/jdpred.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpred.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpred.c 2025-01-16 20:11:42.022717122 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdpred.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdpred.c 2025-02-18 18:05:52.957086481 +0100 @@ -101,8 +101,8 @@ const JDIFFROW diff_buf, const JDIFFROW prev_row, JDIFFROW undiff_buf, JDIMENSION width) @@ -947,8 +947,8 @@ UNDIFFERENCE_1D(INITIAL_PREDICTORx); diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdsample.c dcmtk-3.6.9/dcmjpeg/libijg8/jdsample.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdsample.c 2025-01-16 18:04:46.161846090 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jdsample.c 2025-01-16 20:11:42.022717122 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdsample.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdsample.c 2025-02-18 18:05:52.957086481 +0100 @@ -92,7 +92,7 @@ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail) @@ -1001,8 +1001,8 @@ register JSAMPROW inptr, outptr; register JSAMPLE invalue; diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdscale.c dcmtk-3.6.9/dcmjpeg/libijg8/jdscale.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdscale.c 2025-01-16 18:04:46.160846099 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jdscale.c 2025-01-16 20:11:42.022717122 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jdscale.c 2025-02-18 18:03:13.532405530 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jdscale.c 2025-02-18 18:05:52.958086467 +0100 @@ -67,7 +67,7 @@ const JDIFFROW diff_buf, JSAMPROW output_buf, JDIMENSION width) @@ -1013,8 +1013,8 @@ for (xindex = 0; xindex < width; xindex++) diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jerror.c dcmtk-3.6.9/dcmjpeg/libijg8/jerror.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jerror.c 2025-01-16 18:04:46.160846099 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jerror.c 2025-01-17 11:14:02.543454148 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jerror.c 2025-02-18 18:03:13.533405515 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jerror.c 2025-02-18 18:05:52.958086467 +0100 @@ -34,6 +34,10 @@ #define EXIT_FAILURE 1 #endif @@ -1027,8 +1027,8 @@ /* * Create the message string table. diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant1.c dcmtk-3.6.9/dcmjpeg/libijg8/jquant1.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant1.c 2025-01-16 18:04:46.160846099 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant1.c 2025-01-16 20:11:42.022717122 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant1.c 2025-02-18 18:03:13.532405530 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant1.c 2025-02-18 18:05:52.958086467 +0100 @@ -251,8 +251,8 @@ * (Forcing the upper and lower values to the limits ensures that * dithering can't produce a color outside the selected gamut.) @@ -1070,8 +1070,8 @@ } diff -urEb dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant2.c dcmtk-3.6.9/dcmjpeg/libijg8/jquant2.c ---- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant2.c 2025-01-16 18:04:46.160846099 +0100 -+++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant2.c 2025-01-16 20:11:42.023717112 +0100 +--- dcmtk-3.6.9.orig/dcmjpeg/libijg8/jquant2.c 2025-02-18 18:03:13.532405530 +0100 ++++ dcmtk-3.6.9/dcmjpeg/libijg8/jquant2.c 2025-02-18 18:05:52.958086467 +0100 @@ -224,7 +224,7 @@ prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) @@ -1091,8 +1091,8 @@ } diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_bcs.h dcmtk-3.6.9/oficonv/libsrc/citrus_bcs.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_bcs.h 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_bcs.h 2025-01-17 11:14:44.746131630 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_bcs.h 2025-02-18 18:03:13.519405733 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_bcs.h 2025-02-18 18:05:52.958086467 +0100 @@ -39,7 +39,21 @@ #include #include @@ -1116,8 +1116,8 @@ #define CITRUS_DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_big5.c dcmtk-3.6.9/oficonv/libsrc/citrus_big5.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_big5.c 2025-01-16 18:04:46.137846297 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_big5.c 2025-01-17 09:59:15.249381150 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_big5.c 2025-02-18 18:03:13.511405858 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_big5.c 2025-02-18 18:05:52.959086454 +0100 @@ -218,13 +218,6 @@ return (0); } @@ -1152,8 +1152,8 @@ TAILQ_INIT(&ei->excludes); diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_db_hash.h dcmtk-3.6.9/oficonv/libsrc/citrus_db_hash.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_db_hash.h 2025-01-16 18:04:46.135846314 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_db_hash.h 2025-01-17 09:36:14.692449741 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_db_hash.h 2025-02-18 18:03:13.510405874 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_db_hash.h 2025-02-18 18:05:52.959086454 +0100 @@ -29,7 +29,8 @@ #include "dcmtk/config/osconfig.h" @@ -1165,8 +1165,8 @@ struct _citrus_region; diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.c dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.c 2025-01-16 18:04:46.137846297 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.c 2025-01-17 07:57:57.711032732 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.c 2025-02-18 18:03:13.511405858 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.c 2025-02-18 18:05:52.959086454 +0100 @@ -34,7 +34,15 @@ #include @@ -1184,8 +1184,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.h dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.h 2025-01-16 18:04:46.137846297 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.h 2025-01-17 07:58:02.525992089 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_esdb.h 2025-02-18 18:03:13.512405843 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_esdb.h 2025-02-18 18:05:52.959086454 +0100 @@ -29,7 +29,14 @@ #include "dcmtk/config/osconfig.h" @@ -1202,8 +1202,8 @@ struct _citrus_esdb_charset { _citrus_csid_t ec_csid; diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_gbk2k.c dcmtk-3.6.9/oficonv/libsrc/citrus_gbk2k.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_gbk2k.c 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_gbk2k.c 2025-01-17 07:58:08.702939916 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_gbk2k.c 2025-02-18 18:03:13.519405733 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_gbk2k.c 2025-02-18 18:05:52.959086454 +0100 @@ -34,7 +34,15 @@ #include @@ -1221,8 +1221,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_hz.c dcmtk-3.6.9/oficonv/libsrc/citrus_hz.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_hz.c 2025-01-16 18:04:46.144846236 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_hz.c 2025-01-17 10:04:16.511792074 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_hz.c 2025-02-18 18:03:13.518405749 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_hz.c 2025-02-18 18:05:52.959086454 +0100 @@ -571,13 +571,6 @@ return (0); } @@ -1284,48 +1284,9 @@ memset(ei, 0, sizeof(*ei)); TAILQ_INIT(E0SET(ei)); TAILQ_INIT(E1SET(ei)); -diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c 2025-01-17 09:23:11.596327937 +0100 -@@ -49,7 +49,15 @@ - #endif - - #include -+ -+#if (_MSC_VER >= 1900) - #include -+#else -+#define bool int -+#define false 0 -+#define true 1 -+#endif -+ - #include - #include - #include -@@ -299,14 +307,17 @@ - _citrus_iconv_open(struct _citrus_iconv * * rcv, - const char * src, const char * dst) - { --struct _citrus_iconv *cv = NULL; -+#ifdef HAVE_WINDOWS_H -+ char current_codepage[20]; -+#endif -+ -+ struct _citrus_iconv *cv = NULL; - struct _citrus_iconv_shared *ci = NULL; - char realdst[OFICONV_PATH_MAX], realsrc[OFICONV_PATH_MAX]; - int ret; - - init_cache(); - #ifdef HAVE_WINDOWS_H -- char current_codepage[20]; - snprintf(current_codepage, sizeof(current_codepage), "%lu", (unsigned long) GetConsoleOutputCP()); - #endif - diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_local.h dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_local.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_local.h 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_local.h 2025-01-17 07:58:20.317841727 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_local.h 2025-02-18 18:03:13.520405718 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_local.h 2025-02-18 18:05:52.960086440 +0100 @@ -29,7 +29,15 @@ #include "dcmtk/config/osconfig.h" @@ -1343,8 +1304,8 @@ #ifdef HAVE_SYS_QUEUE_H diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_none.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_none.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_none.c 2025-01-16 18:04:46.141846262 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_none.c 2025-01-17 07:58:25.509797800 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_none.c 2025-02-18 18:03:13.516405780 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_none.c 2025-02-18 18:05:52.960086440 +0100 @@ -35,7 +35,15 @@ @@ -1362,8 +1323,8 @@ #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_std.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_std.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_std.c 2025-01-16 18:04:46.141846262 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_std.c 2025-01-17 07:58:30.205758052 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv_std.c 2025-02-18 18:03:13.516405780 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv_std.c 2025-02-18 18:05:52.960086440 +0100 @@ -36,7 +36,15 @@ #include @@ -1381,8 +1342,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iso2022.c dcmtk-3.6.9/oficonv/libsrc/citrus_iso2022.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iso2022.c 2025-01-16 18:04:46.142846254 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_iso2022.c 2025-01-17 07:58:35.485713336 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iso2022.c 2025-02-18 18:03:13.516405780 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iso2022.c 2025-02-18 18:05:52.960086440 +0100 @@ -35,7 +35,15 @@ #include @@ -1400,8 +1361,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_johab.c dcmtk-3.6.9/oficonv/libsrc/citrus_johab.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_johab.c 2025-01-16 18:04:46.144846236 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_johab.c 2025-01-17 07:58:40.390671776 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_johab.c 2025-02-18 18:03:13.518405749 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_johab.c 2025-02-18 18:05:52.961086427 +0100 @@ -34,7 +34,15 @@ #include @@ -1419,8 +1380,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_module.c dcmtk-3.6.9/oficonv/libsrc/citrus_module.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_module.c 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_module.c 2025-01-17 07:58:45.172631238 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_module.c 2025-02-18 18:03:13.520405718 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_module.c 2025-02-18 18:05:52.961086427 +0100 @@ -98,7 +98,15 @@ #endif #include @@ -1438,8 +1399,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mskanji.c dcmtk-3.6.9/oficonv/libsrc/citrus_mskanji.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mskanji.c 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_mskanji.c 2025-01-17 07:58:51.749575452 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mskanji.c 2025-02-18 18:03:13.519405733 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_mskanji.c 2025-02-18 18:05:52.961086427 +0100 @@ -67,7 +67,15 @@ #include @@ -1457,8 +1418,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_prop.c dcmtk-3.6.9/oficonv/libsrc/citrus_prop.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_prop.c 2025-01-16 18:04:46.140846271 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_prop.c 2025-01-17 07:58:55.534543331 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_prop.c 2025-02-18 18:03:13.514405812 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_prop.c 2025-02-18 18:05:52.961086427 +0100 @@ -30,7 +30,15 @@ #include @@ -1476,8 +1437,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_region.h dcmtk-3.6.9/oficonv/libsrc/citrus_region.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_region.h 2025-01-16 18:04:46.142846254 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_region.h 2025-01-17 07:59:02.917480641 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_region.h 2025-02-18 18:03:13.516405780 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_region.h 2025-02-18 18:05:52.961086427 +0100 @@ -31,7 +31,14 @@ #include "dcmtk/config/osconfig.h" #include @@ -1494,8 +1455,8 @@ #ifdef HAVE_SYS_TYPES_H #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_utf8.c dcmtk-3.6.9/oficonv/libsrc/citrus_utf8.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_utf8.c 2025-01-16 18:04:46.146846219 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_utf8.c 2025-01-17 07:57:46.519127132 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_utf8.c 2025-02-18 18:03:13.519405733 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_utf8.c 2025-02-18 18:05:52.961086427 +0100 @@ -66,7 +66,15 @@ #include @@ -1513,8 +1474,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/oficonv_iconv.c dcmtk-3.6.9/oficonv/libsrc/oficonv_iconv.c ---- dcmtk-3.6.9.orig/oficonv/libsrc/oficonv_iconv.c 2025-01-16 18:04:46.137846297 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/oficonv_iconv.c 2025-01-17 09:16:42.622548106 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/oficonv_iconv.c 2025-02-18 18:03:13.512405843 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/oficonv_iconv.c 2025-02-18 18:05:52.962086413 +0100 @@ -40,7 +40,15 @@ #include @@ -1532,8 +1493,8 @@ #include #include diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/windows_mmap.h dcmtk-3.6.9/oficonv/libsrc/windows_mmap.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/windows_mmap.h 2025-01-16 18:04:46.137846297 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/windows_mmap.h 2025-01-17 09:21:53.565976060 +0100 +--- dcmtk-3.6.9.orig/oficonv/libsrc/windows_mmap.h 2025-02-18 18:03:13.511405858 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/windows_mmap.h 2025-02-18 18:05:52.962086413 +0100 @@ -74,6 +74,12 @@ static void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) @@ -1599,8 +1560,8 @@ { if (mc->addr == addr) diff -urEb dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/oftypes.h dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/oftypes.h ---- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/oftypes.h 2025-01-16 18:04:46.148846202 +0100 -+++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/oftypes.h 2025-01-17 07:49:26.122182761 +0100 +--- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/oftypes.h 2025-02-18 18:03:13.523405671 +0100 ++++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/oftypes.h 2025-02-18 18:05:52.962086413 +0100 @@ -79,10 +79,9 @@ #include diff -r dfc271554574 -r ce002301ecea OrthancFramework/Resources/Patches/dcmtk-3.6.9.patch --- a/OrthancFramework/Resources/Patches/dcmtk-3.6.9.patch Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.9.patch Tue Mar 18 13:35:00 2025 +0100 @@ -1,6 +1,6 @@ diff -urEb dcmtk-3.6.9.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.9/CMake/GenerateDCMTKConfigure.cmake ---- dcmtk-3.6.9.orig/CMake/GenerateDCMTKConfigure.cmake 2025-01-21 15:53:12.632715225 +0100 -+++ dcmtk-3.6.9/CMake/GenerateDCMTKConfigure.cmake 2025-01-21 15:53:27.614627545 +0100 +--- dcmtk-3.6.9.orig/CMake/GenerateDCMTKConfigure.cmake 2025-02-18 18:03:13.505405952 +0100 ++++ dcmtk-3.6.9/CMake/GenerateDCMTKConfigure.cmake 2025-02-18 18:06:53.925278621 +0100 @@ -227,12 +227,15 @@ # Check the sizes of various types @@ -18,8 +18,8 @@ # Check for include files, libraries, and functions include("${DCMTK_CMAKE_INCLUDE}CMake/dcmtkTryCompile.cmake") diff -urEb dcmtk-3.6.9.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.9/dcmdata/include/dcmtk/dcmdata/dcdict.h ---- dcmtk-3.6.9.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2025-01-21 15:53:12.622715283 +0100 -+++ dcmtk-3.6.9/dcmdata/include/dcmtk/dcmdata/dcdict.h 2025-01-21 15:53:27.614627545 +0100 +--- dcmtk-3.6.9.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2025-02-18 18:03:13.497406077 +0100 ++++ dcmtk-3.6.9/dcmdata/include/dcmtk/dcmdata/dcdict.h 2025-02-18 18:06:53.925278621 +0100 @@ -163,6 +163,12 @@ /// returns an iterator to the end of the repeating groups data dictionary DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } @@ -34,8 +34,8 @@ /** private undefined assignment operator diff -urEb dcmtk-3.6.9.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.9/dcmdata/libsrc/dcdict.cc ---- dcmtk-3.6.9.orig/dcmdata/libsrc/dcdict.cc 2025-01-21 15:53:12.625715265 +0100 -+++ dcmtk-3.6.9/dcmdata/libsrc/dcdict.cc 2025-01-21 15:53:27.615627539 +0100 +--- dcmtk-3.6.9.orig/dcmdata/libsrc/dcdict.cc 2025-02-18 18:03:13.499406046 +0100 ++++ dcmtk-3.6.9/dcmdata/libsrc/dcdict.cc 2025-02-18 18:06:53.926278608 +0100 @@ -904,3 +904,5 @@ wrlock().clear(); wrunlock(); @@ -43,8 +43,8 @@ + +#include "dcdict_orthanc.cc" diff -urEb dcmtk-3.6.9.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.9/dcmdata/libsrc/dcpxitem.cc ---- dcmtk-3.6.9.orig/dcmdata/libsrc/dcpxitem.cc 2025-01-21 15:53:12.623715277 +0100 -+++ dcmtk-3.6.9/dcmdata/libsrc/dcpxitem.cc 2025-01-21 15:53:27.615627539 +0100 +--- dcmtk-3.6.9.orig/dcmdata/libsrc/dcpxitem.cc 2025-02-18 18:03:13.497406077 +0100 ++++ dcmtk-3.6.9/dcmdata/libsrc/dcpxitem.cc 2025-02-18 18:06:53.926278608 +0100 @@ -31,6 +31,8 @@ #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ @@ -55,8 +55,8 @@ // ******************************** diff -urEb dcmtk-3.6.9.orig/dcmnet/libsrc/scu.cc dcmtk-3.6.9/dcmnet/libsrc/scu.cc ---- dcmtk-3.6.9.orig/dcmnet/libsrc/scu.cc 2025-01-21 15:53:12.655715091 +0100 -+++ dcmtk-3.6.9/dcmnet/libsrc/scu.cc 2025-01-21 15:53:27.616627533 +0100 +--- dcmtk-3.6.9.orig/dcmnet/libsrc/scu.cc 2025-02-18 18:03:13.525405640 +0100 ++++ dcmtk-3.6.9/dcmnet/libsrc/scu.cc 2025-02-18 18:06:53.927278595 +0100 @@ -19,6 +19,11 @@ * */ @@ -70,8 +70,8 @@ #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ diff -urEb dcmtk-3.6.9.orig/oficonv/include/dcmtk/oficonv/iconv.h dcmtk-3.6.9/oficonv/include/dcmtk/oficonv/iconv.h ---- dcmtk-3.6.9.orig/oficonv/include/dcmtk/oficonv/iconv.h 2025-01-21 15:53:12.637715196 +0100 -+++ dcmtk-3.6.9/oficonv/include/dcmtk/oficonv/iconv.h 2025-01-21 15:53:27.617627527 +0100 +--- dcmtk-3.6.9.orig/oficonv/include/dcmtk/oficonv/iconv.h 2025-02-18 18:03:13.510405874 +0100 ++++ dcmtk-3.6.9/oficonv/include/dcmtk/oficonv/iconv.h 2025-02-18 18:06:53.927278595 +0100 @@ -55,7 +55,12 @@ #endif @@ -85,21 +85,138 @@ #ifndef OFICONV_CITRUS_WC_T_DEFINED #define OFICONV_CITRUS_WC_T_DEFINED +diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_csmapper.c dcmtk-3.6.9/oficonv/libsrc/citrus_csmapper.c +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_csmapper.c 2025-02-18 18:03:13.510405874 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_csmapper.c 2025-02-18 18:06:53.927278595 +0100 +@@ -63,7 +63,8 @@ + + #ifdef WITH_THREADS + #ifdef HAVE_WINDOWS_H +-static SRWLOCK ma_lock = SRWLOCK_INIT; ++static int ma_lock_initialized = 0; ++static CRITICAL_SECTION ma_lock; + #elif defined(HAVE_PTHREAD_H) + static pthread_rwlock_t ma_lock = PTHREAD_RWLOCK_INITIALIZER; + #endif +@@ -382,6 +383,14 @@ + char mapper_path[OFICONV_PATH_MAX]; + unsigned long norm; + int ret; ++ ++#if defined(WITH_THREADS) && defined(HAVE_WINDOWS_H) ++ if (ma_lock_initialized == 0) { /* Very minor risk of race condition here */ ++ InitializeCriticalSection(&ma_lock); ++ ma_lock_initialized = 1; ++ } ++#endif ++ + norm = 0; + + getCSMapperPath(mapper_path, sizeof(mapper_path), NULL); +diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_iconv.c 2025-02-18 18:03:13.520405718 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_iconv.c 2025-02-18 18:10:35.928614598 +0100 +@@ -49,7 +49,15 @@ + #endif + + #include ++ ++#if (_MSC_VER >= 1900) + #include ++#else ++#define bool int ++#define false 0 ++#define true 1 ++#endif ++ + #include + #include + #include +@@ -80,7 +88,8 @@ + + #ifdef WITH_THREADS + #ifdef HAVE_WINDOWS_H +-static SRWLOCK ci_lock = SRWLOCK_INIT; ++static int ci_lock_initialized = 0; ++static CRITICAL_SECTION ci_lock; + #elif defined(HAVE_PTHREAD_H) + static pthread_rwlock_t ci_lock = PTHREAD_RWLOCK_INITIALIZER; + #endif +@@ -299,14 +308,24 @@ + _citrus_iconv_open(struct _citrus_iconv * * rcv, + const char * src, const char * dst) + { +-struct _citrus_iconv *cv = NULL; ++#ifdef HAVE_WINDOWS_H ++ char current_codepage[20]; ++#endif ++ ++ struct _citrus_iconv *cv = NULL; + struct _citrus_iconv_shared *ci = NULL; + char realdst[OFICONV_PATH_MAX], realsrc[OFICONV_PATH_MAX]; + int ret; + ++#if defined(WITH_THREADS) && defined(HAVE_WINDOWS_H) ++ if (ci_lock_initialized == 0) { /* Very minor risk of race condition here */ ++ InitializeCriticalSection(&ci_lock); ++ ci_lock_initialized = 1; ++ } ++#endif ++ + init_cache(); + #ifdef HAVE_WINDOWS_H +- char current_codepage[20]; + snprintf(current_codepage, sizeof(current_codepage), "%lu", (unsigned long) GetConsoleOutputCP()); + #endif + diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_lock.h dcmtk-3.6.9/oficonv/libsrc/citrus_lock.h ---- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_lock.h 2025-01-21 15:53:12.646715143 +0100 -+++ dcmtk-3.6.9/oficonv/libsrc/citrus_lock.h 2025-01-21 16:43:36.463693959 +0100 -@@ -31,7 +31,7 @@ +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_lock.h 2025-02-18 18:03:13.518405749 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_lock.h 2025-02-18 18:06:53.927278595 +0100 +@@ -31,11 +31,11 @@ #ifdef WITH_THREADS -#ifdef HAVE_WINDOWS_H -+#if defined(HAVE_WINDOWS_H) && !defined(HAVE_PTHREAD_H) /* Favor pthread if available, for MinGW */ ++#if defined(HAVE_WINDOWS_H) #include - #define WLOCK(lock) AcquireSRWLockExclusive(lock); +-#define WLOCK(lock) AcquireSRWLockExclusive(lock); +-#define UNLOCK(lock) ReleaseSRWLockExclusive(lock); ++#define WLOCK(lock) EnterCriticalSection(lock); ++#define UNLOCK(lock) LeaveCriticalSection(lock); + + #else /* HAVE_WINDOWS_H */ + +diff -urEb dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mapper.c dcmtk-3.6.9/oficonv/libsrc/citrus_mapper.c +--- dcmtk-3.6.9.orig/oficonv/libsrc/citrus_mapper.c 2025-02-18 18:03:13.516405780 +0100 ++++ dcmtk-3.6.9/oficonv/libsrc/citrus_mapper.c 2025-02-18 18:06:53.928278582 +0100 +@@ -64,7 +64,8 @@ + + #ifdef WITH_THREADS + #ifdef HAVE_WINDOWS_H +-static SRWLOCK cm_lock = SRWLOCK_INIT; ++static int cm_lock_initialized = 0; ++static CRITICAL_SECTION cm_lock; + #elif defined(HAVE_PTHREAD_H) + static pthread_rwlock_t cm_lock = PTHREAD_RWLOCK_INITIALIZER; + #endif +@@ -355,6 +356,13 @@ + const char *module, *variable; + int hashval, ret; + ++#if defined(WITH_THREADS) && defined(HAVE_WINDOWS_H) ++ if (cm_lock_initialized == 0) { /* Very minor risk of race condition here */ ++ InitializeCriticalSection(&cm_lock); ++ cm_lock_initialized = 1; ++ } ++#endif ++ + variable = NULL; + + WLOCK(&cm_lock); diff -urEb dcmtk-3.6.9.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.9/oflog/include/dcmtk/oflog/thread/syncpub.h ---- dcmtk-3.6.9.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2025-01-21 15:53:12.605715381 +0100 -+++ dcmtk-3.6.9/oflog/include/dcmtk/oflog/thread/syncpub.h 2025-01-21 15:53:27.617627527 +0100 +--- dcmtk-3.6.9.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2025-02-18 18:03:13.473406452 +0100 ++++ dcmtk-3.6.9/oflog/include/dcmtk/oflog/thread/syncpub.h 2025-02-18 18:06:53.928278582 +0100 @@ -63,7 +63,7 @@ DCMTK_LOG4CPLUS_INLINE_EXPORT @@ -137,8 +254,8 @@ diff -urEb dcmtk-3.6.9.orig/oflog/libsrc/oflog.cc dcmtk-3.6.9/oflog/libsrc/oflog.cc ---- dcmtk-3.6.9.orig/oflog/libsrc/oflog.cc 2025-01-21 15:53:12.606715376 +0100 -+++ dcmtk-3.6.9/oflog/libsrc/oflog.cc 2025-01-21 15:53:27.617627527 +0100 +--- dcmtk-3.6.9.orig/oflog/libsrc/oflog.cc 2025-02-18 18:03:13.475406421 +0100 ++++ dcmtk-3.6.9/oflog/libsrc/oflog.cc 2025-02-18 18:06:53.928278582 +0100 @@ -19,6 +19,11 @@ * */ @@ -152,8 +269,8 @@ #include "dcmtk/oflog/oflog.h" diff -urEb dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/offile.h ---- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/offile.h 2025-01-21 15:53:12.651715114 +0100 -+++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/offile.h 2025-01-21 15:53:27.618627521 +0100 +--- dcmtk-3.6.9.orig/ofstd/include/dcmtk/ofstd/offile.h 2025-02-18 18:03:13.523405671 +0100 ++++ dcmtk-3.6.9/ofstd/include/dcmtk/ofstd/offile.h 2025-02-18 18:06:53.929278570 +0100 @@ -569,7 +569,7 @@ */ void setlinebuf() @@ -164,8 +281,8 @@ #else :: setlinebuf(file_); diff -urEb dcmtk-3.6.9.orig/ofstd/libsrc/ofstub.cc dcmtk-3.6.9/ofstd/libsrc/ofstub.cc ---- dcmtk-3.6.9.orig/ofstd/libsrc/ofstub.cc 2025-01-21 15:53:12.652715108 +0100 -+++ dcmtk-3.6.9/ofstd/libsrc/ofstub.cc 2025-01-21 15:53:27.618627521 +0100 +--- dcmtk-3.6.9.orig/ofstd/libsrc/ofstub.cc 2025-02-18 18:03:13.523405671 +0100 ++++ dcmtk-3.6.9/ofstd/libsrc/ofstub.cc 2025-02-18 18:06:53.929278570 +0100 @@ -35,6 +35,10 @@ #include #endif /* HAVE_WINDOWS_H */ diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -651,7 +651,7 @@ s += " " + std::string(GetTransferSyntaxUid(*it)); } - throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode instance of SOPClassUID " + + throw OrthancException(ErrorCode_InternalError, "Cannot transcode instance of SOPClassUID " + sopClassUid + " from " + std::string(GetTransferSyntaxUid(sourceSyntax)) + " to one of [" + s + " ]"); diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp --- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -51,7 +51,7 @@ namespace Orthanc { DcmtkTranscoder::DcmtkTranscoder(unsigned int maxConcurrentExecutions) : - lossyQuality_(90), + defaultLossyQuality_(90), maxConcurrentExecutionsSemaphore_(maxConcurrentExecutions) { } @@ -64,26 +64,26 @@ } - void DcmtkTranscoder::SetLossyQuality(unsigned int quality) + void DcmtkTranscoder::SetDefaultLossyQuality(unsigned int quality) { if (quality == 0 || quality > 100) { throw OrthancException( ErrorCode_ParameterOutOfRange, - "The quality for lossy transcoding must be an integer between 1 and 100, received: " + + "The default quality for lossy transcoding must be an integer between 1 and 100, received: " + boost::lexical_cast(quality)); } else { - LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality; - lossyQuality_ = quality; + LOG(INFO) << "Default quality for lossy transcoding using DCMTK is set to: " << quality; + defaultLossyQuality_ = quality; } } - unsigned int DcmtkTranscoder::GetLossyQuality() const + unsigned int DcmtkTranscoder::GetDefaultLossyQuality() const { - return lossyQuality_; + return defaultLossyQuality_; } bool TryTranscode(std::vector& failureReasons, /* out */ @@ -109,7 +109,8 @@ std::string& failureReason /* out */, DcmFileFormat& dicom, /* in/out */ const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) + bool allowNewSopInstanceUid, + unsigned int lossyQuality) { std::vector failureReasons; @@ -169,7 +170,7 @@ else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); + DJ_RPLossy parameters(lossyQuality); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) { @@ -195,7 +196,7 @@ else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); + DJ_RPLossy parameters(lossyQuality); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) { selectedSyntax = DicomTransferSyntax_JPEGProcess2_4; @@ -312,11 +313,20 @@ return false; } + bool DcmtkTranscoder::Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, defaultLossyQuality_); + } + bool DcmtkTranscoder::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) + bool allowNewSopInstanceUid, + unsigned int lossyQuality) { Semaphore::Locker lock(maxConcurrentExecutionsSemaphore_); // limit the number of concurrent executions @@ -363,7 +373,7 @@ return true; } else if (InplaceTranscode(targetSyntax, failureReason, source.GetParsed(), - allowedSyntaxes, allowNewSopInstanceUid)) + allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) { // Sanity check DicomTransferSyntax targetSyntax2; diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h --- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Tue Mar 18 13:35:00 2025 +0100 @@ -41,21 +41,22 @@ class ORTHANC_PUBLIC DcmtkTranscoder : public IDicomTranscoder { private: - unsigned int lossyQuality_; + unsigned int defaultLossyQuality_; Semaphore maxConcurrentExecutionsSemaphore_; bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, std::string& failureReason /* out */, DcmFileFormat& dicom, const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid); + bool allowNewSopInstanceUid, + unsigned int lossyQuality); public: explicit DcmtkTranscoder(unsigned int maxConcurrentExecutions); - void SetLossyQuality(unsigned int quality); + void SetDefaultLossyQuality(unsigned int quality); - unsigned int GetLossyQuality() const; + unsigned int GetDefaultLossyQuality() const; static bool IsSupported(DicomTransferSyntax syntax); @@ -63,5 +64,11 @@ DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) ORTHANC_OVERRIDE; }; } diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -1603,7 +1603,7 @@ // buffer if its size was overestimated by (*) ob.flush(); - size_t effectiveSize = static_cast(ob.tell()); + size_t effectiveSize = static_cast(ob.filled()); if (effectiveSize < buffer.size()) { buffer.resize(effectiveSize); diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h --- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Tue Mar 18 13:35:00 2025 +0100 @@ -116,6 +116,12 @@ const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) = 0; + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) = 0; + static std::string GetSopInstanceUid(DcmFileFormat& dicom); }; } diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp --- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -56,6 +56,14 @@ #endif } + bool MemoryBufferTranscoder::Transcode(DicomImage& target, + DicomImage& source, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQualityNotUsed) + { + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid); + } bool MemoryBufferTranscoder::Transcode(DicomImage& target, DicomImage& source, diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h --- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Tue Mar 18 13:35:00 2025 +0100 @@ -43,5 +43,11 @@ DicomImage& source, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target /* out */, + DicomImage& source, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQualityNotUsed) ORTHANC_OVERRIDE; }; } diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/FileStorage/StorageAccessor.cpp --- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -736,9 +736,10 @@ #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(HttpOutput& output, const FileInfo& info, - MimeType mime) + MimeType mime, + const std::string& contentFilename) { - AnswerFile(output, info, EnumerationToString(mime)); + AnswerFile(output, info, EnumerationToString(mime), contentFilename); } #endif @@ -746,10 +747,12 @@ #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(HttpOutput& output, const FileInfo& info, - const std::string& mime) + const std::string& mime, + const std::string& contentFilename) { BufferHttpSender sender; SetupSender(sender, info, mime); + sender.SetContentFilename(contentFilename); HttpStreamTranscoder transcoder(sender, CompressionType_None); // since 1.11.2, the storage accessor only returns uncompressed buffers output.Answer(transcoder); @@ -760,9 +763,10 @@ #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(RestApiOutput& output, const FileInfo& info, - MimeType mime) + MimeType mime, + const std::string& contentFilename) { - AnswerFile(output, info, EnumerationToString(mime)); + AnswerFile(output, info, EnumerationToString(mime), contentFilename); } #endif @@ -770,11 +774,13 @@ #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void StorageAccessor::AnswerFile(RestApiOutput& output, const FileInfo& info, - const std::string& mime) + const std::string& mime, + const std::string& contentFilename) { BufferHttpSender sender; SetupSender(sender, info, mime); - + sender.SetContentFilename(contentFilename); + HttpStreamTranscoder transcoder(sender, CompressionType_None); // since 1.11.2, the storage accessor only returns uncompressed buffers output.AnswerStream(transcoder); } diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/FileStorage/StorageAccessor.h --- a/OrthancFramework/Sources/FileStorage/StorageAccessor.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.h Tue Mar 18 13:35:00 2025 +0100 @@ -167,19 +167,23 @@ #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 void AnswerFile(HttpOutput& output, const FileInfo& info, - MimeType mime); + MimeType mime, + const std::string& contentFilename); void AnswerFile(HttpOutput& output, const FileInfo& info, - const std::string& mime); + const std::string& mime, + const std::string& contentFilename); void AnswerFile(RestApiOutput& output, const FileInfo& info, - MimeType mime); + MimeType mime, + const std::string& contentFilename); void AnswerFile(RestApiOutput& output, const FileInfo& info, - const std::string& mime); + const std::string& mime, + const std::string& contentFilename); #endif private: void ReadStartRangeInternal(std::string& target, diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/HttpServer/HttpServer.cpp --- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -699,7 +699,7 @@ const HttpToolbox::Arguments& headers, const HttpToolbox::GetArguments& argumentsGET) { - std::string overriden; + std::string overridden; // Check whether some PUT/DELETE faking is done @@ -709,7 +709,7 @@ if (methodOverride != headers.end()) { - overriden = methodOverride->second; + overridden = methodOverride->second; } else if (!strcmp(request->request_method, "GET")) { @@ -719,25 +719,25 @@ { if (argumentsGET[i].first == "_method") { - overriden = argumentsGET[i].second; + overridden = argumentsGET[i].second; break; } } } - if (overriden.size() > 0) + if (overridden.size() > 0) { // A faking has been done within this request - Toolbox::ToUpperCase(overriden); + Toolbox::ToUpperCase(overridden); - CLOG(INFO, HTTP) << "HTTP method faking has been detected for " << overriden; + CLOG(INFO, HTTP) << "HTTP method faking has been detected for " << overridden; - if (overriden == "PUT") + if (overridden == "PUT") { method = HttpMethod_Put; return true; } - else if (overriden == "DELETE") + else if (overridden == "DELETE") { method = HttpMethod_Delete; return true; diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/RestApi/RestApiGetCall.cpp --- a/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -69,4 +69,31 @@ name + "\", found: " + found->second); } } + + uint32_t RestApiGetCall::GetUnsignedInteger32Argument(const std::string& name, + uint32_t defaultValue) const + { + HttpToolbox::Arguments::const_iterator found = getArguments_.find(name); + + uint32_t value; + + if (found == getArguments_.end()) + { + return defaultValue; + } + else if (found->second.empty()) + { + return true; + } + else if (SerializationToolbox::ParseUnsignedInteger32(value, found->second)) + { + return value; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Expected a Unsigned Int for GET argument \"" + + name + "\", found: " + found->second); + } + } + } diff -r dfc271554574 -r ce002301ecea OrthancFramework/Sources/RestApi/RestApiGetCall.h --- a/OrthancFramework/Sources/RestApi/RestApiGetCall.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.h Tue Mar 18 13:35:00 2025 +0100 @@ -65,7 +65,10 @@ bool GetBooleanArgument(const std::string& name, bool defaultValue) const; - + + uint32_t GetUnsignedInteger32Argument(const std::string& name, + uint32_t defaultValue) const; + virtual bool ParseJsonRequest(Json::Value& result) const ORTHANC_OVERRIDE; }; } diff -r dfc271554574 -r ce002301ecea OrthancFramework/UnitTestsSources/RestApiTests.cpp --- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -99,9 +99,9 @@ #if (UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1) && (ORTHANC_ENABLE_SSL == 1) && (ORTHANC_SANDBOXED != 1) /** - The HTTPS CA certificates for BitBucket were extracted as follows: + The HTTPS CA certificates for Github were extracted as follows: - (1) We retrieve the URI of the root CA of BitBucket: + (1) We retrieve the URI of the root CA of Github: # echo | openssl s_client -servername raw.githubusercontent.com -connect raw.githubusercontent.com:443 2>/dev/null | openssl x509 -text | grep "CA Issuers" @@ -109,7 +109,7 @@ macro that can be used by libcurl: # cd UnitTestsSources - # python2 ../Resources/RetrieveCACertificates.py GITHUB_CERTIFICATES http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt > GithubCACertificates.h + # python2 ../Resources/RetrieveCACertificates.py GITHUB_CERTIFICATES http://crt.sectigo.com/SectigoRSADomainValidationSecureServerCA.crt > GithubCACertificates.h **/ #include "GithubCACertificates.h" diff -r dfc271554574 -r ce002301ecea OrthancServer/OrthancExplorer/explorer.js --- a/OrthancServer/OrthancExplorer/explorer.js Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/OrthancExplorer/explorer.js Tue Mar 18 13:35:00 2025 +0100 @@ -71,6 +71,11 @@ return s.match(/^[0-9a-zA-Z]+$/); } +function IsValidLabelName(s) +{ + return s.match(/^[0-9a-zA-Z\-_]+$/); +} + function DeepCopy(obj) { @@ -780,7 +785,7 @@ click: function () { var label = $.mobile.sdLastInput; if (label.length > 0) { - if (IsAlphanumeric(label)) { + if (IsValidLabelName(label)) { $.ajax({ url: '../' + resourceLevel + '/' + resourceId + '/labels/' + label, dataType: 'json', diff -r dfc271554574 -r ce002301ecea OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -4684,7 +4684,7 @@ if (entry == NULL) { - throw OrthancException(ErrorCode_UnknownDicomTag); + throw OrthancException(ErrorCode_UnknownDicomTag, p.name); } else { diff -r dfc271554574 -r ce002301ecea OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -4077,6 +4078,26 @@ } } + void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request) + { + output.clear(); + std::vector arguments; + for (uint32_t i = 0; i < request->getCount; ++i) + { + if (request->getValues[i] && strlen(request->getValues[i]) > 0) + { + arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i])); + } + else + { + arguments.push_back(std::string(request->getKeys[i])); + } + } + + output = boost::algorithm::join(arguments, "&"); + } + + #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4) static void SetPluginProperty(const std::string& pluginIdentifier, _OrthancPluginProperty property, @@ -4130,6 +4151,24 @@ httpStatus_(0) { } + + RestApiClient::RestApiClient(const char* url, + const OrthancPluginHttpRequest* request) : + method_(request->method), + path_(url), + afterPlugins_(false), + httpStatus_(0) + { + OrthancPlugins::GetHttpHeaders(requestHeaders_, request); + + std::string getArguments; + OrthancPlugins::SerializeGetArguments(getArguments, request); + + if (!getArguments.empty()) + { + path_ += "?" + getArguments; + } + } #endif @@ -4195,6 +4234,32 @@ } } } + + void RestApiClient::Forward(OrthancPluginContext* context, OrthancPluginRestOutput* output) + { + if (Execute() && httpStatus_ == 200) + { + const char* mimeType = NULL; + for (HttpHeaders::const_iterator h = answerHeaders_.begin(); h != answerHeaders_.end(); ++h) + { + if (h->first == "content-type") + { + mimeType = h->second.c_str(); + } + } + + AnswerString(answerBody_, mimeType, output); + } + else + { + AnswerHttpError(httpStatus_, output); + } + } + + bool RestApiClient::GetAnswerJson(Json::Value& output) const + { + return ReadJson(output, answerBody_); + } #endif diff -r dfc271554574 -r ce002301ecea OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h --- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h Tue Mar 18 13:35:00 2025 +0100 @@ -1399,6 +1399,9 @@ // helper method to convert Http headers from the plugin SDK to a std::map void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request); +// helper method to re-serialize the get arguments from the SDK into a string +void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request); + #if HAS_ORTHANC_PLUGIN_WEBDAV == 1 class IWebDavCollection : public boost::noncopyable { @@ -1528,6 +1531,10 @@ public: RestApiClient(); + + // used to forward a call from the plugin to the core + RestApiClient(const char* url, + const OrthancPluginHttpRequest* request); void SetMethod(OrthancPluginHttpMethod method) { @@ -1584,12 +1591,17 @@ bool Execute(); + // Execute and forward the response as is + void Forward(OrthancPluginContext* context, OrthancPluginRestOutput* output); + uint16_t GetHttpStatus() const; bool LookupAnswerHeader(std::string& value, const std::string& key) const; const std::string& GetAnswerBody() const; + + bool GetAnswerJson(Json::Value& output) const; }; #endif } diff -r dfc271554574 -r ce002301ecea OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp --- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -549,39 +549,46 @@ const Json::Value& change = changes["Changes"][i]; int64_t seq = change["Seq"].asInt64(); - if (!limitToChange_.empty()) // if updating only maindicomtags for a single level + try { - if (change["ChangeType"] == limitToChange_) + if (!limitToChange_.empty()) // if updating only maindicomtags for a single level + { + if (change["ChangeType"] == limitToChange_) + { + Json::Value result; + Json::Value request; + request["ReconstructFiles"] = false; + request["LimitToThisLevelMainDicomTags"] = true; + OrthancPlugins::RestApiPost(result, "/" + limitToUrl_ + "/" + change["ID"].asString() + "/reconstruct", request, false); + } + } + else { - Json::Value result; - Json::Value request; - request["ReconstructFiles"] = false; - request["LimitToThisLevelMainDicomTags"] = true; - OrthancPlugins::RestApiPost(result, "/" + limitToUrl_ + "/" + change["ID"].asString() + "/reconstruct", request, false); + if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed + { + Json::Value result; + + if (needsReconstruct || needsReingest ||force_) + { + Json::Value request; + if (needsReingest) + { + request["ReconstructFiles"] = true; + } + OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false); + } + + if (needsDicomWebCaching) + { + Json::Value request; + OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/update-dicomweb-cache", request, true); + } + } } } - else + catch (...) { - if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed - { - Json::Value result; - - if (needsReconstruct || needsReingest ||force_) - { - Json::Value request; - if (needsReingest) - { - request["ReconstructFiles"] = true; - } - OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false); - } - - if (needsDicomWebCaching) - { - Json::Value request; - OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/update-dicomweb-cache", request, true); - } - } + ORTHANC_PLUGINS_LOG_ERROR("Housekeeper: unhandled error while processing change " + boost::lexical_cast(seq) + ", skipping resource."); } { @@ -703,9 +710,17 @@ { if (runningPeriods_.isInPeriod()) { - completed = ProcessChanges(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration); - SaveStatusInDb(); - + try + { + completed = ProcessChanges(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration); + SaveStatusInDb(); + } + catch (...) + { + ORTHANC_PLUGINS_LOG_ERROR("Housekeeper: unhandled error while processing change " + boost::lexical_cast(pluginStatus_.lastProcessedChange) + + " / " + boost::lexical_cast(pluginStatus_.lastChangeToProcess)); + } + if (!completed) { boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); diff -r dfc271554574 -r ce002301ecea OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Resources/Configuration.json Tue Mar 18 13:35:00 2025 +0100 @@ -214,7 +214,7 @@ // "?" accepts any single character at that position. // "*" accepts any string value at that position. // If you want to add a a SOP class that is not defined in - // DCMTK defaults, you must add it explicitely. + // DCMTK defaults, you must add it explicitly. // (new in Orthanc 1.12.6) // Example to add a non standard class // and keep the default ones: @@ -531,9 +531,9 @@ // accept C-FIND requests from Orthanc (new in Orthanc 1.8.1). "DicomEchoChecksFind" : false, - // Wheter Orthanc uses C-MOVE or C-GET to retrieve a resource after + // Whether Orthanc uses C-MOVE or C-GET to retrieve a resource after // a C-Find (when calling /queries/.../retrieve). - // This configuration can be overriden for each modality by providing + // This configuration can be overridden for each modality by providing // "RetrieveMethod" in the "DicomModalities" entry. // (new in Orthanc 1.12.6) "DicomDefaultRetrieveMethod" : "C-MOVE", @@ -920,8 +920,10 @@ // that have a compressed transfer syntax (new in Orthanc 1.8.2). "IngestTranscodingOfCompressed" : true, - // The compression level that is used when transcoding to one of the - // lossy/JPEG transfer syntaxes (integer between 1 and 100). + // The default compression level that is used when transcoding to one + // of the lossy/JPEG transfer syntaxes (integer between 1 and 100). + // This value is currently only used by the default built-in DCMTK + // transcoder and is not provided to transcoding plugins. "DicomLossyTranscodingQuality" : 90, // Whether "fsync()" is called after each write to the storage area @@ -945,7 +947,8 @@ // Deidentify/anonymize the contents of the logs (notably C-FIND, // C-GET, and C-MOVE queries submitted to Orthanc) according to - // Table E.1-1 of the DICOM standard (new in Orthanc 1.8.2) + // Table E.1-1 of the DICOM standard (new in Orthanc 1.8.2). + // Note that, the DICOM logs at TRACE level are not deidentified ! "DeidentifyLogs" : true, // If "DeidentifyLogs" is true, this sets the DICOM standard to diff -r dfc271554574 -r ce002301ecea OrthancServer/Resources/RunCppCheck-2.17.0.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Resources/RunCppCheck-2.17.0.sh Tue Mar 18 13:35:00 2025 +0100 @@ -0,0 +1,110 @@ +#!/bin/bash + +set -ex + +CPPCHECK=cppcheck + +if [ $# -ge 1 ]; then + CPPCHECK=$1 +fi + +cat < /tmp/cppcheck-suppressions.txt +nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:321 +stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1525 +stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166 +stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74 +stlFindInsert:../../OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp:65 +stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:328 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp:41 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:191 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:361 +syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53 +syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74 +syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133 +syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:322 +uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:417 +unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1173 +useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91 +useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99 +useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275 +assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:277 +assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1026 +assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:292 +assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:391 +assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3058 +assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286 +assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454 +EOF + +${CPPCHECK} --enable=all --std=gnu++11 --library=boost \ + --suppressions-list=/tmp/cppcheck-suppressions.txt \ + -I/usr/include/ \ + -I/usr/include/jsoncpp/ \ + -I/usr/include/linux/ \ + -I/usr/include/c++/11/ \ + -I/usr/include/c++/11/tr1/ \ + -I/usr/include/x86_64-linux-gnu/c++/11/ \ + -DBOOST_HAS_DATE_TIME=1 \ + -DBOOST_HAS_FILESYSTEM_V3=1 \ + -DBOOST_HAS_REGEX=1 \ + -DCIVETWEB_HAS_DISABLE_KEEP_ALIVE=1 \ + -DCIVETWEB_HAS_WEBDAV_WRITING=1 \ + -DDCMTK_VERSION_NUMBER=369 \ + -DJCONFIG_INCLUDED \ + -DHAVE_MALLOPT=1 \ + -DMONGOOSE_USE_CALLBACKS=1 \ + -DJSONCPP_VERSION_MAJOR=1 \ + -DJSONCPP_VERSION_MINOR=0 \ + -DORTHANC_BUILDING_FRAMEWORK_LIBRARY=0 \ + -DORTHANC_BUILD_UNIT_TESTS=1 \ + -DORTHANC_ENABLE_BASE64=1 \ + -DORTHANC_ENABLE_CIVETWEB=1 \ + -DORTHANC_ENABLE_CURL=1 \ + -DORTHANC_ENABLE_DCMTK=1 \ + -DORTHANC_ENABLE_DCMTK_JPEG=1 \ + -DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1 \ + -DORTHANC_ENABLE_DCMTK_NETWORKING=1 \ + -DORTHANC_ENABLE_DCMTK_TRANSCODING=1 \ + -DORTHANC_ENABLE_JPEG=1 \ + -DORTHANC_ENABLE_LOCALE=1 \ + -DORTHANC_ENABLE_LOGGING=1 \ + -DORTHANC_ENABLE_LOGGING_STDIO=1 \ + -DORTHANC_ENABLE_LUA=1 \ + -DORTHANC_ENABLE_MD5=1 \ + -DORTHANC_ENABLE_MONGOOSE=0 \ + -DORTHANC_ENABLE_PKCS11=1 \ + -DORTHANC_ENABLE_PLUGINS=1 \ + -DORTHANC_ENABLE_PNG=1 \ + -DORTHANC_ENABLE_PUGIXML=1 \ + -DORTHANC_ENABLE_SQLITE=1 \ + -DORTHANC_ENABLE_SSL=1 \ + -DORTHANC_ENABLE_ZLIB=1 \ + -DORTHANC_SANDBOXED=0 \ + -DORTHANC_SQLITE_VERSION=3027001 \ + -DORTHANC_UNIT_TESTS_LINK_FRAMEWORK=0 \ + -DPUGIXML_VERSION=150 \ + -DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1 \ + -D__BYTE_ORDER=__LITTLE_ENDIAN \ + -D__GNUC__=11 \ + -D__GNUC_MINOR__=4 \ + -D__GNUC_PATCHLEVEL_=0 \ + -D__STDC__ \ + -D__cplusplus=201103 \ + -D__linux__ \ + -UNDEBUG \ + -DHAS_ORTHANC_EXCEPTION=1 \ + \ + ../../OrthancFramework/Sources \ + ../../OrthancFramework/UnitTestsSources \ + ../../OrthancServer/Plugins/Engine \ + ../../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 diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancConfiguration.cpp --- a/OrthancServer/Sources/OrthancConfiguration.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancConfiguration.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -46,6 +46,7 @@ static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier"; static const char* const WARNINGS = "Warnings"; static const char* const JOBS_ENGINE_THREADS_COUNT = "JobsEngineThreadsCount"; +static const char* const DICOM_LOSSY_TRANSCODING_QUALITY = "DicomLossyTranscodingQuality"; namespace Orthanc { @@ -700,6 +701,11 @@ } } + unsigned int OrthancConfiguration::GetDicomLossyTranscodingQuality() const + { + return GetUnsignedIntegerParameter(DICOM_LOSSY_TRANSCODING_QUALITY, 90); + } + bool OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const { diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancConfiguration.h --- a/OrthancServer/Sources/OrthancConfiguration.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancConfiguration.h Tue Mar 18 13:35:00 2025 +0100 @@ -197,6 +197,8 @@ void GetListOfOrthancPeers(std::set& target) const; + unsigned int GetDicomLossyTranscodingQuality() const; + // Returns "true" iff. at least one user is registered bool SetupRegisteredUsers(HttpServer& httpServer) const; diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancInitialization.cpp --- a/OrthancServer/Sources/OrthancInitialization.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancInitialization.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -59,6 +59,7 @@ # undef __FILE__ # define __FILE__ __ORTHANC_FILE__ # endif +# include # include #endif diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -58,6 +58,8 @@ static const char* const SERIES = "Series"; static const char* const TAGS = "Tags"; static const char* const TRANSCODE = "Transcode"; +static const char* const LOSSY_QUALITY = "LossyQuality"; + namespace Orthanc @@ -88,6 +90,10 @@ .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String, "Transcode the DICOM instances to the provided DICOM transfer syntax: " "https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, "Allow the modification of tags related to DICOM identifiers, at the risk of " "breaking the DICOM model of the real world", false) @@ -116,6 +122,10 @@ .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String, "Transcode the DICOM instances to the provided DICOM transfer syntax: " "https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, "Allow the modification of tags related to DICOM identifiers, at the risk of " "breaking the DICOM model of the real world", false) @@ -196,7 +206,8 @@ static void AnonymizeOrModifyInstance(DicomModification& modification, RestApiPostCall& call, bool transcode, - DicomTransferSyntax targetSyntax) + DicomTransferSyntax targetSyntax, + unsigned int lossyQuality) { ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); @@ -220,7 +231,7 @@ std::set s; s.insert(targetSyntax); - if (context.Transcode(transcoded, source, s, true)) + if (context.Transcode(transcoded, source, s, true, lossyQuality)) { call.GetOutput().AnswerBuffer(transcoded.GetBufferData(), transcoded.GetBufferSize(), MimeType_Dicom); @@ -259,6 +270,20 @@ } } + static unsigned int GetLossyQuality(const Json::Value& request) + { + unsigned int lossyQuality; + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + if (request.isMember(LOSSY_QUALITY)) + { + lossyQuality = SerializationToolbox::ReadUnsignedInteger(request, LOSSY_QUALITY); + } + return lossyQuality; +} static void ModifyInstance(RestApiPostCall& call) { @@ -286,11 +311,11 @@ if (request.isMember(TRANSCODE)) { std::string s = SerializationToolbox::ReadString(request, TRANSCODE); - + DicomTransferSyntax syntax; if (LookupTransferSyntax(syntax, s)) { - AnonymizeOrModifyInstance(modification, call, true, syntax); + AnonymizeOrModifyInstance(modification, call, true, syntax, GetLossyQuality(request)); } else { @@ -300,7 +325,7 @@ else { AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, - DicomTransferSyntax_LittleEndianImplicit /* unused */); + DicomTransferSyntax_LittleEndianImplicit /* unused */, 0 /* unused */); } } @@ -326,8 +351,25 @@ Json::Value request; ParseAnonymizationRequest(request, modification, call); - AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, - DicomTransferSyntax_LittleEndianImplicit /* unused */); + if (request.isMember(TRANSCODE)) + { + std::string s = SerializationToolbox::ReadString(request, TRANSCODE); + + DicomTransferSyntax syntax; + if (LookupTransferSyntax(syntax, s)) + { + AnonymizeOrModifyInstance(modification, call, true, syntax, GetLossyQuality(request)); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown transfer syntax: " + s); + } + } + else + { + AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, + DicomTransferSyntax_LittleEndianImplicit /* unused */, 0 /* unused */); + } } diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -42,6 +42,13 @@ static const char* const KEY_RESOURCES = "Resources"; static const char* const KEY_EXTENDED = "Extended"; static const char* const KEY_TRANSCODE = "Transcode"; + static const char* const KEY_LOSSY_QUALITY = "LossyQuality"; + static const char* const KEY_FILENAME = "Filename"; + + static const char* const GET_TRANSCODE = "transcode"; + static const char* const GET_LOSSY_QUALITY = "lossy-quality"; + static const char* const GET_FILENAME = "filename"; + static const char* const GET_RESOURCES = "resources"; static const char* const CONFIG_LOADER_THREADS = "ZipLoaderThreads"; @@ -113,10 +120,13 @@ bool& extended, /* out */ bool& transcode, /* out */ DicomTransferSyntax& syntax, /* out */ + unsigned int& lossyQuality, /* out */ int& priority, /* out */ unsigned int& loaderThreads, /* out */ + std::string& filename, /* out */ const Json::Value& body, /* in */ - const bool defaultExtended /* in */) + const bool defaultExtended /* in */, + const std::string& defaultFilename /* in */) { synchronous = OrthancRestApi::IsSynchronousJobRequest (true /* synchronous by default */, body); @@ -138,12 +148,32 @@ { transcode = true; syntax = Orthanc::GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE)); + + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + if (body.isMember(KEY_LOSSY_QUALITY)) + { + lossyQuality = SerializationToolbox::ReadUnsignedInteger(body, KEY_LOSSY_QUALITY); + } } else { transcode = false; } + if (body.type() == Json::objectValue && + body.isMember(KEY_FILENAME) && body[KEY_FILENAME].isString()) + { + filename = body[KEY_FILENAME].asString(); + } + else + { + filename = defaultFilename; + } + { OrthancConfiguration::ReaderLock lock; loaderThreads = lock.GetConfiguration().GetUnsignedIntegerParameter(CONFIG_LOADER_THREADS, 0); // New in Orthanc 1.10.0 @@ -487,6 +517,7 @@ } else { + job->SetFilename(filename); OrthancRestApi::SubmitGenericJob(output, context, job.release(), false, priority); } } @@ -508,6 +539,13 @@ .SetRequestField(KEY_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files in the archive will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(KEY_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) + .SetRequestField(KEY_FILENAME, RestApiCallDocumentation::Type_String, + "Filename to set in the \"Content-Disposition\" HTTP header " + "(including file extension)", false) .SetRequestField("Priority", RestApiCallDocumentation::Type_Number, "In asynchronous mode, the priority of the job. The higher the value, the higher the priority.", false) .AddAnswerType(MimeType_Zip, "In synchronous mode, the ZIP file containing the archive") @@ -539,8 +577,11 @@ .SetSummary("Create " + m) .SetDescription("Create a " + m + " containing the DICOM resources (patients, studies, series, or instances) " "whose Orthanc identifiers are provided in the body") - .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, - "The list of Orthanc identifiers of interest.", false); + .SetRequestField(KEY_RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings, + "The list of Orthanc identifiers of interest.", false) + .SetRequestField(KEY_FILENAME, RestApiCallDocumentation::Type_String, + "Filename to set in the \"Content-Disposition\" HTTP header " + "(including file extension)", false); return; } @@ -553,8 +594,11 @@ DicomTransferSyntax transferSyntax; int priority; unsigned int loaderThreads; - GetJobParameters(synchronous, extended, transcode, transferSyntax, - priority, loaderThreads, body, DEFAULT_IS_EXTENDED); + std::string filename; + unsigned int lossyQuality; + + GetJobParameters(synchronous, extended, transcode, transferSyntax, lossyQuality, + priority, loaderThreads, filename, body, DEFAULT_IS_EXTENDED, "Archive.zip"); std::unique_ptr job(new ArchiveJob(context, IS_MEDIA, extended, ResourceType_Patient)); AddResourcesOfInterest(*job, body); @@ -562,11 +606,12 @@ if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } job->SetLoaderThreads(loaderThreads); - SubmitJob(call.GetOutput(), context, job, priority, synchronous, "Archive.zip"); + SubmitJob(call.GetOutput(), context, job, priority, synchronous, filename); } else { @@ -574,15 +619,23 @@ "Expected a list of resources to archive in the body"); } } - + + static unsigned int GetLossyQuality(const RestApiGetCall& call) + { + unsigned int lossyQuality; + + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + lossyQuality = call.GetUnsignedInteger32Argument(GET_LOSSY_QUALITY, lossyQuality); + + return lossyQuality; + } + template static void CreateBatchGet(RestApiGetCall& call) { - static const char* const TRANSCODE = "transcode"; - static const char* const RESOURCES = "resources"; - if (call.IsDocumentation()) { std::string m = (IS_MEDIA ? "DICOMDIR media" : "ZIP archive"); @@ -591,10 +644,17 @@ .SetSummary("Create " + m) .SetDescription("Create a " + m + " containing the DICOM resources (patients, studies, series, or instances) " "whose Orthanc identifiers are provided in the 'resources' argument") - .SetHttpGetArgument(TRANSCODE, RestApiCallDocumentation::Type_String, + .SetHttpGetArgument(GET_FILENAME, RestApiCallDocumentation::Type_String, + "Filename to set in the \"Content-Disposition\" HTTP header " + "(including file extension)", false) + .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) - .SetHttpGetArgument(RESOURCES, RestApiCallDocumentation::Type_String, + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) + .SetHttpGetArgument(GET_RESOURCES, RestApiCallDocumentation::Type_String, "A comma separated list of Orthanc resource identifiers to include in the " + m + ".", true); return; } @@ -602,27 +662,32 @@ ServerContext& context = OrthancRestApi::GetContext(call); bool transcode = false; DicomTransferSyntax transferSyntax = DicomTransferSyntax_LittleEndianImplicit; // Initialize variable to avoid warnings + unsigned int lossyQuality; - if (call.HasArgument(TRANSCODE)) + if (call.HasArgument(GET_TRANSCODE)) { transcode = true; - transferSyntax = GetTransferSyntax(call.GetArgument(TRANSCODE, "")); + transferSyntax = GetTransferSyntax(call.GetArgument(GET_TRANSCODE, "")); + lossyQuality = GetLossyQuality(call); } - if (!call.HasArgument(RESOURCES)) + if (!call.HasArgument(GET_RESOURCES)) { - throw OrthancException(Orthanc::ErrorCode_BadRequest, std::string("Missing ") + RESOURCES + " argument"); + throw OrthancException(Orthanc::ErrorCode_BadRequest, std::string("Missing ") + GET_RESOURCES + " argument"); } std::unique_ptr job(new ArchiveJob(context, IS_MEDIA, DEFAULT_IS_EXTENDED, ResourceType_Patient)); - AddResourcesOfInterestFromString(*job, call.GetArgument(RESOURCES, "")); + AddResourcesOfInterestFromString(*job, call.GetArgument(GET_RESOURCES, "")); if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } - SubmitJob(call.GetOutput(), context, job, 0, true, "Archive.zip"); + const std::string filename = call.GetArgument(GET_FILENAME, "Archive.zip"); // New in Orthanc 1.12.7 + + SubmitJob(call.GetOutput(), context, job, 0, true, filename); } @@ -630,8 +695,6 @@ bool IS_MEDIA> static void CreateSingleGet(RestApiGetCall& call) { - static const char* const TRANSCODE = "transcode"; - static const char* const FILENAME = "filename"; if (call.IsDocumentation()) { @@ -646,12 +709,16 @@ "which might *not* be desirable to archive large amount of data, as it might " "lead to network timeouts. Prefer the asynchronous version using `POST` method.") .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") - .SetHttpGetArgument(FILENAME, RestApiCallDocumentation::Type_String, + .SetHttpGetArgument(GET_FILENAME, RestApiCallDocumentation::Type_String, "Filename to set in the \"Content-Disposition\" HTTP header " "(including file extension)", false) - .SetHttpGetArgument(TRANSCODE, RestApiCallDocumentation::Type_String, + .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files in the archive will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .AddAnswerType(MimeType_Zip, "ZIP file containing the archive"); if (IS_MEDIA) { @@ -665,7 +732,7 @@ ServerContext& context = OrthancRestApi::GetContext(call); const std::string id = call.GetUriComponent("id", ""); - const std::string filename = call.GetArgument(FILENAME, id + ".zip"); // New in Orthanc 1.11.0 + const std::string filename = call.GetArgument(GET_FILENAME, id + ".zip"); // New in Orthanc 1.11.0 bool extended; if (IS_MEDIA) @@ -680,9 +747,10 @@ std::unique_ptr job(new ArchiveJob(context, IS_MEDIA, extended, (LEVEL == ResourceType_Patient ? ResourceType_Patient : ResourceType_Study))); // use patient info from study except when exporting a patient job->AddResource(id, true, LEVEL); - if (call.HasArgument(TRANSCODE)) + if (call.HasArgument(GET_TRANSCODE)) { - job->SetTranscode(GetTransferSyntax(call.GetArgument(TRANSCODE, ""))); + job->SetTranscode(GetTransferSyntax(call.GetArgument(GET_TRANSCODE, ""))); + job->SetLossyQuality(GetLossyQuality(call)); } { @@ -726,8 +794,10 @@ DicomTransferSyntax transferSyntax; int priority; unsigned int loaderThreads; - GetJobParameters(synchronous, extended, transcode, transferSyntax, - priority, loaderThreads, body, false /* by default, not extented */); + std::string filename; + unsigned int lossyQuality; + GetJobParameters(synchronous, extended, transcode, transferSyntax, lossyQuality, + priority, loaderThreads, filename, body, false /* by default, not extented */, id + ".zip"); std::unique_ptr job(new ArchiveJob(context, IS_MEDIA, extended, LEVEL)); job->AddResource(id, true, LEVEL); @@ -735,11 +805,12 @@ if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } job->SetLoaderThreads(loaderThreads); - SubmitJob(call.GetOutput(), context, job, priority, synchronous, id + ".zip"); + SubmitJob(call.GetOutput(), context, job, priority, synchronous, filename); } else { diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -1697,7 +1697,7 @@ "Usage of wildcards is prohibited and the query shall only contain DICOM ID tags. " "Additionally, you may provide SOPClassesInStudy to limit the scope of the DICOM " "negotiation to certain SOPClassUID or to present uncommon SOPClassUID during " - "the DICOM negotation. By default, " + "the DICOM negotiation. By default, " "Orhanc will propose the most 120 common SOPClassUIDs.", true) .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, "A query object identifying all the DICOM resources to be retrieved", true) diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -156,7 +156,6 @@ ResourceFinder finder(level, responseContent, context.GetFindStorageAccessMode(), context.GetIndex().HasFindSupport()); finder.SetOrthancId(level, identifier); - finder.SetRetrieveMetadata(retrieveMetadata); return finder.ExecuteOneResource(target, context, format, retrieveMetadata); } @@ -338,7 +337,9 @@ static void GetInstanceFile(RestApiGetCall& call) { - static const char* const TRANSCODE = "transcode"; + static const char* const GET_TRANSCODE = "transcode"; + static const char* const GET_LOSSY_QUALITY = "lossy-quality"; + static const char* const GET_FILENAME = "filename"; if (call.IsDocumentation()) { @@ -348,9 +349,16 @@ .SetDescription("Download one DICOM instance") .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") .SetHttpHeader("Accept", "This HTTP header can be set to retrieve the DICOM instance in DICOMweb format") - .SetHttpGetArgument(TRANSCODE, RestApiCallDocumentation::Type_String, + .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM file will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) + .SetHttpGetArgument(GET_FILENAME, RestApiCallDocumentation::Type_String, + "Filename to set in the \"Content-Disposition\" HTTP header " + "(including file extension)", false) .AddAnswerType(MimeType_Dicom, "The DICOM instance") .AddAnswerType(MimeType_DicomWebJson, "The DICOM instance, in DICOMweb JSON format") .AddAnswerType(MimeType_DicomWebXml, "The DICOM instance, in DICOMweb XML format"); @@ -399,15 +407,40 @@ } } - if (call.HasArgument(TRANSCODE)) + const std::string filename = call.GetArgument(GET_FILENAME, publicId + ".dcm"); // New in Orthanc 1.12.7 + + if (call.HasArgument(GET_TRANSCODE)) { + unsigned int lossyQuality; + unsigned int defaultLossyQuality; + { + OrthancConfiguration::ReaderLock lock; + defaultLossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + lossyQuality = call.GetUnsignedInteger32Argument(GET_LOSSY_QUALITY, defaultLossyQuality); + std::string source; std::string attachmentId; std::string transcoded; context.ReadDicom(source, attachmentId, publicId); - if (context.TranscodeWithCache(transcoded, source, publicId, attachmentId, GetTransferSyntax(call.GetArgument(TRANSCODE, "")))) + if (lossyQuality != defaultLossyQuality) // we can't use the cache if the lossy quality is not the default one { + IDicomTranscoder::DicomImage targetImage; + IDicomTranscoder::DicomImage sourceImage; + sourceImage.SetExternalBuffer(source); + std::set allowedSyntaxes; + allowedSyntaxes.insert(GetTransferSyntax(call.GetArgument(GET_TRANSCODE, ""))); + + if (context.Transcode(targetImage, sourceImage, allowedSyntaxes, true, lossyQuality)) + { + call.GetOutput().SetContentFilename(filename.c_str()); + call.GetOutput().AnswerBuffer(targetImage.GetBufferData(), targetImage.GetBufferSize(), MimeType_Dicom); + } + } + else if (context.TranscodeWithCache(transcoded, source, publicId, attachmentId, GetTransferSyntax(call.GetArgument(GET_TRANSCODE, "")))) + { + call.GetOutput().SetContentFilename(filename.c_str()); call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom); } } @@ -422,7 +455,7 @@ } else { - context.AnswerAttachment(call.GetOutput(), info); + context.AnswerAttachment(call.GetOutput(), info, filename); } } } @@ -850,7 +883,7 @@ .SetTag("Instances") .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") .SetHttpGetArgument("quality", RestApiCallDocumentation::Type_Number, "Quality for JPEG images (between 1 and 100, defaults to 90)", false) - .SetHttpGetArgument("returnUnsupportedImage", RestApiCallDocumentation::Type_Boolean, "Returns an unsupported.png placeholder image if unable to provide the image instead of returning a 415 HTTP error (defaults to false)", false) + .SetHttpGetArgument("returnUnsupportedImage", RestApiCallDocumentation::Type_Boolean, "Returns an unsupported.png placeholder image if unable to provide the image instead of returning a 415 HTTP error (value is true if option is present)", false) .SetHttpHeader("Accept", "Format of the resulting image. Can be `image/png` (default), `image/jpeg` or `image/x-portable-arbitrarymap`") .AddAnswerType(MimeType_Png, "PNG image") .AddAnswerType(MimeType_Jpeg, "JPEG image") @@ -913,7 +946,8 @@ } else { - if (call.HasArgument("returnUnsupportedImage")) + // if present and not explicitly set to false + if (call.HasArgument("returnUnsupportedImage") && call.GetBooleanArgument("returnUnsupportedImage", true)) { std::string root = ""; for (size_t i = 1; i < call.GetFullUri().size(); i++) @@ -2297,6 +2331,8 @@ { const ResourceType level = GetResourceTypeFromUri(call); + static const char* const GET_FILENAME = "filename"; + if (call.IsDocumentation()) { std::string r = GetResourceTypeText(level, false /* plural */, false /* upper case */); @@ -2307,11 +2343,15 @@ std::string(uncompress ? "" : ". The attachment will not be decompressed if `StorageCompression` is `true`.")) .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") .SetUriArgument("name", "The name of the attachment, or its index (cf. `UserContentType` configuration option)") + .SetHttpGetArgument(GET_FILENAME, RestApiCallDocumentation::Type_String, + "Filename to set in the \"Content-Disposition\" HTTP header " + "(including file extension)", false) .AddAnswerType(MimeType_Binary, "The attachment") .SetAnswerHeader("ETag", "Revision of the attachment, to be used in further `PUT` or `DELETE` operations") .SetHttpHeader("If-None-Match", "Optional revision of the attachment, to check if its content has changed") .SetHttpHeader("Content-Range", "Optional content range to access part of the attachment (new in Orthanc 1.12.5)"); - return; + + return; } ServerContext& context = OrthancRestApi::GetContext(call); @@ -2342,6 +2382,8 @@ return; } + const std::string filename = call.GetArgument(GET_FILENAME, info.GetUuid()); // New in Orthanc 1.12.7 + if (hasRangeHeader) { std::string fragment; @@ -2355,7 +2397,7 @@ else if (uncompress || info.GetCompressionType() == CompressionType_None) { - context.AnswerAttachment(call.GetOutput(), info); + context.AnswerAttachment(call.GetOutput(), info, filename); } else { @@ -3614,6 +3656,14 @@ Json::Value answer; finder.Execute(answer, OrthancRestApi::GetContext(call), format, false /* no "Metadata" field */); + + // Given the data model, if there are no children, it means there is no parent. + // https://discourse.orthanc-server.org/t/patients-id-instances-quirk/5498 + if (answer.size() == 0) + { + throw OrthancException(ErrorCode_UnknownResource); + } + call.GetOutput().AnswerJson(answer); } diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/ResourceFinder.cpp --- a/OrthancServer/Sources/ResourceFinder.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/ResourceFinder.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -849,7 +849,8 @@ if (request_.GetLevel() != ResourceType_Instance) { - request_.SetRetrieveOneInstanceMetadataAndAttachments(true); + // only retrieve the instance attachments and metadata if we have allowed access to the storage + request_.SetRetrieveOneInstanceMetadataAndAttachments(IsStorageAccessAllowed()); } } } @@ -1147,6 +1148,15 @@ Toolbox::RemoveSets(remainingRequestedTags, metaTagsToRemove); } + // if SOPClassUID has been requested, we might still find it at metadata level (useful e.g. for StoneViewer) + std::string sopClassUidFromMetadata; + if (resource.GetLevel() == ResourceType_Instance && + remainingRequestedTags.find(DICOM_TAG_SOP_CLASS_UID) != remainingRequestedTags.end() && + resource.LookupMetadata(sopClassUidFromMetadata, ResourceType_Instance, MetadataType_Instance_SopClassUid)) + { + outRequestedTags.SetValue(DICOM_TAG_SOP_CLASS_UID, sopClassUidFromMetadata, false); + remainingRequestedTags.erase(DICOM_TAG_SOP_CLASS_UID); + } if (!remainingRequestedTags.empty() && !DicomMap::HasOnlyComputedTags(remainingRequestedTags)) // if the only remaining tags are computed tags, it is worthless to read them from disk diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/Search/ISqlLookupFormatter.cpp --- a/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/Search/ISqlLookupFormatter.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -373,7 +373,7 @@ std::string arg = "order" + boost::lexical_cast(index); - target = " INNER JOIN Metadata " + arg + " ON " + arg + ".id = " + FormatLevel(requestLevel) + + target = " LEFT JOIN Metadata " + arg + " ON " + arg + ".id = " + FormatLevel(requestLevel) + ".internalId AND " + arg + ".type = " + boost::lexical_cast(metadata); } diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -393,8 +393,6 @@ { try { - unsigned int lossyQuality; - { OrthancConfiguration::ReaderLock lock; @@ -425,7 +423,6 @@ // New options in Orthanc 1.7.0 transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true); builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After")); - lossyQuality = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomLossyTranscodingQuality", 90); std::string s; if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding")) @@ -502,6 +499,8 @@ SetAcceptedSopClasses(acceptedSopClasses, rejectedSopClasses); defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetStringParameter("DicomDefaultRetrieveMethod", "C-MOVE")); + + dynamic_cast(*dcmtkTranscoder_).SetDefaultLossyQuality(lock.GetConfiguration().GetDicomLossyTranscodingQuality()); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -516,8 +515,6 @@ #else LOG(INFO) << "Your platform does not support malloc_trim(), not starting the memory trimming thread"; #endif - - dynamic_cast(*dcmtkTranscoder_).SetLossyQuality(lossyQuality); } catch (OrthancException&) { @@ -985,10 +982,11 @@ void ServerContext::AnswerAttachment(RestApiOutput& output, - const FileInfo& attachment) + const FileInfo& attachment, + const std::string& filename) { StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); - accessor.AnswerFile(output, attachment, GetFileContentMime(attachment.GetContentType())); + accessor.AnswerFile(output, attachment, GetFileContentMime(attachment.GetContentType()), filename); } @@ -1958,15 +1956,31 @@ return true; } - bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) { + unsigned int lossyQuality; + + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); + } + + + bool ServerContext::Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) + { if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { - if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) + if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) { return true; } @@ -1976,7 +1990,7 @@ if (HasPlugins() && GetPlugins().HasCustomTranscoder()) { - if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) + if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) // TODO: pass lossyQuality to plugins -> needs a new plugin interface { return true; } @@ -1990,7 +2004,7 @@ if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { - return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid); + return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); } else { diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Tue Mar 18 13:35:00 2025 +0100 @@ -361,7 +361,8 @@ bool isReconstruct = false); void AnswerAttachment(RestApiOutput& output, - const FileInfo& fileInfo); + const FileInfo& fileInfo, + const std::string& filename); void ChangeAttachmentCompression(ResourceType level, const std::string& resourceId, @@ -576,6 +577,12 @@ DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) ORTHANC_OVERRIDE; virtual bool TranscodeWithCache(std::string& target, const std::string& source, diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/ServerJobs/ArchiveJob.cpp --- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Mar 18 13:35:00 2025 +0100 @@ -88,11 +88,13 @@ ServerContext& context_; bool transcode_; DicomTransferSyntax transferSyntax_; + unsigned int lossyQuality_; public: - explicit InstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax) + explicit InstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) : context_(context), transcode_(transcode), - transferSyntax_(transferSyntax) + transferSyntax_(transferSyntax), + lossyQuality_(lossyQuality) { } @@ -114,7 +116,7 @@ IDicomTranscoder::DicomImage source, transcoded; source.SetExternalBuffer(sourceBuffer); - if (context_.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */)) + if (context_.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */, lossyQuality_)) { transcodedBuffer.assign(reinterpret_cast(transcoded.GetBufferData()), transcoded.GetBufferSize()); return true; @@ -139,8 +141,8 @@ class ArchiveJob::SynchronousInstanceLoader : public ArchiveJob::InstanceLoader { public: - explicit SynchronousInstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax) - : InstanceLoader(context, transcode, transferSyntax) + explicit SynchronousInstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) + : InstanceLoader(context, transcode, transferSyntax, lossyQuality) { } @@ -192,8 +194,8 @@ public: - ThreadedInstanceLoader(ServerContext& context, size_t threadCount, bool transcode, DicomTransferSyntax transferSyntax) - : InstanceLoader(context, transcode, transferSyntax), + ThreadedInstanceLoader(ServerContext& context, size_t threadCount, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) + : InstanceLoader(context, transcode, transferSyntax, lossyQuality), availableInstancesSemaphore_(0), bufferedInstancesSemaphore_(3*threadCount) { @@ -1243,12 +1245,14 @@ archive_(new ArchiveIndex(GetArchiveResourceType(jobLevel))), // get patient Info from this level isMedia_(isMedia), enableExtendedSopClass_(enableExtendedSopClass), + filename_("archive.zip"), currentStep_(0), instancesCount_(0), uncompressedSize_(0), archiveSize_(0), transcode_(false), transferSyntax_(DicomTransferSyntax_LittleEndianImplicit), + lossyQuality_(100), loaderThreads_(0) { } @@ -1296,7 +1300,18 @@ } } - + void ArchiveJob::SetFilename(const std::string& filename) + { + if (writer_.get() != NULL) // Already started + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + filename_ = filename; + } + } + void ArchiveJob::AddResource(const std::string& publicId, bool mustExist, ResourceType expectedType) @@ -1337,7 +1352,20 @@ } } - + + void ArchiveJob::SetLossyQuality(unsigned int lossyQuality) + { + if (writer_.get() != NULL) // Already started + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + lossyQuality_ = lossyQuality; + } + } + + void ArchiveJob::SetLoaderThreads(unsigned int loaderThreads) { if (writer_.get() != NULL) // Already started @@ -1363,11 +1391,11 @@ if (loaderThreads_ == 0) { // default behaviour before loaderThreads was introducted in 1.10.0 - instanceLoader_.reset(new SynchronousInstanceLoader(context_, transcode_, transferSyntax_)); + instanceLoader_.reset(new SynchronousInstanceLoader(context_, transcode_, transferSyntax_, lossyQuality_)); } else { - instanceLoader_.reset(new ThreadedInstanceLoader(context_, loaderThreads_, transcode_, transferSyntax_)); + instanceLoader_.reset(new ThreadedInstanceLoader(context_, loaderThreads_, transcode_, transferSyntax_, lossyQuality_)); } if (writer_.get() != NULL) @@ -1582,7 +1610,7 @@ const DynamicTemporaryFile& f = dynamic_cast(accessor.GetItem()); f.GetFile().Read(output); mime = MimeType_Zip; - filename = "archive.zip"; + filename = filename_; return true; } else diff -r dfc271554574 -r ce002301ecea OrthancServer/Sources/ServerJobs/ArchiveJob.h --- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h Tue Mar 11 15:34:04 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h Tue Mar 18 13:35:00 2025 +0100 @@ -57,6 +57,7 @@ bool isMedia_; bool enableExtendedSopClass_; std::string description_; + std::string filename_; boost::shared_ptr writer_; size_t currentStep_; @@ -68,6 +69,7 @@ // New in Orthanc 1.7.0 bool transcode_; DicomTransferSyntax transferSyntax_; + unsigned int lossyQuality_; // New in Orthanc 1.10.0 unsigned int loaderThreads_; @@ -91,12 +93,21 @@ return description_; } + void SetFilename(const std::string& filename); + + const std::string& GetFilename() const + { + return filename_; + } + void AddResource(const std::string& publicId, bool mustExist, ResourceType expectedType); void SetTranscode(DicomTransferSyntax transferSyntax); + void SetLossyQuality(unsigned int lossyQuality); + void SetLoaderThreads(unsigned int loaderThreads); virtual void Reset() ORTHANC_OVERRIDE; diff -r dfc271554574 -r ce002301ecea TODO --- a/TODO Tue Mar 11 15:34:04 2025 +0100 +++ b/TODO Tue Mar 18 13:35:00 2025 +0100 @@ -36,7 +36,7 @@ For a DICOM Transfer, that would be nice to include the modality in the context + a study identifier or a job id. * (1) Accept extra DICOM tags dictionaries in the DCMTK format '.dic' (easier to use than declare them in the Orthanc configuration file). Even the standard dictionaries could be - overriden by these custom dictionaries. + overridden by these custom dictionaries. * Add configurations to enable/disable warnings: - Modifying an instance while keeping its original SOPInstanceUID: This should be avoided! - Modifying a study while keeping its original StudyInstanceUID: This should be avoided! @@ -57,6 +57,8 @@ - Also consider the use case of an Orthanc cluster that is being scaled-down just after one Orthanc instance has received a few instances -> we can not only check for missing stable events at startup since no Orthanc will start. We would need to maintain the list of "unstable" resources in DB instead of memory only. + - Also check the PG plugin and its new table InvalidChildCounts, with a timestamp there, we can detect for + how long a study has not been modified ! * In prometheus metrics, implement Histograms or Exponential Histograms to measure durations. Right now, we only provide "average" durations that are not very relevant (https://opentelemetry.io/docs/specs/otel/metrics/data-model/#histogram) @@ -204,6 +206,10 @@ * Log outgoing C-Find queries * Support other Transfer Syntaxes in the Worklist plugin: https://discourse.orthanc-server.org/t/could-you-please-create-an-option-to-set-the-transfer-syntax-in-the-worklist-plugin-currently-little-endian-explicit-is-fixed/4871 +* Deidentify Trace level logs: https://discourse.orthanc-server.org/t/dicom-log-deidentification-at-trace-log-level/5563 + We should implement something like GetDeidentifiedContent(const DcmDataSet& dataset) that would + reproduce DcmDataSet::print but hide individual elements that contain PHI. + --------- Long-term @@ -258,6 +264,8 @@ Alternative option 4: Catch out-of-memory exceptions at quite high level in HTTPHandlers and DICOM receivers and implement retries. After a few retries, fail for good and return "out-of-resources". Would be interesting to log these errors and count them in the prometheus metrics. + Note: I'm (maybe naively) thinking that you only need the beginning of the file to get the DICOM tags to be stored + in the database, so maybe the rest of the file could be "streamed" directly to disk and not kept in memory? * To investigate: usage of mapped_file (not only in the indexer plugin): https://discourse.orthanc-server.org/t/patch-for-orthanc-indexer-plugin-crashing-on-big-non-dicom-files/3849/7