# HG changeset patch # User Alain Mazy # Date 1725290242 -7200 # Node ID 796cb17db15c8c658fed0c2fae4b442849e725ce # Parent 95a3802ad13326c1083bf31ca31c8ec9e3f9d81a# Parent 8bb3f2fca242cfe64ef323d2c0cd94adcbf4926b merged default -> find-refactoring diff -r 95a3802ad133 -r 796cb17db15c NEWS --- a/NEWS Fri Jul 19 14:02:22 2024 +0200 +++ b/NEWS Mon Sep 02 17:17:22 2024 +0200 @@ -21,6 +21,14 @@ for a long period. * Fix C-Find queries not returning computed tags like ModalitiesInStudy, NumberOfStudyRelatedSeries, ... in very specific use-cases. +* Fix extremely rare error when 2 threads are trying to create the same folder in the File Storage + at the same time. +* Upgraded dependencies for static builds: + - curl 8.9.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 +* Added a new warning that can be disabled in the configuration: W003_DecoderFailure diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Resources/CMake/Compiler.cmake --- a/OrthancFramework/Resources/CMake/Compiler.cmake Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/Resources/CMake/Compiler.cmake Mon Sep 02 17:17:22 2024 +0200 @@ -232,6 +232,10 @@ endif() elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + + # fix this error that appears with recent compilers on MacOS: boost/mpl/aux_/integral_wrapper.hpp:73:31: error: integer value -1 is outside the valid range of values [0, 3] for this enumeration type [-Wenum-constexpr-conversion] + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-enum-constexpr-conversion") + add_definitions( -D_XOPEN_SOURCE=1 ) diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake --- a/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Mon Sep 02 17:17:22 2024 +0200 @@ -21,9 +21,9 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_CURL) - SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.5.0) - SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.5.0.tar.gz") - SET(CURL_MD5 "0bc69288b20ae165ff4b7d6d7bbe70d2") + SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.9.0) + SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.9.0.tar.gz") + SET(CURL_MD5 "f9bca5d4d5bac1f04e6c5eb4d0418618") if (IS_DIRECTORY "${CURL_SOURCES_DIR}") set(FirstRun OFF) @@ -36,7 +36,7 @@ if (FirstRun) execute_process( COMMAND ${PATCH_EXECUTABLE} -p0 -N -i - ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.5.0.patch + ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.9.0.patch WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Resources/Patches/curl-8.5.0.patch --- a/OrthancFramework/Resources/Patches/curl-8.5.0.patch Fri Jul 19 14:02:22 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -diff -urEb curl-8.5.0.orig/CMake/Macros.cmake curl-8.5.0/CMake/Macros.cmake ---- curl-8.5.0.orig/CMake/Macros.cmake 2024-01-24 17:21:21.387965189 +0100 -+++ curl-8.5.0/CMake/Macros.cmake 2024-01-24 17:21:48.523719072 +0100 -@@ -48,7 +48,7 @@ - message(STATUS "Performing Curl Test ${CURL_TEST}") - try_compile(${CURL_TEST} - ${CMAKE_BINARY_DIR} -- ${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c -+ ${CURL_SOURCES_DIR}/CMake/CurlTests.c - CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} - "${CURL_TEST_ADD_LIBRARIES}" - OUTPUT_VARIABLE OUTPUT) diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Resources/Patches/curl-8.9.0.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Resources/Patches/curl-8.9.0.patch Mon Sep 02 17:17:22 2024 +0200 @@ -0,0 +1,12 @@ +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 +@@ -48,7 +48,7 @@ + message(STATUS "Performing Test ${CURL_TEST}") + try_compile(${CURL_TEST} + ${CMAKE_BINARY_DIR} +- ${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c ++ ${CURL_SOURCES_DIR}/CMake/CurlTests.c + CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_FUNCTION_DEFINITIONS} + "${CURL_TEST_ADD_LIBRARIES}" + OUTPUT_VARIABLE OUTPUT) diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Resources/Patches/protobuf-3.5.1.patch --- a/OrthancFramework/Resources/Patches/protobuf-3.5.1.patch Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/Resources/Patches/protobuf-3.5.1.patch Mon Sep 02 17:17:22 2024 +0200 @@ -2,7 +2,7 @@ --- protobuf-3.5.1.orig/src/google/protobuf/stubs/io_win32.cc 2023-03-26 20:13:45.095021011 +0200 +++ protobuf-3.5.1/src/google/protobuf/stubs/io_win32.cc 2023-03-26 20:19:19.932920102 +0200 @@ -91,7 +91,12 @@ - + template bool null_or_empty(const char_type* s) { - return s == nullptr || *s == 0; @@ -13,5 +13,18 @@ + **/ + return s == NULL || *s == 0; } - + // Returns true if the path starts with a drive letter, e.g. "c:". +diff -urEb protobuf-3.5.1.orig/src/google/protobuf/stubs/hash.h protobuf-3.5.1/src/google/protobuf/stubs/hash.h +--- protobuf-3.5.1.orig/src/google/protobuf/stubs/hash.h 2023-03-26 20:13:45.095021011 +0200 ++++ protobuf-3.5.1/src/google/protobuf/stubs/hash.h 2023-03-26 20:19:19.932920102 +0200 +@@ -1,3 +1,9 @@ ++#if _MSC_VER >= 1930 // Since Visual Studio 2022 ++#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS ++#include ++#include ++#endif ++ + // Protocol Buffers - Google's data interchange format + // Copyright 2008 Google Inc. All rights reserved. + // https://developers.google.com/protocol-buffers/ \ No newline at end of file diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp --- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -949,12 +949,12 @@ { throw OrthancException(ErrorCode_NotImplemented, "The built-in DCMTK decoder cannot decode some DICOM instance " - "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s))); + "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), false /* don't log here*/); } else { throw OrthancException(ErrorCode_NotImplemented, - "The built-in DCMTK decoder cannot decode some DICOM instance"); + "The built-in DCMTK decoder cannot decode some DICOM instance", false /* don't log here*/); } } diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp --- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -24,6 +24,7 @@ #include "../PrecompiledHeaders.h" #include "FilesystemStorage.h" +#include // http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system // http://stackoverflow.com/questions/446358/storing-a-large-number-of-images @@ -135,26 +136,54 @@ { // Extremely unlikely case: This Uuid has already been created // in the past. - throw OrthancException(ErrorCode_InternalError); + throw OrthancException(ErrorCode_InternalError, "This file UUID already exists"); } - if (boost::filesystem::exists(path.parent_path())) + // In very unlikely cases, a thread could be deleting a + // directory while another thread needs it -> introduce 3 retries at 1 ms interval + int retryCount = 0; + const int maxRetryCount = 3; + + while (retryCount < maxRetryCount) { - if (!boost::filesystem::is_directory(path.parent_path())) + retryCount++; + if (retryCount > 1) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(2 * retryCount + (rand() % 10))); + LOG(INFO) << "Retrying to create attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) + << "\" type"; + } + + try + { + boost::filesystem::create_directories(path.parent_path()); // the function ensures that the directory exists or throws + } + catch (boost::filesystem::filesystem_error& er) { - throw OrthancException(ErrorCode_DirectoryOverFile); + if (er.code() == boost::system::errc::file_exists // the last element of the parent_path is a file + || er.code() == boost::system::errc::not_a_directory) // one of the element of the parent_path is not a directory + { + throw OrthancException(ErrorCode_DirectoryOverFile, "One of the element of the path is a file"); // no need to retry this error + } + + // ignore other errors and retry + } + + try + { + SystemToolbox::WriteFile(content, size, path.string(), fsyncOnWrite_); + + LOG(INFO) << "Created attachment \"" << uuid << "\" (" << timer.GetHumanTransferSpeed(true, size) << ")"; + return; + } + catch (OrthancException& ex) + { + if (retryCount >= maxRetryCount) + { + throw ex; + } } } - else - { - if (!boost::filesystem::create_directories(path.parent_path())) - { - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - } - - SystemToolbox::WriteFile(content, size, path.string(), fsyncOnWrite_); - LOG(INFO) << "Created attachment \"" << uuid << "\" (" << timer.GetHumanTransferSpeed(true, size) << ")"; } diff -r 95a3802ad133 -r 796cb17db15c OrthancFramework/UnitTestsSources/FileStorageTests.cpp --- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -37,6 +37,7 @@ #include "../Sources/Logging.h" #include "../Sources/OrthancException.h" #include "../Sources/Toolbox.h" +#include "../Sources/SystemToolbox.h" #include @@ -88,6 +89,48 @@ ASSERT_EQ(s.GetSize(uid), data.size()); } +TEST(FilesystemStorage, FileWithSameNameAsTopDirectory) +{ + FilesystemStorage s("UnitTestsStorageTop"); + s.Clear(); + + std::vector data; + StringToVector(data, Toolbox::GenerateUuid()); + + SystemToolbox::WriteFile("toto", "UnitTestsStorageTop/12"); + ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException); + s.Clear(); +} + +TEST(FilesystemStorage, FileWithSameNameAsChildDirectory) +{ + FilesystemStorage s("UnitTestsStorageChild"); + s.Clear(); + + std::vector data; + StringToVector(data, Toolbox::GenerateUuid()); + + SystemToolbox::MakeDirectory("UnitTestsStorageChild/12"); + SystemToolbox::WriteFile("toto", "UnitTestsStorageChild/12/34"); + ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException); + s.Clear(); +} + +TEST(FilesystemStorage, FileAlreadyExists) +{ + FilesystemStorage s("UnitTestsStorageFileAlreadyExists"); + s.Clear(); + + std::vector data; + StringToVector(data, Toolbox::GenerateUuid()); + + SystemToolbox::MakeDirectory("UnitTestsStorageFileAlreadyExists/12/34"); + SystemToolbox::WriteFile("toto", "UnitTestsStorageFileAlreadyExists/12/34/12345678-1234-1234-1234-1234567890ab"); + ASSERT_THROW(s.Create("12345678-1234-1234-1234-1234567890ab", &data[0], data.size(), FileContentType_Unknown), OrthancException); + s.Clear(); +} + + TEST(FilesystemStorage, EndToEnd) { FilesystemStorage s("UnitTestsStorage"); diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/OrthancExplorer/explorer.js --- a/OrthancServer/OrthancExplorer/explorer.js Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/OrthancExplorer/explorer.js Mon Sep 02 17:17:22 2024 +0200 @@ -715,7 +715,7 @@ $('.' + liClass).remove(); for (var key in attachments) { if (attachments[key] >= 1024) { - target.append('
  • Download ' + key + '
  • ') + target.append('
  • Download attachment "' + key + '"
  • ') } } target.listview('refresh'); diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Resources/Configuration.json Mon Sep 02 17:17:22 2024 +0200 @@ -205,6 +205,7 @@ // Set the timeout (in seconds) after which the DICOM associations // are closed by the Orthanc SCP (server) if no further DIMSE // command is received from the SCU (client). + // A value of 0 means "no timeout". "DicomScpTimeout" : 30, @@ -491,6 +492,7 @@ // The timeout (in seconds) after which the DICOM associations are // considered as closed by the Orthanc SCU (client) if the remote // DICOM SCP (server) does not answer. + // A value of 0 means "no timeout". "DicomScuTimeout" : 10, // During a C-STORE SCU request initiated by Orthanc, if the remote @@ -984,7 +986,11 @@ // your response might be incomplete/inconsistent. // You should call patients|studies|series|instances/../reconstruct to rebuild // the DB. You may also check for the "Housekeeper" plugin - "W002_InconsistentDicomTagsInDb": true + "W002_InconsistentDicomTagsInDb": true, + + // Display a warning message when Orthanc and its plugins are unable + // to decode a frame (new in Orthanc 1.12.5). + "W003_DecoderFailure": true } } diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/OrthancConfiguration.cpp --- a/OrthancServer/Sources/OrthancConfiguration.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/OrthancConfiguration.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -1157,6 +1157,10 @@ { warning = Warnings_002_InconsistentDicomTagsInDb; } + else if (name == "W003_DecoderFailure") + { + warning = Warnings_003_DecoderFailure; + } else { throw OrthancException(ErrorCode_BadFileFormat, name + " is not recognized as a valid warning name"); diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -472,7 +472,7 @@ "If `true`, run the job in asynchronous mode, which means that the REST API call will immediately " "return, reporting the identifier of a job. Prefer this flavor wherever possible.", false) .SetRequestField(KEY_PRIORITY, RestApiCallDocumentation::Type_Number, - "In asynchronous mode, the priority of the job. The lower the value, the higher the priority.", false) + "In asynchronous mode, the priority of the job. The higher the value, the higher the priority.", false) .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "In asynchronous mode, identifier of the job") .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "In asynchronous mode, path to access the job in the REST API"); } diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -509,7 +509,7 @@ "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("Priority", RestApiCallDocumentation::Type_Number, - "In asynchronous mode, the priority of the job. The lower the value, the higher the priority.", false) + "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") .AddAnswerType(MimeType_Json, "In asynchronous mode, information about the job that has been submitted to " "generate the archive: https://orthanc.uclouvain.be/book/users/advanced-rest.html#jobs") diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -65,7 +65,7 @@ call.GetDocumentation() .SetTag("Tracking changes") .SetSummary("List changes") - .SetDescription("Whenever Orthanc receives a new DICOM instance, this event is recorded in the so-called _Changes Log_. This enables remote scripts to react to the arrival of new DICOM resources. A typical application is auto-routing, where an external script waits for a new DICOM instance to arrive into Orthanc, then forward this instance to another modality.") + .SetDescription("Whenever Orthanc receives a new DICOM instance, this event is recorded in the so-called _Changes Log_. This enables remote scripts to react to the arrival of new DICOM resources. A typical application is auto-routing, where an external script waits for a new DICOM instance to arrive into Orthanc, then forward this instance to another modality. Please note that, when resources are deleted, their corresponding change entries are also removed from the Changes Log, which helps ensuring that this log does not grow indefinitely.") .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false) .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index", false) .AddAnswerType(MimeType_Json, "The list of changes") diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -1347,18 +1347,17 @@ // Throttle to avoid loading several large DICOM files simultaneously largeDicomLocker_.reset(new Semaphore::Locker(context.largeDicomThrottler_)); - std::string content; - context_.ReadDicom(content, instancePublicId); + context_.ReadDicom(buffer_, instancePublicId_); // Release the throttle if loading "small" DICOM files (under // 50MB, which is an arbitrary value) - if (content.size() < 50 * 1024 * 1024) + if (buffer_.size() < 50 * 1024 * 1024) { largeDicomLocker_.reset(NULL); } - dicom_.reset(new ParsedDicomFile(content)); - dicomSize_ = content.size(); + dicom_.reset(new ParsedDicomFile(buffer_)); + dicomSize_ = buffer_.size(); } assert(accessor_.get() != NULL || @@ -1394,6 +1393,18 @@ } } + const std::string& ServerContext::DicomCacheLocker::GetBuffer() + { + if (buffer_.size() > 0) + { + return buffer_; + } + else + { + context_.ReadDicom(buffer_, instancePublicId_); + return buffer_; + } + } void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) { @@ -1845,27 +1856,50 @@ } + + + ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId, unsigned int frameIndex) { + ServerContext::DicomCacheLocker locker(*this, publicId); + std::unique_ptr decoded(DecodeDicomFrame(locker.GetDicom(), locker.GetBuffer().c_str(), locker.GetBuffer().size(), frameIndex)); + + if (decoded.get() == NULL) + { + OrthancConfiguration::ReaderLock configLock; + if (configLock.GetConfiguration().IsWarningEnabled(Warnings_003_DecoderFailure)) + { + LOG(WARNING) << "W003: Unable to decode frame " << frameIndex << " from instance " << publicId; + } + return NULL; + } + + return decoded.release(); + } + + + ImageAccessor* ServerContext::DecodeDicomFrame(const ParsedDicomFile& parsedDicom, + const void* buffer, // actually the buffer that is the source of the ParsedDicomFile + size_t size, + unsigned int frameIndex) + { + std::unique_ptr decoded; + if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { - // Use Orthanc's built-in decoder, using the cache to speed-up - // things on multi-frame images - - std::unique_ptr decoded; + // Use Orthanc's built-in decoder + try { - ServerContext::DicomCacheLocker locker(*this, publicId); - decoded.reset(locker.GetDicom().DecodeFrame(frameIndex)); + decoded.reset(parsedDicom.DecodeFrame(frameIndex)); + if (decoded.get() != NULL) + { + return decoded.release(); + } } catch (OrthancException& e) - { - } - - if (decoded.get() != NULL) - { - return decoded.release(); + { // ignore, we'll try other alternatives } } @@ -1873,14 +1907,9 @@ if (HasPlugins() && GetPlugins().HasCustomImageDecoder()) { - // TODO: Store the raw buffer in the DicomCacheLocker - std::string dicomContent; - ReadDicom(dicomContent, publicId); - - std::unique_ptr decoded; try { - decoded.reset(GetPlugins().Decode(dicomContent.c_str(), dicomContent.size(), frameIndex)); + decoded.reset(GetPlugins().Decode(buffer, size, frameIndex)); } catch (OrthancException& e) { @@ -1900,69 +1929,50 @@ if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { - ServerContext::DicomCacheLocker locker(*this, publicId); - return locker.GetDicom().DecodeFrame(frameIndex); + try + { + decoded.reset(parsedDicom.DecodeFrame(frameIndex)); + if (decoded.get() != NULL) + { + return decoded.release(); + } + } + catch (OrthancException& e) + { + LOG(INFO) << e.GetDetails(); + } } - else + + if (HasPlugins() && GetPlugins().HasCustomTranscoder()) { - return NULL; // Built-in decoder is disabled + LOG(INFO) << "The plugins and built-in image decoders failed to decode a frame, " + << "trying to transcode the file to LittleEndianExplicit using the plugins."; + DicomImage explicitTemporaryImage; + DicomImage source; + std::set allowedSyntaxes; + + source.SetExternalBuffer(buffer, size); + allowedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); + + if (Transcode(explicitTemporaryImage, source, allowedSyntaxes, true)) + { + std::unique_ptr file(explicitTemporaryImage.ReleaseAsParsedDicomFile()); + return file->DecodeFrame(frameIndex); + } } + + return NULL; } ImageAccessor* ServerContext::DecodeDicomFrame(const DicomInstanceToStore& dicom, unsigned int frameIndex) { - if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) - { - std::unique_ptr decoded; - try - { - decoded.reset(dicom.DecodeFrame(frameIndex)); - } - catch (OrthancException& e) - { - } - - if (decoded.get() != NULL) - { - return decoded.release(); - } - } - -#if ORTHANC_ENABLE_PLUGINS == 1 - if (HasPlugins() && - GetPlugins().HasCustomImageDecoder()) - { - std::unique_ptr decoded; - try - { - decoded.reset(GetPlugins().Decode(dicom.GetBufferData(), dicom.GetBufferSize(), frameIndex)); - } - catch (OrthancException& e) - { - } - - if (decoded.get() != NULL) - { - return decoded.release(); - } - else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) - { - LOG(INFO) << "The installed image decoding plugins cannot handle an image, " - << "fallback to the built-in DCMTK decoder"; - } - } -#endif - - if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) - { - return dicom.DecodeFrame(frameIndex); - } - else - { - return NULL; - } + return DecodeDicomFrame(dicom.GetParsedDicomFile(), + dicom.GetBufferData(), + dicom.GetBufferSize(), + frameIndex); + } @@ -1970,8 +1980,8 @@ size_t size, unsigned int frameIndex) { - std::unique_ptr instance(DicomInstanceToStore::CreateFromBuffer(dicom, size)); - return DecodeDicomFrame(*instance, frameIndex); + std::unique_ptr instance(new ParsedDicomFile(dicom, size)); + return DecodeDicomFrame(*instance, dicom, size, frameIndex); } diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.h Mon Sep 02 17:17:22 2024 +0200 @@ -305,6 +305,7 @@ std::unique_ptr dicom_; size_t dicomSize_; std::unique_ptr largeDicomLocker_; + std::string buffer_; public: DicomCacheLocker(ServerContext& context, @@ -313,6 +314,8 @@ ~DicomCacheLocker(); ParsedDicomFile& GetDicom() const; + + const std::string& GetBuffer(); }; ServerContext(IDatabaseWrapper& database, @@ -560,7 +563,12 @@ ImageAccessor* DecodeDicomFrame(const void* dicom, size_t size, unsigned int frameIndex); - + + ImageAccessor* DecodeDicomFrame(const ParsedDicomFile& parsedDicom, + const void* buffer, // actually the buffer that is the source of the ParsedDicomFile + size_t size, + unsigned int frameIndex); + void StoreWithTranscoding(std::string& sopClassUid, std::string& sopInstanceUid, DicomStoreUserConnection& connection, diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/Sources/ServerEnumerations.h --- a/OrthancServer/Sources/ServerEnumerations.h Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.h Mon Sep 02 17:17:22 2024 +0200 @@ -206,6 +206,7 @@ Warnings_None, Warnings_001_TagsBeingReadFromStorage, Warnings_002_InconsistentDicomTagsInDb, + Warnings_003_DecoderFailure, // new in Orthanc 1.12.5 }; diff -r 95a3802ad133 -r 796cb17db15c OrthancServer/UnitTestsSources/VersionsTests.cpp --- a/OrthancServer/UnitTestsSources/VersionsTests.cpp Fri Jul 19 14:02:22 2024 +0200 +++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp Mon Sep 02 17:17:22 2024 +0200 @@ -120,7 +120,7 @@ TEST(Versions, CurlStatic) { curl_version_info_data* v = curl_version_info(CURLVERSION_NOW); - ASSERT_STREQ("8.5.0", v->version); + ASSERT_STREQ("8.9.0", v->version); } TEST(Versions, PngStatic) diff -r 95a3802ad133 -r 796cb17db15c TODO --- a/TODO Fri Jul 19 14:02:22 2024 +0200 +++ b/TODO Mon Sep 02 17:17:22 2024 +0200 @@ -29,11 +29,6 @@ * (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. -* Provide more flexibility wrt Dicom TLS ciphers and TLS version: - https://groups.google.com/g/orthanc-users/c/X4IhmXCSr7I/m/EirawAFcBwAJ - Can maybe be achieved by adding a configuration to select the TLS Security Profile: - https://github.com/DCMTK/dcmtk/blob/master/dcmtls/include/dcmtk/dcmtls/tlsciphr.h#L83 - (e.g: TSP_Profile_BCP195_ND instead of TSP_Profile_BCP195) * 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! @@ -41,6 +36,7 @@ https://discourse.orthanc-server.org/t/performance-issue-when-adding-a-lot-of-jobs-in-the-queue/3915/2 Note: that might also be the right time to have a central jobs registry when working with multiple Orthanc instances on the same DB. + Note: the json serialization of a job "content" can be very large -> compress it before saving it to DB ? * Right now, some Stable events never occurs (e.g. when Orthanc is restarted before the event is triggered). Since these events are used to e.g. generate dicom-web cache (or update it !), we should try to make sure these events always happen. @@ -71,7 +67,7 @@ * Write a getting started guide (step by step) for each platform to replace https://orthanc.uclouvain.be/book/users/cookbook.html : - Ubuntu/Debian - - Windows + - Windows (done) - OSX - Docker on Linux Each step by step guide should contain: @@ -153,7 +149,7 @@ https://groups.google.com/g/orthanc-users/c/hsZ1jng5rIg/m/8xZL2C1VBgAJ * add an "AutoDeleteIfSuccessful": false option when creating jobs https://discourse.orthanc-server.org/t/job-history-combined-with-auto-forwarding/3729/10 -* Allow skiping automatic conversion of color-space in transcoding/decoding. +* Allow skipping automatic conversion of color-space in transcoding/decoding. The patch that was initialy provided was breaking the IngestTranscoding. This might require a DCMTK decoding plugin ? https://discourse.orthanc-server.org/t/orthanc-convert-ybr-to-rgb-but-does-not-change-metadata/3533/9