Mercurial > hg > orthanc
changeset 5885:207371ec031e find-refactoring
integration mainline->find-refactoring
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 29 Nov 2024 10:54:20 +0100 |
parents | 92e5579681f2 (current diff) 1e51e6299f7a (diff) |
children | 9b73ec6a07be |
files | NEWS OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake OrthancFramework/UnitTestsSources/DicomMapTests.cpp |
diffstat | 20 files changed, 336 insertions(+), 62 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Fri Nov 29 09:46:30 2024 +0100 +++ b/NEWS Fri Nov 29 10:54:20 2024 +0100 @@ -22,7 +22,7 @@ REST API -------- -* API version upgraded to 25 +* API version upgraded to 26 * Improved parsing of multiple numerical values in DICOM tags. https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6 * in /system, added a new field "Capabilities" with new values: @@ -45,6 +45,12 @@ is similar to tools/find but only returns the number of resources matching the criteria. * With DB backend with "HasExtendedFind" support, usage of 'Limit' and 'Since in /tools/find is not allowed if your query includes filtering on DICOM tags that are not stored in DB. +* In DICOMWeb json, the "DS - Decimal String" values were represented by float numbers + and they are now represented as strings to avoid introduction of long float representation + (e.g 0.1429999999999 vs "0.143") and be more compliant with the DICOMWeb + standard https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html. + This has no impact on StoneViewer and OHIF. + https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195 Maintenance @@ -57,14 +63,20 @@ times in case you don't call /statistics for a long period. * Fix C-Find queries not returning computed tags like ModalitiesInStudy, NumberOfStudyRelatedSeries, ... in very specific use-cases. +* Fix C-Find queries not returning private tags in the modality worklist plugin. * Fix extremely rare error when 2 threads are trying to create the same folder in the File Storage at the same time. +* Fix crashes if handling very large images. +* Fix deadlock when parsing specific invalid DICOM files. +* Loading plugins: + - Orthanc will now fail to start when provided with a plugin path that can not be found. * Metrics: - fix a few metrics that were not published - added 2 metrics: orthanc_storage_cache_miss_count & orthanc_storage_cache_hit_count * Upgraded dependencies for static builds: - curl 8.9.0 - SQLite 3.46 + - boost 1.86.0 * Added a new fallback when trying to decode a frame: transcode the file using the plugin before decoding the frame. This solves some issues with JP2K Lossy compression: https://discourse.orthanc-server.org/t/decoding-displaying-jpeg2000-lossy-images/5117 @@ -109,7 +121,7 @@ * Housekeeper plugin: - Added an option "LimitMainDicomTagsReconstructLevel" (allowed values: "Patient", "Study", "Series", "Instance"). This can greatly speed - up the housekeeper process, e.g. if you have only update the Study level ExtraMainDicomTags. + up the housekeeper process, e.g. if you have only updated the Study level ExtraMainDicomTags. - Fixed broken /instances/../tags route after running the Housekeeper after having changed the "IngestTranscoding". * SDK: added OrthancPluginLogMessage() as a new primitive for plugins
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.cmake Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Resources/CMake/BoostConfiguration.cmake Fri Nov 29 10:54:20 2024 +0100 @@ -91,10 +91,10 @@ ## Parameters for static compilation of Boost ## - set(BOOST_NAME boost_1_85_0) - set(BOOST_VERSION 1.85.0) - set(BOOST_BCP_SUFFIX bcpdigest-1.12.4) - set(BOOST_MD5 "1017e9c8383efdea01c059a8d3cc4dda") + set(BOOST_NAME boost_1_86_0) + set(BOOST_VERSION 1.86.0) + set(BOOST_BCP_SUFFIX bcpdigest-1.12.5) + set(BOOST_MD5 "20b9c325c0dde830889ee75a9e64ded8") set(BOOST_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz") set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) @@ -115,7 +115,7 @@ if (FirstRun) execute_process( COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-1.85.0-emscripten.patch + ${CMAKE_CURRENT_LIST_DIR}/../Patches/boost-1.86.0-emscripten.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure )
--- a/OrthancFramework/Resources/CMake/BoostConfiguration.sh Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Resources/CMake/BoostConfiguration.sh Fri Nov 29 10:54:20 2024 +0100 @@ -27,9 +27,10 @@ ## - Orthanc 1.12.2: Boost 1.83.0 ## - Orthanc 1.12.3: Boost 1.84.0 ## - Orthanc > 1.12.3: Boost 1.85.0 +## - Orthanc 1.12.5: Boost 1.86.0 -BOOST_VERSION=1_85_0 -ORTHANC_VERSION=1.12.4 +BOOST_VERSION=1_86_0 +ORTHANC_VERSION=1.12.5 rm -rf /tmp/boost_${BOOST_VERSION} rm -rf /tmp/bcp/boost_${BOOST_VERSION}
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Fri Nov 29 10:54:20 2024 +0100 @@ -39,7 +39,7 @@ # Version of the Orthanc API, can be retrieved from "/system" URI in # order to check whether new URI endpoints are available even if using # the mainline version of Orthanc -set(ORTHANC_API_VERSION "25") +set(ORTHANC_API_VERSION "26") #####################################################################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Resources/Patches/boost-1.86.0-emscripten.patch Fri Nov 29 10:54:20 2024 +0100 @@ -0,0 +1,127 @@ +diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/date_time.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/date_time.cpp +--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/date_time.cpp 2024-09-25 15:46:01.000000000 +0200 ++++ boost_1_86_0/libs/locale/src/boost/locale/shared/date_time.cpp 2024-09-25 15:58:51.306131987 +0200 +@@ -12,8 +12,10 @@ + #include <boost/locale/date_time.hpp> + #include <boost/locale/formatting.hpp> + #include <boost/core/exchange.hpp> +-#include <boost/thread/locks.hpp> +-#include <boost/thread/mutex.hpp> ++#if !defined(__EMSCRIPTEN__) ++# include <boost/thread/locks.hpp> ++# include <boost/thread/mutex.hpp> ++#endif + #include <cmath> + + namespace boost { namespace locale { +@@ -400,6 +402,7 @@ + return impl_->get_option(abstract_calendar::is_dst) != 0; + } + ++#if !defined(__EMSCRIPTEN__) + namespace time_zone { + boost::mutex& tz_mutex() + { +@@ -422,7 +425,7 @@ + return boost::exchange(tz_id(), new_id); + } + } // namespace time_zone +- ++#endif + }} // namespace boost::locale + + // boostinspect:nominmax +diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/generator.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/generator.cpp +--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/generator.cpp 2024-09-25 15:46:01.000000000 +0200 ++++ boost_1_86_0/libs/locale/src/boost/locale/shared/generator.cpp 2024-09-25 16:00:07.756233916 +0200 +@@ -7,8 +7,10 @@ + #include <boost/locale/encoding.hpp> + #include <boost/locale/generator.hpp> + #include <boost/locale/localization_backend.hpp> +-#include <boost/thread/locks.hpp> +-#include <boost/thread/mutex.hpp> ++#if !defined(__EMSCRIPTEN__) ++# include <boost/thread/locks.hpp> ++# include <boost/thread/mutex.hpp> ++#endif + #include <algorithm> + #include <map> + #include <vector> +@@ -21,8 +23,9 @@ + {} + + mutable std::map<std::string, std::locale> cached; ++#if !defined(__EMSCRIPTEN__) + mutable boost::mutex cached_lock; +- ++#endif + category_t cats; + char_facet_t chars; + +@@ -101,7 +104,9 @@ + std::locale generator::generate(const std::locale& base, const std::string& id) const + { + if(d->caching_enabled) { ++#if !defined(__EMSCRIPTEN__) + boost::unique_lock<boost::mutex> guard(d->cached_lock); ++#endif + const auto p = d->cached.find(id); + if(p != d->cached.end()) + return p->second; +@@ -126,7 +131,9 @@ + result = backend->install(result, facet, char_facet_t::nochar); + } + if(d->caching_enabled) { ++#if !defined(__EMSCRIPTEN__) + boost::unique_lock<boost::mutex> guard(d->cached_lock); ++#endif + const auto p = d->cached.find(id); + if(p == d->cached.end()) + d->cached[id] = result; +diff -urEb boost_1_86_0.orig/libs/locale/src/boost/locale/shared/localization_backend.cpp boost_1_86_0/libs/locale/src/boost/locale/shared/localization_backend.cpp +--- boost_1_86_0.orig/libs/locale/src/boost/locale/shared/localization_backend.cpp 2024-09-25 15:46:01.000000000 +0200 ++++ boost_1_86_0/libs/locale/src/boost/locale/shared/localization_backend.cpp 2024-09-25 16:01:09.196820495 +0200 +@@ -5,8 +5,10 @@ + // https://www.boost.org/LICENSE_1_0.txt + + #include <boost/locale/localization_backend.hpp> +-#include <boost/thread/locks.hpp> +-#include <boost/thread/mutex.hpp> ++#if !defined(__EMSCRIPTEN__) ++# include <boost/thread/locks.hpp> ++# include <boost/thread/mutex.hpp> ++#endif + #include <functional> + #include <memory> + #include <vector> +@@ -211,11 +213,13 @@ + return mgr; + } + ++#if !defined(__EMSCRIPTEN__) + boost::mutex& localization_backend_manager_mutex() + { + static boost::mutex the_mutex; + return the_mutex; + } ++#endif + localization_backend_manager& localization_backend_manager_global() + { + static localization_backend_manager the_manager = make_default_backend_mgr(); +@@ -225,12 +229,16 @@ + + localization_backend_manager localization_backend_manager::global() + { ++#if !defined(__EMSCRIPTEN__) + boost::unique_lock<boost::mutex> lock(localization_backend_manager_mutex()); ++#endif + return localization_backend_manager_global(); + } + localization_backend_manager localization_backend_manager::global(const localization_backend_manager& in) + { ++#if !defined(__EMSCRIPTEN__) + boost::unique_lock<boost::mutex> lock(localization_backend_manager_mutex()); ++#endif + return exchange(localization_backend_manager_global(), in); + } +
--- a/OrthancFramework/Resources/Patches/dcmtk-3.6.8.patch Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Resources/Patches/dcmtk-3.6.8.patch Fri Nov 29 10:54:20 2024 +0100 @@ -1,6 +1,6 @@ diff -urEb dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake ---- dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake 2024-01-09 17:13:10.329673608 +0100 -+++ dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/CMake/GenerateDCMTKConfigure.cmake 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/CMake/GenerateDCMTKConfigure.cmake 2024-11-25 16:54:59.036009112 +0100 @@ -224,6 +224,8 @@ # Check the sizes of various types @@ -18,9 +18,11 @@ # Check for include files, libraries, and functions include("${DCMTK_CMAKE_INCLUDE}CMake/dcmtkTryCompile.cmake") +Only in dcmtk-DCMTK-3.6.8/config/include/dcmtk/config: arith.h +Only in dcmtk-DCMTK-3.6.8/config/include/dcmtk/config: osconfig.h diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h ---- dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2024-01-09 17:13:10.337673529 +0100 -+++ dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/dcmdata/include/dcmtk/dcmdata/dcdict.h 2024-11-25 16:54:59.036009112 +0100 @@ -162,6 +162,12 @@ /// returns an iterator to the end of the repeating tag dictionary DcmDictEntryListIterator repeatingEnd() { return repDict.end(); } @@ -35,17 +37,18 @@ /** private undefined assignment operator diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc ---- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc 2024-01-09 17:13:10.337673529 +0100 -+++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcdict.cc 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcdict.cc 2024-11-25 16:54:59.036009112 +0100 @@ -914,3 +914,5 @@ wrlock().clear(); wrunlock(); } + +#include "dcdict_orthanc.cc" +Only in dcmtk-DCMTK-3.6.8/dcmdata/libsrc: dcdict_orthanc.cc diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc ---- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc 2024-01-09 17:13:10.337673529 +0100 -+++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/dcmdata/libsrc/dcpxitem.cc 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/dcmdata/libsrc/dcpxitem.cc 2024-11-25 16:54:59.036009112 +0100 @@ -31,6 +31,9 @@ #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ @@ -57,8 +60,8 @@ // ******************************** diff -urEb dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc ---- dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc 2024-01-09 17:13:10.349673411 +0100 -+++ dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc 2024-01-09 18:23:08.723435667 +0100 +--- dcmtk-DCMTK-3.6.8.orig/dcmnet/libsrc/scu.cc 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/dcmnet/libsrc/scu.cc 2024-11-25 16:54:59.036009112 +0100 @@ -19,6 +19,11 @@ * */ @@ -72,8 +75,8 @@ #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ diff -urEb dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h ---- dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2024-01-09 17:13:10.389673016 +0100 -+++ dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/oflog/include/dcmtk/oflog/thread/syncpub.h 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/oflog/include/dcmtk/oflog/thread/syncpub.h 2024-11-25 16:54:59.037009100 +0100 @@ -63,7 +63,7 @@ DCMTK_LOG4CPLUS_INLINE_EXPORT @@ -111,8 +114,8 @@ diff -urEb dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc ---- dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc 2024-01-09 17:13:10.389673016 +0100 -+++ dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/oflog/libsrc/oflog.cc 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/oflog/libsrc/oflog.cc 2024-11-25 16:54:59.037009100 +0100 @@ -19,6 +19,11 @@ * */ @@ -126,8 +129,8 @@ #include "dcmtk/oflog/oflog.h" diff -urEb dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h ---- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h 2024-01-09 17:13:10.389673016 +0100 -+++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h 2024-01-09 18:21:52.568142681 +0100 +--- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/offile.h 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/offile.h 2024-11-25 16:54:59.037009100 +0100 @@ -570,7 +570,7 @@ */ void setlinebuf() @@ -137,3 +140,18 @@ this->setvbuf(NULL, _IOLBF, 0); #else :: setlinebuf(file_); +diff -urEb dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/ofutil.h dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/ofutil.h +--- dcmtk-DCMTK-3.6.8.orig/ofstd/include/dcmtk/ofstd/ofutil.h 2023-12-19 11:12:57.000000000 +0100 ++++ dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd/ofutil.h 2024-11-25 17:00:27.525244000 +0100 +@@ -75,8 +75,8 @@ + // copy constructor should be fine for primitive types. + inline type(const T& pt) + : t( pt ) {} +- inline type(const OFrvalue_storage& rhs) +- : t( rhs.pt ) {} ++ inline type(const type& rhs) ++ : t( rhs.t ) {} + + // automatic conversion to the underlying type + inline operator T&() const { return OFconst_cast( T&, t ); } +Only in dcmtk-DCMTK-3.6.8/ofstd/include/dcmtk/ofstd: ofutil.h~
--- a/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -247,6 +247,10 @@ pos += length + 12; } + else + { + throw OrthancException(ErrorCode_BadFileFormat, "Invalid DICOM File: Unable to parse Meta Header"); + } } if (pos != block.size())
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -202,7 +202,7 @@ DicomToJsonFormat format) const { const ParsedDicomFile& answer = GetAnswer(index); - answer.DatasetToJson(target, format, DicomToJsonFlags_None, 0); + answer.DatasetToJson(target, format, DicomToJsonFlags_IncludePrivateTags, 0); }
--- a/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -28,6 +28,7 @@ #include "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" +#include "../SerializationToolbox.h" #include "FromDcmtkBridge.h" #include <boost/math/special_functions/round.hpp> @@ -341,6 +342,31 @@ } } + Json::Value DicomWebJsonVisitor::FormatDecimalString(double value, const std::string& originalString) + { + try + { + long long a = boost::math::llround<double>(value); + + double d = fabs(value - static_cast<double>(a)); + + if (d <= std::numeric_limits<double>::epsilon() * 100.0) + { + return FormatInteger(a); // if the decimal number is an integer, you can represent it as an integer + } + else + { + return Json::Value(originalString); // keep the original string to avoid rounding errors e.g, transforming "0.143" into 0.14299999999999 + } + } + catch (boost::math::rounding_error&) + { + // Can occur if "long long" is too small to receive this value + // (e.g. infinity) + return Json::Value(originalString); + } + } + DicomWebJsonVisitor::DicomWebJsonVisitor() : formatter_(NULL) { @@ -677,14 +703,33 @@ case ValueRepresentation_DecimalString: { std::string t = Toolbox::StripSpaces(tokens[i]); + boost::replace_all(t, ",", "."); // some invalid files uses "," instead of "." + + // remove invalid/useless trailing decimal separator + if (t.size() > 0 && t[t.size()-1] == '.') + { + t.resize(t.size() -1); + } + if (t.empty()) { node[KEY_VALUE].append(Json::nullValue); } else { - double tmp = boost::lexical_cast<double>(t); - node[KEY_VALUE].append(FormatDouble(tmp)); + // https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html + // DS values can be represented as String or Number in Json. + // For IS, DS, SV and UV, a JSON String representation can be used to preserve the original format during transformation of the representation, or if needed to avoid losing precision of a decimal string. + // Since 1.12.5, always use the string repesentation. Before, decimal numbers were represented as double which led to loss of precision (e.g: 0.143 represented as 0.1429999999) + double tmp; + if (SerializationToolbox::ParseDouble(tmp, t)) // make sure that the string contains a valid decimal number + { + node[KEY_VALUE].append(t); + } + else + { + throw boost::bad_lexical_cast(); + } } break;
--- a/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h Fri Nov 29 10:54:20 2024 +0100 @@ -74,6 +74,8 @@ static Json::Value FormatDouble(double value); + static Json::Value FormatDecimalString(double value, const std::string& originalString); + public: DicomWebJsonVisitor();
--- a/OrthancFramework/Sources/Images/ImageAccessor.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/Images/ImageAccessor.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -139,7 +139,7 @@ return pitch_; } - unsigned int ImageAccessor::GetSize() const + size_t ImageAccessor::GetSize() const { return GetHeight() * GetPitch(); } @@ -165,7 +165,7 @@ { if (buffer_ != NULL) { - return buffer_ + y * pitch_; + return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_); } else { @@ -184,7 +184,7 @@ if (buffer_ != NULL) { - return buffer_ + y * pitch_; + return buffer_ + static_cast<size_t>(y) * static_cast<size_t>(pitch_); } else { @@ -325,8 +325,8 @@ else { uint8_t* p = (buffer_ + - y * pitch_ + - x * GetBytesPerPixel()); + static_cast<size_t>(y) * static_cast<size_t>(pitch_) + + static_cast<size_t>(x) * static_cast<size_t>(GetBytesPerPixel())); if (readOnly_) {
--- a/OrthancFramework/Sources/Images/ImageAccessor.h Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/Images/ImageAccessor.h Fri Nov 29 10:54:20 2024 +0100 @@ -86,7 +86,7 @@ unsigned int GetPitch() const; - unsigned int GetSize() const; + size_t GetSize() const; const void* GetConstBuffer() const;
--- a/OrthancFramework/Sources/Images/ImageBuffer.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/Images/ImageBuffer.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -47,7 +47,7 @@ */ pitch_ = GetBytesPerPixel() * width_; - size_t size = pitch_ * height_; + size_t size = static_cast<size_t>(pitch_) * static_cast<size_t>(height_); if (size == 0) {
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -2888,8 +2888,7 @@ const unsigned int width = image.GetWidth(); const unsigned int height = image.GetHeight(); const unsigned int pitch = image.GetPitch(); - uint8_t* buffer = reinterpret_cast<uint8_t*>(image.GetBuffer()); - + if (image.GetFormat() != PixelFormat_RGB24 || pitch < 3 * width) { @@ -2898,7 +2897,7 @@ for (unsigned int y = 0; y < height; y++) { - uint8_t* p = buffer + y * pitch; + uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); for (unsigned int x = 0; x < width; x++, p += 3) {
--- a/OrthancFramework/Sources/Toolbox.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/Sources/Toolbox.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -801,7 +801,6 @@ return result; } - void Toolbox::ComputeSHA1(std::string& result, const void* data, size_t size) @@ -813,11 +812,31 @@ sha1.process_bytes(data, size); } - unsigned int digest[5]; +#if BOOST_VERSION >= 108600 + unsigned char digest[20]; // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide - assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); - + assert(sizeof(digest) == (160 / 8)); + assert(sizeof(boost::uuids::detail::sha1::digest_type) == 20); + + // From Boost 1.86, digest_type is "unsigned char[20]" while it was "unsigned int[5]"" in previous versions. + // Always perform the cast even if it is useless for Boost < 1.86 + sha1.get_digest(digest); + + result.resize(8 * 5 + 4); + sprintf(&result[0], "%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x", + digest[0], digest[1], digest[2], digest[3], + digest[4], digest[5], digest[6], digest[7], + digest[8], digest[9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15], + digest[16], digest[17], digest[18], digest[19]); + +#else + unsigned int digest[5]; + // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide + assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); + assert(sizeof(boost::uuids::detail::sha1::digest_type) == 20); + sha1.get_digest(digest); result.resize(8 * 5 + 4); @@ -827,6 +846,9 @@ digest[2], digest[3], digest[4]); + +#endif + } void Toolbox::ComputeSHA1(std::string& result,
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -806,6 +806,7 @@ dicom.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "SB1^SB2^SB3^SB4^SB5"); dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1\\2.3\\4"); dicom.ReplacePlainString(DICOM_TAG_IMAGE_POSITION_PATIENT, ""); + dicom.ReplacePlainString(DICOM_TAG_PIXEL_SPACING, "0,143\\0,143"); // seen in https://discourse.orthanc-server.org/t/dicomwebplugin-does-not-return-series-metadata-properly/5195 DicomWebJsonVisitor visitor; dicom.Apply(visitor); @@ -817,10 +818,10 @@ ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); ASSERT_EQ(2u, tag.getMemberNames().size()); ASSERT_EQ(3u, value.size()); - ASSERT_EQ(Json::realValue, value[1].type()); - ASSERT_FLOAT_EQ(1.0f, value[0].asFloat()); - ASSERT_FLOAT_EQ(2.3f, value[1].asFloat()); - ASSERT_FLOAT_EQ(4.0f, value[2].asFloat()); + ASSERT_EQ(Json::stringValue, value[1].type()); // since Orthanc 1.12.5, this is now stored as a string + ASSERT_EQ("1", value[0].asString()); + ASSERT_EQ("2.3", value[1].asString()); + ASSERT_EQ("4", value[2].asString()); } { @@ -829,13 +830,23 @@ ASSERT_EQ(1u, tag.getMemberNames().size()); } + { + const Json::Value& tag = visitor.GetResult() ["00280030"]; // PixelSpacing + const Json::Value& value = tag["Value"]; + + ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); + ASSERT_EQ(2u, value.size()); + ASSERT_EQ("0.143", value[0].asString()); + ASSERT_EQ("0.143", value[1].asString()); + } + std::string xml; visitor.FormatXml(xml); { DicomMap m; m.FromDicomWeb(visitor.GetResult()); - ASSERT_EQ(3u, m.GetSize()); + ASSERT_EQ(4u, m.GetSize()); std::string s; ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false)); @@ -871,12 +882,12 @@ ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); ASSERT_EQ(2u, tag.getMemberNames().size()); ASSERT_EQ(4u, value.size()); - ASSERT_EQ(Json::realValue, value[0].type()); + ASSERT_EQ(Json::stringValue, value[0].type()); ASSERT_EQ(Json::nullValue, value[1].type()); ASSERT_EQ(Json::nullValue, value[2].type()); - ASSERT_EQ(Json::realValue, value[3].type()); - ASSERT_FLOAT_EQ(1.5f, value[0].asFloat()); - ASSERT_FLOAT_EQ(2.5f, value[3].asFloat()); + ASSERT_EQ(Json::stringValue, value[3].type()); + ASSERT_EQ("1.5", value[0].asString()); + ASSERT_EQ("2.5", value[3].asString()); } std::string xml; @@ -914,8 +925,8 @@ target.FromDicomWeb(visitor.GetResult()); ASSERT_EQ("DS", visitor.GetResult() ["00280030"]["vr"].asString()); - ASSERT_FLOAT_EQ(1.5f, visitor.GetResult() ["00280030"]["Value"][0].asFloat()); - ASSERT_FLOAT_EQ(1.3f, visitor.GetResult() ["00280030"]["Value"][1].asFloat()); + ASSERT_EQ("1.5", visitor.GetResult() ["00280030"]["Value"][0].asString()); + ASSERT_EQ("1.3", visitor.GetResult() ["00280030"]["Value"][1].asString()); std::string s; ASSERT_TRUE(target.LookupStringValue(s, DICOM_TAG_PIXEL_SPACING, false));
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -2043,7 +2043,7 @@ ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString()); ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString()); ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString()); - ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat()); + ASSERT_EQ("42", visitor.GetResult() ["00101020"]["Value"][0].asString()); ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString()); ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString()); ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
--- a/OrthancServer/Plugins/Engine/PluginsManager.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -244,8 +244,21 @@ { if (!boost::filesystem::exists(path)) { - LOG(ERROR) << "Inexistent path to plugins: " << path; - return; + boost::filesystem::path p(path); + std::string extension = p.extension().string(); + Toolbox::ToLowerCase(extension); + + if (extension == PLUGIN_EXTENSION) + { + // if this is a plugin path, fail to start + throw OrthancException(ErrorCode_SharedLibrary, "Inexistent path to plugin: " + path); + } + else + { + // it might be a directory -> just log a warning + LOG(WARNING) << "Inexistent path to plugins: " << path; + return; + } } if (boost::filesystem::is_directory(path))
--- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Fri Nov 29 09:46:30 2024 +0100 +++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp Fri Nov 29 10:54:20 2024 +0100 @@ -859,12 +859,15 @@ "StorageCompressionChange": true, "MainDicomTagsChange": true, "UnnecessaryDicomAsJsonFiles": true, + "IngestTranscodingChange": true, "DicomWebCacheChange": true // new in 1.12.2 }, - // When rebuilding MainDicomTags, limit to a single level of resource. - // Allowed values: "Patient", "Study", "Series", "Instance" - "LimitMainDicomTagsReconstructLevel": "Study" + // When rebuilding MainDicomTags, limit to a single level of resource + // which can greatly improve performances e.g. if you have only updated + // the Study level ExtraMainDicomTags. + // Allowed values: "Patient", "Study", "Series", "Instance", "All" + "LimitMainDicomTagsReconstructLevel": "All" } } @@ -887,11 +890,12 @@ triggerOnDicomWebCacheChange_ = triggers.GetBooleanValue("DicomWebCacheChange", true); } - limitMainDicomTagsReconstructLevel_ = housekeeper.GetStringValue("LimitMainDicomTagsReconstructLevel", ""); + limitMainDicomTagsReconstructLevel_ = housekeeper.GetStringValue("LimitMainDicomTagsReconstructLevel", "All"); if (limitMainDicomTagsReconstructLevel_ != "Patient" && limitMainDicomTagsReconstructLevel_ != "Study" - && limitMainDicomTagsReconstructLevel_ != "Series" && limitMainDicomTagsReconstructLevel_ != "Instance") + && limitMainDicomTagsReconstructLevel_ != "Series" && limitMainDicomTagsReconstructLevel_ != "Instance" && limitMainDicomTagsReconstructLevel_ != "All") { ORTHANC_PLUGINS_LOG_ERROR("Housekeeper invalid value for 'LimitMainDicomTagsReconstructLevel': '" + limitMainDicomTagsReconstructLevel_ + "'"); + return -1; } else if (limitMainDicomTagsReconstructLevel_ == "Patient") {
--- a/TODO Fri Nov 29 09:46:30 2024 +0100 +++ b/TODO Fri Nov 29 10:54:20 2024 +0100 @@ -174,6 +174,8 @@ -------- * Support C-GET SCU (note that C-GET SCP was introduced in Orthanc 1.7.0) +* Configure the list of accepted SOP Classes + https://discourse.orthanc-server.org/t/can-you-limit-the-sop-classes-accepted-as-store-scp/4606 * Support "Retrieve AE Title" (0008,0054) in C-FIND: - On SCP side: done by https://orthanc.uclouvain.be/hg/orthanc/rev/1ec3e1e18f50 - On SCU side: @@ -282,8 +284,22 @@ https://groups.google.com/g/orthanc-users/c/ymtaAmgSs6Q/m/PqVBactQAQAJ * Add an index on the UUID column in the DelayedDeletion plugin: https://discourse.orthanc-server.org/t/delayeddeletion-improvement-unique-index-on-pending-uuid-column/4032 +* Orthanc shall refuse to start if one registers 2 storage plugins. + Right now, this is not possible because OrthancPluginRegisterStorageArea2 does not return any value + and it can not throw an Exception because that's a core function called from a plugin -> the Exception + can not cross the C/C++ frontier safely -> we need a OrthancPluginRegisterStorageArea3 with a return value. + Ex: install DelayedDeletion + S3 storage. Right now, the second plugin to load is just ignored with an error + message in the logs. +----------- +Housekeeper +----------- + +* The Housekeeper should just refuse to start if you are using a lossy transfer syntax because that would generate + new orthanc ids as well and there is a risk of messing up things. tools/reconstruct shall return a 400 as well. + https://discourse.orthanc-server.org/t/crashes-on-housekeeper-transcode-storing-in-s3/5330/2 + ---------------- Ideas of plugins ----------------