# HG changeset patch # User Sebastien Jodogne # Date 1720087190 -7200 # Node ID 301212a3fa0898ca3e32320e0f08b7b87612f7e8 # Parent a3c244090f67f473ad5219cda55bcc567537c227# Parent d77292629430ef79d7c44108d2d7e0ec161d42f8 integration mainline->find-refactoring diff -r a3c244090f67 -r 301212a3fa08 NEWS --- a/NEWS Thu Jun 06 13:24:04 2024 +0200 +++ b/NEWS Thu Jul 04 11:59:50 2024 +0200 @@ -4,6 +4,22 @@ * TODO-FIND: complete the list of updated routes: /studies?expand and sibbling routes now also return "Metadata" (if the DB implements 'extended-api-v1') +REST API +-------- + +* Improved parsing of multiple numerical values in DICOM tags. + https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6 + +Maintenance +----------- + +* DICOM TLS: "DicomTlsTrustedCertificates" is not required anymore when issuing + an outgoing SCU connexion when "DicomTlsRemoteCertificateRequired" is set to false. +* Introduced a new thread to update the statistics at regular interval for the + DB plugins that are implementing the UpdateAndGetStatistics function (currently only + PostgreSQL). This avoids very long update times in case you don't call /statistics + for a long period. + Version 1.12.4 (2024-06-05) =========================== diff -r a3c244090f67 -r 301212a3fa08 OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -195,7 +195,7 @@ throw OrthancException(ErrorCode_BadSequenceOfCalls, "DICOM TLS - No path to the local certificate was provided"); } - else if (trustedCertificatesPath_.empty()) + else if (remoteCertificateRequired_ && trustedCertificatesPath_.empty()) { throw OrthancException(ErrorCode_BadSequenceOfCalls, "DICOM TLS - No path to the trusted remote certificates was provided"); diff -r a3c244090f67 -r 301212a3fa08 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -1978,13 +1978,27 @@ case EVR_SL: // signed long { - ok = element.putSint32(boost::lexical_cast(*decoded)).good(); + if (decoded->find('\\') != std::string::npos) + { + ok = element.putString(decoded->c_str()).good(); + } + else + { + ok = element.putSint32(boost::lexical_cast(*decoded)).good(); + } break; } case EVR_SS: // signed short { - ok = element.putSint16(boost::lexical_cast(*decoded)).good(); + if (decoded->find('\\') != std::string::npos) + { + ok = element.putString(decoded->c_str()).good(); + } + else + { + ok = element.putSint16(boost::lexical_cast(*decoded)).good(); + } break; } @@ -2023,14 +2037,28 @@ case EVR_US: // unsigned short { - ok = element.putUint16(boost::lexical_cast(*decoded)).good(); + if (decoded->find('\\') != std::string::npos) + { + ok = element.putString(decoded->c_str()).good(); + } + else + { + ok = element.putUint16(boost::lexical_cast(*decoded)).good(); + } break; } case EVR_FL: // float single-precision case EVR_OF: // other float (requires byte swapping) { - ok = element.putFloat32(boost::lexical_cast(*decoded)).good(); + if (decoded->find('\\') != std::string::npos) + { + ok = element.putString(decoded->c_str()).good(); + } + else + { + ok = element.putFloat32(boost::lexical_cast(*decoded)).good(); + } break; } @@ -2039,7 +2067,14 @@ case EVR_OD: // other double (requires byte-swapping) #endif { - ok = element.putFloat64(boost::lexical_cast(*decoded)).good(); + if (decoded->find('\\') != std::string::npos) + { + ok = element.putString(decoded->c_str()).good(); + } + else + { + ok = element.putFloat64(boost::lexical_cast(*decoded)).good(); + } break; } diff -r a3c244090f67 -r 301212a3fa08 OrthancFramework/Sources/Toolbox.cpp --- a/OrthancFramework/Sources/Toolbox.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancFramework/Sources/Toolbox.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -1885,7 +1885,7 @@ std::string Toolbox::GenerateUuid() { -#ifdef WIN32 +#ifdef _WIN32 UUID uuid; UuidCreate ( &uuid ); diff -r a3c244090f67 -r 301212a3fa08 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -3092,6 +3092,21 @@ } +TEST(ParsedDicomFile, MultipleFloatValue) +{ + // from https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6 + Json::Value v = Json::objectValue; + v["4010,1001"][0]["4010,1004"] = "30\\20\\10"; + std::unique_ptr dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, "")); + ASSERT_TRUE(dicom->HasTag(Orthanc::DicomTag(0x4010, 0x1001))); + + DicomMap m; + ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x4010, 0x1001)), 0)); + ASSERT_EQ(1u, m.GetSize()); + std::string value = m.GetStringValue(DicomTag(0x4010, 0x1004), "", false); + ASSERT_EQ("30\\20\\10", value); +} + TEST(ParsedDicomFile, ImageInformation) { diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Plugins/Engine/PluginsManager.cpp --- a/OrthancServer/Plugins/Engine/PluginsManager.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -37,7 +37,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #define PLUGIN_EXTENSION ".dll" #elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) #define PLUGIN_EXTENSION ".so" diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jul 04 11:59:50 2024 +0200 @@ -111,7 +111,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 # define ORTHANC_PLUGINS_API __declspec(dllexport) #elif __GNUC__ >= 4 # define ORTHANC_PLUGINS_API __attribute__ ((visibility ("default"))) diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Resources/Configuration.json Thu Jul 04 11:59:50 2024 +0200 @@ -311,6 +311,8 @@ // to "true" (resp. "false") corresponds to "--require-peer-cert" // (resp. "--ignore-peer-cert") in the DCMTK command-line // tools. (new in Orthanc 1.9.3) + // Once you set this configuration to true, you must provide a list of + // trusted certificates in DicomTlsTrustedCertificates. "DicomTlsRemoteCertificateRequired" : true, // Sets the minimum accepted TLS protocol version for the DICOM server diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Sources/ServerIndex.cpp --- a/OrthancServer/Sources/ServerIndex.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Sources/ServerIndex.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -266,6 +266,39 @@ } }; + void ServerIndex::UpdateStatisticsThread(ServerIndex* that, + unsigned int threadSleepGranularityMilliseconds) + { + Logging::SetCurrentThreadName("DB-STATS"); + + static const unsigned int SLEEP_SECONDS = 60; + + if (threadSleepGranularityMilliseconds > 1000) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + LOG(INFO) << "Starting the update statistics thread (sleep = " << SLEEP_SECONDS << " seconds)"; + + unsigned int count = 0; + unsigned int countThreshold = (1000 * SLEEP_SECONDS) / threadSleepGranularityMilliseconds; + + while (!that->done_) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(threadSleepGranularityMilliseconds)); + count++; + + if (count >= countThreshold) + { + uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances; + that->GetGlobalStatistics(diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances); + + count = 0; + } + } + + LOG(INFO) << "Stopping the update statistics thread"; + } void ServerIndex::FlushThread(ServerIndex* that, unsigned int threadSleepGranularityMilliseconds) @@ -326,11 +359,20 @@ // execution of Orthanc StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_); + // For some DB engines (like SQLite), make sure we flush the DB to disk at regular interval if (GetDatabaseCapabilities().HasFlushToDisk()) { flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds); } + // For some DB plugins that implements the UpdateAndGetStatistics function, updating + // the statistics can take quite some time if you have not done it for a long time + // -> make sure they are updated at regular interval + if (GetDatabaseCapabilities().HasUpdateAndGetStatistics()) + { + updateStatisticsThread_ = boost::thread(UpdateStatisticsThread, this, threadSleepGranularityMilliseconds); + } + unstableResourcesMonitorThread_ = boost::thread (UnstableResourcesMonitorThread, this, threadSleepGranularityMilliseconds); } @@ -357,6 +399,11 @@ flushThread_.join(); } + if (updateStatisticsThread_.joinable()) + { + updateStatisticsThread_.join(); + } + if (unstableResourcesMonitorThread_.joinable()) { unstableResourcesMonitorThread_.join(); diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Sources/ServerIndex.h --- a/OrthancServer/Sources/ServerIndex.h Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Sources/ServerIndex.h Thu Jul 04 11:59:50 2024 +0200 @@ -42,6 +42,7 @@ bool done_; boost::mutex monitoringMutex_; boost::thread flushThread_; + boost::thread updateStatisticsThread_; boost::thread unstableResourcesMonitorThread_; LeastRecentlyUsedIndex, UnstableResourcePayload> unstableResources_; @@ -53,6 +54,9 @@ static void FlushThread(ServerIndex* that, unsigned int threadSleep); + static void UpdateStatisticsThread(ServerIndex* that, + unsigned int threadSleep); + static void UnstableResourcesMonitorThread(ServerIndex* that, unsigned int threadSleep); diff -r a3c244090f67 -r 301212a3fa08 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Thu Jun 06 13:24:04 2024 +0200 +++ b/OrthancServer/Sources/main.cpp Thu Jul 04 11:59:50 2024 +0200 @@ -1187,7 +1187,7 @@ else { context.SetRestApiWriteToFileSystemEnabled(false); - LOG(WARNING) << "REST API cannot write to the file system bacause the \"RestApiWriteToFileSystemEnabled\" configuration is set to false. The URI /instances/../export is disabled. This is the most secure configuration."; + LOG(WARNING) << "REST API cannot write to the file system because the \"RestApiWriteToFileSystemEnabled\" configuration is set to false. The URI /instances/../export is disabled. This is the most secure configuration."; } if (lock.GetConfiguration().GetBooleanParameter("WebDavEnabled", true))