# HG changeset patch # User Alain Mazy # Date 1724938902 -7200 # Node ID 9d6167ddcb35e8aa6ba259fd5b484206a91f2c00 # Parent e63538a6d9deeeb7cdf90af91af5e1be9852d024# Parent 8bb3f2fca242cfe64ef323d2c0cd94adcbf4926b merged default -> find-refactoring-clean diff -r e63538a6d9de -r 9d6167ddcb35 NEWS --- a/NEWS Fri Jul 19 14:03:09 2024 +0200 +++ b/NEWS Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Resources/CMake/Compiler.cmake --- a/OrthancFramework/Resources/CMake/Compiler.cmake Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/Resources/CMake/Compiler.cmake Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake --- a/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Resources/Patches/curl-8.5.0.patch --- a/OrthancFramework/Resources/Patches/curl-8.5.0.patch Fri Jul 19 14:03:09 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 e63538a6d9de -r 9d6167ddcb35 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 Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Resources/Patches/protobuf-3.5.1.patch --- a/OrthancFramework/Resources/Patches/protobuf-3.5.1.patch Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/Resources/Patches/protobuf-3.5.1.patch Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp --- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp --- a/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/Sources/FileStorage/FilesystemStorage.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancFramework/UnitTestsSources/FileStorageTests.cpp --- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/OrthancExplorer/explorer.js --- a/OrthancServer/OrthancExplorer/explorer.js Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/OrthancExplorer/explorer.js Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Resources/Configuration.json Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/Database/FindResponse.cpp --- a/OrthancServer/Sources/Database/FindResponse.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/Database/FindResponse.cpp Thu Aug 29 15:41:42 2024 +0200 @@ -180,7 +180,7 @@ { std::set s; s.insert(value); - metadataValues_[metadata] = new std::set(s); + metadataValues_[metadata] = new std::set(s); } else { @@ -216,7 +216,7 @@ { std::set s; s.insert(value); - mainDicomTagValues_[tag] = new std::set(s); + mainDicomTagValues_[tag] = new std::set(s); } else { diff -r e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/OrthancConfiguration.cpp --- a/OrthancServer/Sources/OrthancConfiguration.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/OrthancConfiguration.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/ServerContext.cpp --- a/OrthancServer/Sources/ServerContext.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Thu Aug 29 15:41:42 2024 +0200 @@ -1341,18 +1341,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 || @@ -1388,6 +1387,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) { @@ -1657,27 +1668,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 + // Use Orthanc's built-in decoder - std::unique_ptr decoded; 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 } } @@ -1685,14 +1719,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) { @@ -1712,69 +1741,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(); - } - } + return DecodeDicomFrame(dicom.GetParsedDicomFile(), + dicom.GetBufferData(), + dicom.GetBufferSize(), + frameIndex); -#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; - } } @@ -1782,8 +1792,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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/ServerContext.h --- a/OrthancServer/Sources/ServerContext.h Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/ServerContext.h Thu Aug 29 15:41:42 2024 +0200 @@ -286,6 +286,7 @@ std::unique_ptr dicom_; size_t dicomSize_; std::unique_ptr largeDicomLocker_; + std::string buffer_; public: DicomCacheLocker(ServerContext& context, @@ -294,6 +295,8 @@ ~DicomCacheLocker(); ParsedDicomFile& GetDicom() const; + + const std::string& GetBuffer(); }; ServerContext(IDatabaseWrapper& database, @@ -524,7 +527,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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/Sources/ServerEnumerations.h --- a/OrthancServer/Sources/ServerEnumerations.h Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/Sources/ServerEnumerations.h Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 OrthancServer/UnitTestsSources/VersionsTests.cpp --- a/OrthancServer/UnitTestsSources/VersionsTests.cpp Fri Jul 19 14:03:09 2024 +0200 +++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp Thu Aug 29 15:41:42 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 e63538a6d9de -r 9d6167ddcb35 TODO --- a/TODO Fri Jul 19 14:03:09 2024 +0200 +++ b/TODO Thu Aug 29 15:41:42 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