Mercurial > hg > orthanc
changeset 4810:7afbb54bd028
merge storage-cache
author | Alain Mazy <am@osimis.io> |
---|---|
date | Tue, 23 Nov 2021 09:22:11 +0100 |
parents | 2ca4213fb50a (diff) 96ab170294fd (current diff) |
children | 064d86287630 cd6dc99e0470 |
files | NEWS OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerJobs/ArchiveJob.cpp OrthancServer/Sources/ServerJobs/ArchiveJob.h OrthancServer/Sources/main.cpp |
diffstat | 47 files changed, 411 insertions(+), 78 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Thu Oct 07 13:45:36 2021 +0200 +++ b/NEWS Tue Nov 23 09:22:11 2021 +0100 @@ -17,6 +17,30 @@ ----------- * Fix handling of option "DeidentifyLogs", notably for tags (0010,0010) and (0010,0020) +* New configuration options: + - "DicomThreadsCount" to set the number of threads in the embedded DICOM server +* Fix instances accumulating in DB while their attachments were not stored because of + MaximumStorageSize limit reached with a single patient in DB. + +REST API +-------- + +* API version upgraded to 16 +* If an image can not be decoded, ../preview and ../rendered routes are now returning + unsupported.png only if the ?returnUnsupportedImage option is specified; otherwise, + it raises a 415 error code. +* Archive jobs response now contains a header Content-Disposition:filename='archive.zip' + +Lua +--- + +* New "ReceivedCStoreInstanceFilter" Lua callback to filter instances received + through C-Store and return a specific C-Store status code. + +Plugins +------- + +* New function in the SDK: OrthancPluginRegisterIncomingCStoreInstanceFilter() Version 1.9.7 (2021-08-31)
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake Tue Nov 23 09:22:11 2021 +0100 @@ -37,7 +37,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 "15") +set(ORTHANC_API_VERSION "16") #####################################################################
--- a/OrthancFramework/Sources/Compression/ZipReader.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/Compression/ZipReader.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -353,7 +353,7 @@ } else { - throw OrthancException(ErrorCode_BadFileFormat); + throw OrthancException(ErrorCode_BadFileFormat, "Invalid file or unsupported compression method (e.g. Deflate64)"); } }
--- a/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -93,6 +93,7 @@ port_(104), continue_(false), associationTimeout_(30), + threadsCount_(4), modalities_(NULL), findRequestHandlerFactory_(NULL), moveRequestHandlerFactory_(NULL), @@ -424,7 +425,10 @@ #endif continue_ = true; - pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? + + CLOG(INFO, DICOM) << "The embedded DICOM server will use " << threadsCount_ << " threads"; + + pimpl_->workers_.reset(new RunnableWorkersPool(threadsCount_)); pimpl_->thread_ = boost::thread(ServerThread, this, maximumPduLength_, useDicomTls_); } @@ -588,4 +592,16 @@ { return remoteCertificateRequired_; } + + void DicomServer::SetThreadsCount(unsigned int threads) + { + if (threads == 0) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + Stop(); + threadsCount_ = threads; + } + }
--- a/OrthancFramework/Sources/DicomNetworking/DicomServer.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.h Tue Nov 23 09:22:11 2021 +0100 @@ -72,6 +72,7 @@ uint16_t port_; bool continue_; uint32_t associationTimeout_; + unsigned int threadsCount_; IRemoteModalities* modalities_; IFindRequestHandlerFactory* findRequestHandlerFactory_; IMoveRequestHandlerFactory* moveRequestHandlerFactory_; @@ -89,6 +90,7 @@ unsigned int maximumPduLength_; bool remoteCertificateRequired_; // New in 1.9.3 + static void ServerThread(DicomServer* server, unsigned int maximumPduLength, bool useDicomTls); @@ -163,5 +165,8 @@ void SetRemoteCertificateRequired(bool required); bool IsRemoteCertificateRequired() const; + + void SetThreadsCount(unsigned int threadsCount); + }; }
--- a/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h Tue Nov 23 09:22:11 2021 +0100 @@ -39,9 +39,9 @@ { } - virtual void Handle(DcmDataset& dicom, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; + virtual uint16_t Handle(DcmDataset& dicom, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; }; }
--- a/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -161,14 +161,14 @@ // which SOP class and SOP instance ? #if DCMTK_VERSION_NUMBER >= 364 - if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sizeof(sopClass), - sopInstance, sizeof(sopInstance), /*opt_correctUIDPadding*/ OFFalse)) + if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sizeof(sopClass), + sopInstance, sizeof(sopInstance), /*opt_correctUIDPadding*/ OFFalse)) #else if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) #endif { - //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); - rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; + //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); + rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; } else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) { @@ -182,7 +182,7 @@ { try { - cbdata->handler->Handle(**imageDataSet, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); + rsp->DimseStatus = cbdata->handler->Handle(**imageDataSet, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); } catch (OrthancException& e) {
--- a/OrthancFramework/Sources/JobsEngine/IJob.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/IJob.h Tue Nov 23 09:22:11 2021 +0100 @@ -59,6 +59,7 @@ // "success" state virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) = 0; }; }
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -650,6 +650,7 @@ bool JobsRegistry::GetJobOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& job, const std::string& key) { @@ -668,7 +669,7 @@ if (handler.GetState() == JobState_Success) { - return handler.GetJob().GetOutput(output, mime, key); + return handler.GetJob().GetOutput(output, mime, filename, key); } else {
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Tue Nov 23 09:22:11 2021 +0100 @@ -148,6 +148,7 @@ bool GetJobOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& job, const std::string& key);
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -448,6 +448,7 @@ bool SequenceOfOperationsJob::GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) { return false;
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Tue Nov 23 09:22:11 2021 +0100 @@ -125,6 +125,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE; void AwakeTrailingSleep();
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -270,6 +270,7 @@ bool SetOfCommandsJob::GetOutput(std::string &output, MimeType &mime, + std::string& filename, const std::string &key) { return false;
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Tue Nov 23 09:22:11 2021 +0100 @@ -104,6 +104,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE; }; }
--- a/OrthancFramework/Sources/Lua/LuaFunctionCall.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/Lua/LuaFunctionCall.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -146,6 +146,20 @@ } } + void LuaFunctionCall::ExecuteToInt(int& result) + { + ExecuteInternal(1); + + int top = lua_gettop(context_.lua_); + if (lua_isnumber(context_.lua_, top)) + { + result = static_cast<int>(lua_tointeger(context_.lua_, top)); + } + else + { + throw OrthancException(ErrorCode_LuaReturnsNoString); + } + } void LuaFunctionCall::PushStringMap(const std::map<std::string, std::string>& value) {
--- a/OrthancFramework/Sources/Lua/LuaFunctionCall.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/Lua/LuaFunctionCall.h Tue Nov 23 09:22:11 2021 +0100 @@ -78,6 +78,8 @@ void ExecuteToString(std::string& result); + void ExecuteToInt(int& result); + #if ORTHANC_ENABLE_DCMTK == 1 void ExecuteToDicom(DicomMap& target); #endif
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/RestApi/RestApiOutput.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -214,4 +214,9 @@ // empty string SetCookie(name, "", 1); } + + void RestApiOutput::SetContentFilename(const char* filename) + { + output_.SetContentFilename(filename); + } }
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/Sources/RestApi/RestApiOutput.h Tue Nov 23 09:22:11 2021 +0100 @@ -77,6 +77,8 @@ size_t length, MimeType contentType); + void SetContentFilename(const char* filename); + void SignalError(HttpStatus status); void SignalError(HttpStatus status,
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -125,6 +125,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE { return false;
--- a/OrthancServer/OrthancExplorer/explorer.js Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/OrthancExplorer/explorer.js Tue Nov 23 09:22:11 2021 +0100 @@ -1113,7 +1113,7 @@ if (frames.length == 1) { // Viewing a single-frame image - jQuery.slimbox('../instances/' + pageData.uuid + '/preview', '', { + jQuery.slimbox('../instances/' + pageData.uuid + '/preview?returnUnsupportedImage', '', { overlayFadeDuration : 1, resizeDuration : 1, imageFadeDuration : 1 @@ -1125,7 +1125,7 @@ images = []; for (var i = 0; i < frames.length; i++) { - images.push([ '../instances/' + pageData.uuid + '/frames/' + i + '/preview' ]); + images.push([ '../instances/' + pageData.uuid + '/frames/' + i + '/preview?returnUnsupportedImage' ]); } jQuery.slimbox(images, 0, { @@ -1155,7 +1155,7 @@ images = []; for (var i = 0; i < instances.length; i++) { - images.push([ '../instances/' + instances[i].ID + '/preview', + images.push([ '../instances/' + instances[i].ID + '/preview?returnUnsupportedImage', (i + 1).toString() + '/' + instances.length.toString() ]) }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -79,6 +79,7 @@ #include <boost/regex.hpp> #include <dcmtk/dcmdata/dcdict.h> #include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmnet/dimse.h> #define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc API is necessary" @@ -1165,6 +1166,7 @@ typedef std::list<OrthancPluginIncomingHttpRequestFilter> IncomingHttpRequestFilters; typedef std::list<OrthancPluginIncomingHttpRequestFilter2> IncomingHttpRequestFilters2; typedef std::list<OrthancPluginIncomingDicomInstanceFilter> IncomingDicomInstanceFilters; + typedef std::list<OrthancPluginIncomingCStoreInstanceFilter> IncomingCStoreInstanceFilters; typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks; typedef std::list<OrthancPluginTranscoderCallback> TranscoderCallbacks; typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers; @@ -1187,6 +1189,7 @@ IncomingHttpRequestFilters incomingHttpRequestFilters_; IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; IncomingDicomInstanceFilters incomingDicomInstanceFilters_; + IncomingCStoreInstanceFilters incomingCStoreInstanceFilters_; // New in Orthanc 1.9.8 RefreshMetricsCallbacks refreshMetricsCallbacks_; StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_; std::unique_ptr<StorageAreaFactory> storageArea_; @@ -2261,7 +2264,36 @@ return true; } - + + + uint16_t OrthancPlugins::FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + DicomInstanceFromCallback wrapped(instance); + + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + + for (PImpl::IncomingCStoreInstanceFilters::const_iterator + filter = pimpl_->incomingCStoreInstanceFilters_.begin(); + filter != pimpl_->incomingCStoreInstanceFilters_.end(); ++filter) + { + int32_t filterResult = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped)); + + if (filterResult >= 0 && filterResult <= 0xFFFF) + { + return static_cast<uint16_t>(filterResult); + } + else + { + // The callback is only allowed to answer uint16_t + throw OrthancException(ErrorCode_Plugin); + } + } + + return STATUS_Success; + } + + void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resource) @@ -2479,6 +2511,16 @@ } + void OrthancPlugins::RegisterIncomingCStoreInstanceFilter(const void* parameters) + { + const _OrthancPluginIncomingCStoreInstanceFilter& p = + *reinterpret_cast<const _OrthancPluginIncomingCStoreInstanceFilter*>(parameters); + + CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming C-Store DICOM instances"; + pimpl_->incomingCStoreInstanceFilters_.push_back(p.callback); + } + + void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters) { const _OrthancPluginRegisterRefreshMetricsCallback& p = @@ -4957,6 +4999,10 @@ RegisterIncomingDicomInstanceFilter(parameters); return true; + case _OrthancPluginService_RegisterIncomingCStoreInstanceFilter: + RegisterIncomingCStoreInstanceFilter(parameters); + return true; + case _OrthancPluginService_RegisterRefreshMetricsCallback: RegisterRefreshMetricsCallback(parameters); return true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Tue Nov 23 09:22:11 2021 +0100 @@ -133,6 +133,8 @@ void RegisterIncomingDicomInstanceFilter(const void* parameters); + void RegisterIncomingCStoreInstanceFilter(const void* parameters); + void RegisterRefreshMetricsCallback(const void* parameters); void RegisterStorageCommitmentScpCallback(const void* parameters); @@ -279,6 +281,9 @@ virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, const Json::Value& simplified) ORTHANC_OVERRIDE; + virtual uint16_t FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) ORTHANC_OVERRIDE; + bool HasStorageArea() const; IStorageArea* CreateStorageArea(); // To be freed after use
--- a/OrthancServer/Plugins/Engine/PluginsJob.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsJob.h Tue Nov 23 09:22:11 2021 +0100 @@ -75,6 +75,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE { // TODO
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Tue Nov 23 09:22:11 2021 +0100 @@ -461,7 +461,8 @@ _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, _OrthancPluginService_RegisterTranscoderCallback = 1015, /* New in Orthanc 1.7.0 */ _OrthancPluginService_RegisterStorageArea2 = 1016, /* New in Orthanc 1.9.0 */ - + _OrthancPluginService_RegisterIncomingCStoreInstanceFilter = 1017, /* New in Orthanc 1.9.8 */ + /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ @@ -7764,6 +7765,63 @@ /** + * @brief Callback to filter incoming DICOM instances received by + * Orthanc through C-Store. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (through DICOM protocol), + * and that answers whether this DICOM instance should be accepted + * or discarded by Orthanc. If the instance is discarded, the callback + * can specify the C-Store error code. + * + * Note that the metadata information is not available + * (i.e. GetInstanceMetadata() should not be used on "instance"). + * + * @param instance The received DICOM instance. + * @return 0 to accept the instance, any valid C-Store error code + * to reject the instance, -1 if error. + * @ingroup Callback + **/ + typedef int32_t (*OrthancPluginIncomingCStoreInstanceFilter) ( + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingCStoreInstanceFilter callback; + } _OrthancPluginIncomingCStoreInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instances + * received by Orthanc through C-Store. + * + * + * @warning Your callback function will be called synchronously with + * the core of Orthanc. This implies that deadlocks might emerge if + * you call other core primitives of Orthanc in your callback (such + * deadlocks are particular visible in the presence of other plugins + * or Lua scripts). It is thus strongly advised to avoid any call to + * the REST API of Orthanc in the callback. If you have to call + * other primitives of Orthanc, you should make these calls in a + * separate thread, passing the pending events to be processed + * through a message queue. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingCStoreInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingCStoreInstanceFilter callback) + { + _OrthancPluginIncomingCStoreInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingCStoreInstanceFilter, ¶ms); + } + + /** * @brief Get the transfer syntax of a DICOM file. * * This function returns a pointer to a newly created string that
--- a/OrthancServer/Resources/Configuration.json Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Resources/Configuration.json Tue Nov 23 09:22:11 2021 +0100 @@ -424,7 +424,16 @@ // (1.2.840.10008.1.2.1). This parameter can possibly correspond to // a compressed transfer syntax. (new in Orthanc 1.9.0) "DicomScuPreferredTransferSyntax" : "1.2.840.10008.1.2.1", - + + // Number of threads that are used by the embedded DICOM server. + // This defines the number of concurrent DICOM operations that can + // be run. Note: this is not limiting the number of concurrent + // connections. With a single thread, if a C-Find is received + // during e.g the transcoding of an incoming C-Store, it will + // have to wait until the end of the C-Store before being processed. + // (new in Orthanc 1.9.8, before this version, the value was fixed to 4) + "DicomThreadsCount" : 4, + // The list of the known Orthanc peers. This option is ignored if // "OrthancPeersInDatabase" is set to "true", in which case you must // use the REST API to define Orthanc peers.
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -2810,7 +2810,7 @@ if (!ok) { - throw OrthancException(ErrorCode_FullStorage); + throw OrthancException(ErrorCode_FullStorage, "Cannot recycle more patients"); } LOG(TRACE) << "Recycling one patient"; @@ -3264,11 +3264,18 @@ { if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize) { - throw; + throw; // the transaction has failed -> do not commit the current transaction (and retry) } else { - LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; + LOG(ERROR) << "EXCEPTION [" << e.What() << " - " << e.GetDetails() << "]"; + + if (e.GetErrorCode() == ErrorCode_FullStorage) + { + throw; // do not commit the current transaction + } + + // this is an expected failure, exit normaly and commit the current transaction storeStatus_ = StoreStatus_Failure; } }
--- a/OrthancServer/Sources/IServerListener.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/IServerListener.h Tue Nov 23 09:22:11 2021 +0100 @@ -55,5 +55,9 @@ virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, const Json::Value& simplified) = 0; + + virtual uint16_t FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) = 0; + }; }
--- a/OrthancServer/Sources/LuaScripting.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/LuaScripting.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -43,6 +43,8 @@ #include "../../OrthancFramework/Sources/Logging.h" #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h" +#include <dcmtk/dcmnet/dimse.h> + #include <OrthancServerResources.h> @@ -945,6 +947,41 @@ return true; } + uint16_t LuaScripting::FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + static const char* NAME = "ReceivedCStoreInstanceFilter"; + + boost::recursive_mutex::scoped_lock lock(mutex_); + + if (lua_.IsExistingFunction(NAME)) + { + LuaFunctionCall call(lua_, NAME); + call.PushJson(simplified); + + Json::Value origin; + instance.GetOrigin().Format(origin); + call.PushJson(origin); + + Json::Value info = Json::objectValue; + info["HasPixelData"] = instance.HasPixelData(); + + DicomTransferSyntax s; + if (instance.LookupTransferSyntax(s)) + { + info["TransferSyntaxUID"] = GetTransferSyntaxUid(s); + } + + call.PushJson(info); + + int result; + call.ExecuteToInt(result); + return static_cast<uint16_t>(result); + } + + return STATUS_Success; + } + void LuaScripting::Execute(const std::string& command) {
--- a/OrthancServer/Sources/LuaScripting.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/LuaScripting.h Tue Nov 23 09:22:11 2021 +0100 @@ -129,6 +129,9 @@ bool FilterIncomingInstance(const DicomInstanceToStore& instance, const Json::Value& simplifiedTags); + uint16_t FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified); + void Execute(const std::string& command); void SignalJobSubmitted(const std::string& jobId);
--- a/OrthancServer/Sources/OrthancGetRequestHandler.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancGetRequestHandler.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -497,7 +497,7 @@ { MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms"); - CLOG(WARNING, DICOM) << "C-GET-SCU request received from AET \"" << originatorAet << "\""; + CLOG(INFO, DICOM) << "C-GET-SCU request received from AET \"" << originatorAet << "\""; { DicomArray query(input);
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -334,7 +334,7 @@ { MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_move_scp_duration_ms"); - CLOG(WARNING, DICOM) << "Move-SCU request received for AET \"" << targetAet << "\""; + CLOG(INFO, DICOM) << "Move-SCU request received for AET \"" << targetAet << "\""; { DicomArray query(input);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -554,16 +554,16 @@ toStore->SetOrigin(DicomInstanceOrigin::FromRest(call)); ServerContext& context = OrthancRestApi::GetContext(call); - StoreStatus status = context.Store(id, *toStore, StoreInstanceMode_Default); + ServerContext::StoreResult result = context.Store(id, *toStore, StoreInstanceMode_Default); - if (status == StoreStatus_Failure) + if (result.GetStatus() == StoreStatus_Failure) { throw OrthancException(ErrorCode_CannotStoreInstance); } if (sendAnswer) { - OrthancRestApi::GetApi(call).AnswerStoredInstance(call, *toStore, status, id); + OrthancRestApi::GetApi(call).AnswerStoredInstance(call, *toStore, result.GetStatus(), id); } }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -199,10 +199,10 @@ try { - StoreStatus status = context.Store(publicId, *toStore, StoreInstanceMode_Default); + ServerContext::StoreResult result = context.Store(publicId, *toStore, StoreInstanceMode_Default); Json::Value info; - SetupResourceAnswer(info, *toStore, status, publicId); + SetupResourceAnswer(info, *toStore, result.GetStatus(), publicId); answer.append(info); } catch (OrthancException& e) @@ -252,9 +252,9 @@ toStore->SetOrigin(DicomInstanceOrigin::FromRest(call)); std::string publicId; - StoreStatus status = context.Store(publicId, *toStore, StoreInstanceMode_Default); + ServerContext::StoreResult result = context.Store(publicId, *toStore, StoreInstanceMode_Default); - OrthancRestApi::GetApi(call).AnswerStoredInstance(call, *toStore, status, publicId); + OrthancRestApi::GetApi(call).AnswerStoredInstance(call, *toStore, result.GetStatus(), publicId); } }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -755,6 +755,7 @@ .SetTag("Instances") .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") .SetHttpGetArgument("quality", RestApiCallDocumentation::Type_Number, "Quality for JPEG images (between 1 and 100, defaults to 90)", false) + .SetHttpGetArgument("returnUnsupportedImage", RestApiCallDocumentation::Type_Boolean, "Returns an unsupported.png placeholder image if unable to provide the image instead of returning a 415 HTTP error (defaults to false)", false) .SetHttpHeader("Accept", "Format of the resulting image. Can be `image/png` (default), `image/jpeg` or `image/x-portable-arbitrarymap`") .AddAnswerType(MimeType_Png, "PNG image") .AddAnswerType(MimeType_Jpeg, "JPEG image") @@ -817,13 +818,20 @@ } else { - std::string root = ""; - for (size_t i = 1; i < call.GetFullUri().size(); i++) + if (call.HasArgument("returnUnsupportedImage")) { - root += "../"; + std::string root = ""; + for (size_t i = 1; i < call.GetFullUri().size(); i++) + { + root += "../"; + } + + call.GetOutput().Redirect(root + "app/images/unsupported.png"); } - - call.GetOutput().Redirect(root + "app/images/unsupported.png"); + else + { + call.GetOutput().SignalError(HttpStatus_415_UnsupportedMediaType); + } } return; }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -696,10 +696,16 @@ std::string value; MimeType mime; + std::string filename; if (OrthancRestApi::GetContext(call).GetJobsEngine(). - GetRegistry().GetJobOutput(value, mime, job, key)) + GetRegistry().GetJobOutput(value, mime, filename, job, key)) { + if (!filename.empty()) + { + call.GetOutput().SetContentFilename(filename.c_str()); + } + call.GetOutput().AnswerBuffer(value, mime); } else
--- a/OrthancServer/Sources/OrthancWebDav.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -1313,9 +1313,9 @@ try { std::string publicId; - StoreStatus status = context_.Store(publicId, *instance, StoreInstanceMode_Default); - if (status == StoreStatus_Success || - status == StoreStatus_AlreadyStored) + ServerContext::StoreResult result = context_.Store(publicId, *instance, StoreInstanceMode_Default); + if (result.GetStatus() == StoreStatus_Success || + result.GetStatus() == StoreStatus_AlreadyStored) { LOG(INFO) << "Successfully imported DICOM instance from WebDAV: " << path << " (Orthanc ID: " << publicId << ")";
--- a/OrthancServer/Sources/ServerContext.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -57,6 +57,7 @@ #include "StorageCommitmentReports.h" #include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmnet/dimse.h> static size_t DICOM_CACHE_SIZE = 128 * 1024 * 1024; // 128 MB @@ -100,6 +101,13 @@ transferSyntax != DicomTransferSyntax_XML); } + + ServerContext::StoreResult::StoreResult() : + status_(StoreStatus_Failure), + cstoreStatusCode_(0) + { + } + void ServerContext::ChangeThread(ServerContext* that, unsigned int sleepDelay) @@ -489,9 +497,9 @@ } - StoreStatus ServerContext::StoreAfterTranscoding(std::string& resultPublicId, - DicomInstanceToStore& dicom, - StoreInstanceMode mode) + ServerContext::StoreResult ServerContext::StoreAfterTranscoding(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode) { bool overwrite; switch (mode) @@ -538,7 +546,7 @@ Toolbox::SimplifyDicomAsJson(simplifiedTags, dicomAsJson, DicomToJsonFormat_Human); // Test if the instance must be filtered out - bool accepted = true; + StoreResult result; { boost::shared_lock<boost::shared_mutex> lock(listenersMutex_); @@ -549,9 +557,22 @@ { if (!it->GetListener().FilterIncomingInstance(dicom, simplifiedTags)) { - accepted = false; + result.SetStatus(StoreStatus_FilteredOut); + result.SetCStoreStatusCode(STATUS_Success); // to keep backward compatibility, we still return 'success' break; } + + if (dicom.GetOrigin().GetRequestOrigin() == Orthanc::RequestOrigin_DicomProtocol) + { + uint16_t filterResult = it->GetListener().FilterIncomingCStoreInstance(dicom, simplifiedTags); + if (filterResult != 0x0000) + { + result.SetStatus(StoreStatus_FilteredOut); + result.SetCStoreStatusCode(filterResult); + break; + } + } + } catch (OrthancException& e) { @@ -563,10 +584,10 @@ } } - if (!accepted) + if (result.GetStatus() == StoreStatus_FilteredOut) { LOG(INFO) << "An incoming instance has been discarded by the filter"; - return StoreStatus_FilteredOut; + return result; } // Remove the file from the DicomCache (useful if @@ -595,9 +616,9 @@ typedef std::map<MetadataType, std::string> InstanceMetadata; InstanceMetadata instanceMetadata; - StoreStatus status = index_.Store( + result.SetStatus(index_.Store( instanceMetadata, summary, attachments, dicom.GetMetadata(), dicom.GetOrigin(), overwrite, - hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset); + hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset)); // Only keep the metadata for the "instance" level dicom.ClearMetadata(); @@ -608,7 +629,7 @@ dicom.AddMetadata(ResourceType_Instance, it->first, it->second); } - if (status != StoreStatus_Success) + if (result.GetStatus() != StoreStatus_Success) { accessor.Remove(dicomInfo); @@ -618,7 +639,7 @@ } } - switch (status) + switch (result.GetStatus()) { case StoreStatus_Success: LOG(INFO) << "New instance stored"; @@ -637,8 +658,8 @@ break; } - if (status == StoreStatus_Success || - status == StoreStatus_AlreadyStored) + if (result.GetStatus() == StoreStatus_Success || + result.GetStatus() == StoreStatus_AlreadyStored) { boost::shared_lock<boost::shared_mutex> lock(listenersMutex_); @@ -657,7 +678,7 @@ } } - return status; + return result; } catch (OrthancException& e) { @@ -671,9 +692,9 @@ } - StoreStatus ServerContext::Store(std::string& resultPublicId, - DicomInstanceToStore& dicom, - StoreInstanceMode mode) + ServerContext::StoreResult ServerContext::Store(std::string& resultPublicId, + DicomInstanceToStore& dicom, + StoreInstanceMode mode) { if (!isIngestTranscoding_) { @@ -733,10 +754,10 @@ std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp)); toStore->SetOrigin(dicom.GetOrigin()); - StoreStatus ok = StoreAfterTranscoding(resultPublicId, *toStore, mode); + StoreResult result = StoreAfterTranscoding(resultPublicId, *toStore, mode); assert(resultPublicId == tmp->GetHasher().HashInstance()); - return ok; + return result; } else {
--- a/OrthancServer/Sources/ServerContext.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerContext.h Tue Nov 23 09:22:11 2021 +0100 @@ -94,6 +94,36 @@ const Json::Value* dicomAsJson) = 0; }; + struct StoreResult + { + private: + StoreStatus status_; + uint16_t cstoreStatusCode_; + // uint16_t httpStatusCode_; // for future use + + public: + StoreResult(); + + void SetStatus(StoreStatus status) + { + status_ = status; + } + + StoreStatus GetStatus() + { + return status_; + } + + void SetCStoreStatusCode(uint16_t statusCode) + { + cstoreStatusCode_ = statusCode; + } + + uint16_t GetCStoreStatusCode() + { + return cstoreStatusCode_; + } + }; private: class LuaServerListener : public IServerListener @@ -124,6 +154,12 @@ { return context_.filterLua_.FilterIncomingInstance(instance, simplified); } + + virtual uint16_t FilterIncomingCStoreInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) ORTHANC_OVERRIDE + { + return context_.filterLua_.FilterIncomingCStoreInstance(instance, simplified); + } }; class ServerListener @@ -233,7 +269,7 @@ bool isUnknownSopClassAccepted_; std::set<DicomTransferSyntax> acceptedTransferSyntaxes_; - StoreStatus StoreAfterTranscoding(std::string& resultPublicId, + StoreResult StoreAfterTranscoding(std::string& resultPublicId, DicomInstanceToStore& dicom, StoreInstanceMode mode); @@ -311,7 +347,7 @@ int64_t oldRevision, const std::string& oldMD5); - StoreStatus Store(std::string& resultPublicId, + StoreResult Store(std::string& resultPublicId, DicomInstanceToStore& dicom, StoreInstanceMode mode);
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -1412,6 +1412,7 @@ bool ArchiveJob::GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) { if (key == "archive" && @@ -1424,6 +1425,7 @@ const DynamicTemporaryFile& f = dynamic_cast<DynamicTemporaryFile&>(accessor.GetItem()); f.GetFile().Read(output); mime = MimeType_Zip; + filename = "archive.zip"; return true; } else
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h Tue Nov 23 09:22:11 2021 +0100 @@ -127,6 +127,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -161,8 +161,8 @@ toStore->SetOrigin(origin_); std::string modifiedInstance; - if (GetContext().Store(modifiedInstance, *toStore, - StoreInstanceMode_Default) != StoreStatus_Success) + ServerContext::StoreResult result = GetContext().Store(modifiedInstance, *toStore, StoreInstanceMode_Default); + if (result.GetStatus() != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false;
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -290,8 +290,8 @@ **/ std::string modifiedInstance; - if (GetContext().Store(modifiedInstance, *toStore, - StoreInstanceMode_Default) != StoreStatus_Success) + ServerContext::StoreResult result = GetContext().Store(modifiedInstance, *toStore, StoreInstanceMode_Default); + if (result.GetStatus() != StoreStatus_Success) { throw OrthancException(ErrorCode_CannotStoreInstance, "Error while storing a modified instance " + instance);
--- a/OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -143,8 +143,8 @@ toStore->SetOrigin(origin_); std::string modifiedInstance; - if (GetContext().Store(modifiedInstance, *toStore, - StoreInstanceMode_Default) != StoreStatus_Success) + ServerContext::StoreResult result = GetContext().Store(modifiedInstance, *toStore, StoreInstanceMode_Default); + if (result.GetStatus() != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false;
--- a/OrthancServer/Sources/main.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/Sources/main.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -84,10 +84,10 @@ } - virtual void Handle(DcmDataset& dicom, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE + virtual uint16_t Handle(DcmDataset& dicom, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE { std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromDcmDataset(dicom)); @@ -97,8 +97,11 @@ (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); std::string id; - context_.Store(id, *toStore, StoreInstanceMode_Default); + ServerContext::StoreResult result = context_.Store(id, *toStore, StoreInstanceMode_Default); + return result.GetCStoreStatusCode(); } + + return STATUS_STORE_Error_CannotUnderstand; } }; @@ -1210,6 +1213,7 @@ dicomServer.SetCalledApplicationEntityTitleCheck(lock.GetConfiguration().GetBooleanParameter("DicomCheckCalledAet", false)); dicomServer.SetAssociationTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScpTimeout", 30)); dicomServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242)); + dicomServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomThreadsCount", 4)); dicomServer.SetApplicationEntityTitle(lock.GetConfiguration().GetOrthancAET()); // Configuration of DICOM TLS for Orthanc SCP (since Orthanc 1.9.0)
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -859,7 +859,8 @@ ASSERT_EQ(id, hasher.HashInstance()); std::string id2; - ASSERT_EQ(StoreStatus_Success, context.Store(id2, *toStore, StoreInstanceMode_Default)); + ServerContext::StoreResult result = context.Store(id2, *toStore, StoreInstanceMode_Default); + ASSERT_EQ(StoreStatus_Success, result.GetStatus()); ASSERT_EQ(id, id2); } @@ -908,8 +909,8 @@ toStore->SetOrigin(DicomInstanceOrigin::FromPlugins()); std::string id2; - ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, - context.Store(id2, *toStore, StoreInstanceMode_Default)); + ServerContext::StoreResult result = context.Store(id2, *toStore, StoreInstanceMode_Default); + ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, result.GetStatus()); ASSERT_EQ(id, id2); } @@ -1008,7 +1009,8 @@ std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(dicom)); dicomSize = toStore->GetBufferSize(); toStore->SetOrigin(DicomInstanceOrigin::FromPlugins()); - ASSERT_EQ(StoreStatus_Success, context.Store(id, *toStore, StoreInstanceMode_Default)); + ServerContext::StoreResult result = context.Store(id, *toStore, StoreInstanceMode_Default); + ASSERT_EQ(StoreStatus_Success, result.GetStatus()); } std::set<FileContentType> attachments;
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Thu Oct 07 13:45:36 2021 +0200 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Tue Nov 23 09:22:11 2021 +0100 @@ -140,6 +140,7 @@ virtual bool GetOutput(std::string& output, MimeType& mime, + std::string& filename, const std::string& key) ORTHANC_OVERRIDE { return false; @@ -537,7 +538,8 @@ std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(dicom)); - return (context_->Store(id, *toStore, StoreInstanceMode_Default) == StoreStatus_Success); + ServerContext::StoreResult result = context_->Store(id, *toStore, StoreInstanceMode_Default); + return (result.GetStatus() == StoreStatus_Success); } }; }
--- a/TODO Thu Oct 07 13:45:36 2021 +0200 +++ b/TODO Tue Nov 23 09:22:11 2021 +0100 @@ -60,6 +60,8 @@ image. The SOPClassUID might be used to identify such secondary captures. * Support "/preview" and "/matlab" for LUT color images +* Try to transcode files if a simple decoding fails: + https://groups.google.com/g/orthanc-users/c/b8168-NkAhA/m/Df3j-CO9CgAJ * Add asynchronous mode in "/modalitities/.../move" for C-MOVE SCU: https://groups.google.com/g/orthanc-users/c/G3_jBy4X4NQ/m/8BanTsdMBQAJ * Ranges of DICOM tags for "Keep" and "Remove" in ".../modify" and ".../anonymize": @@ -125,7 +127,10 @@ useful in ServerContext::DecodeDicomInstance() * DicomMap: create a cache to the main DICOM tags index * Check out rapidjson: https://github.com/miloyip/nativejson-benchmark - +* optimize tools/find with ModalitiesInStudies: + https://groups.google.com/g/orthanc-users/c/aN8nqcRd3jw/m/pmc9ylVeAwAJ. + One solution could be: filter first without ModalitiesInStudies and then + cycle through the responses to filter out with ModalitiesInStudies ======== Database