# HG changeset patch # User Sebastien Jodogne # Date 1587384254 -7200 # Node ID 44bfcfdf42e8de728e1fafe5a1cf4adc1353db2f # Parent 5bba4d249422f1dbdbe75ff82e73ee8b66864476# Parent 1491d501836a23a966b3639a4bd1d189b792573f integration mainline->transcoding diff -r 5bba4d249422 -r 44bfcfdf42e8 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -745,7 +745,7 @@ } - bool DicomMap::IsDicomFile(const char* dicom, + bool DicomMap::IsDicomFile(const void* dicom, size_t size) { /** @@ -755,16 +755,18 @@ * account to determine whether the file is or is not a DICOM file. **/ + const uint8_t* p = reinterpret_cast(dicom); + return (size >= 132 && - dicom[128] == 'D' && - dicom[129] == 'I' && - dicom[130] == 'C' && - dicom[131] == 'M'); + p[128] == 'D' && + p[129] == 'I' && + p[130] == 'C' && + p[131] == 'M'); } bool DicomMap::ParseDicomMetaInformation(DicomMap& result, - const char* dicom, + const void* dicom, size_t size) { if (!IsDicomFile(dicom, size)) @@ -788,7 +790,7 @@ DicomTag tag(0x0000, 0x0000); // Dummy initialization ValueRepresentation vr; std::string value; - if (!ReadNextTag(tag, vr, value, dicom, size, position) || + if (!ReadNextTag(tag, vr, value, reinterpret_cast(dicom), size, position) || tag.GetGroup() != 0x0002 || tag.GetElement() != 0x0000 || vr != ValueRepresentation_UnsignedLong || @@ -805,7 +807,7 @@ while (position < stopPosition) { - if (ReadNextTag(tag, vr, value, dicom, size, position)) + if (ReadNextTag(tag, vr, value, reinterpret_cast(dicom), size, position)) { result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); } diff -r 5bba4d249422 -r 44bfcfdf42e8 Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Wed Apr 15 22:03:21 2020 +0200 +++ b/Core/DicomFormat/DicomMap.h Mon Apr 20 14:04:14 2020 +0200 @@ -180,11 +180,11 @@ void GetTags(std::set& tags) const; - static bool IsDicomFile(const char* dicom, + static bool IsDicomFile(const void* dicom, size_t size); static bool ParseDicomMetaInformation(DicomMap& result, - const char* dicom, + const void* dicom, size_t size); void LogMissingTagsForStore() const; diff -r 5bba4d249422 -r 44bfcfdf42e8 Core/DicomNetworking/DicomAssociation.cpp --- a/Core/DicomNetworking/DicomAssociation.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/Core/DicomNetworking/DicomAssociation.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -178,9 +178,10 @@ { Close(); } - catch (OrthancException&) + catch (OrthancException& e) { // Don't throw exception in destructors + LOG(ERROR) << "Error while destroying a DICOM association: " << e.What(); } } @@ -515,7 +516,7 @@ } throw OrthancException(ErrorCode_NetworkProtocol, - "DicomUserConnection - " + command + " to AET \"" + + "DicomAssociation - " + command + " to AET \"" + parameters.GetRemoteApplicationEntityTitle() + "\": " + info); } diff -r 5bba4d249422 -r 44bfcfdf42e8 Core/DicomNetworking/DicomControlUserConnection.cpp --- a/Core/DicomNetworking/DicomControlUserConnection.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/Core/DicomNetworking/DicomControlUserConnection.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -226,6 +226,7 @@ void DicomControlUserConnection::SetupPresentationContexts() { + assert(association_.get() != NULL); association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); @@ -241,6 +242,7 @@ const char* level) { assert(isWorklist ^ (level != NULL)); + assert(association_.get() != NULL); association_->Open(parameters_); @@ -325,6 +327,7 @@ ResourceType level, const DicomMap& fields) { + assert(association_.get() != NULL); association_->Open(parameters_); std::unique_ptr query( @@ -440,8 +443,16 @@ } + void DicomControlUserConnection::Close() + { + assert(association_.get() != NULL); + association_->Close(); + } + + bool DicomControlUserConnection::Echo() { + assert(association_.get() != NULL); association_->Open(parameters_); DIC_US status; diff -r 5bba4d249422 -r 44bfcfdf42e8 Core/DicomNetworking/DicomControlUserConnection.h --- a/Core/DicomNetworking/DicomControlUserConnection.h Wed Apr 15 22:03:21 2020 +0200 +++ b/Core/DicomNetworking/DicomControlUserConnection.h Mon Apr 20 14:04:14 2020 +0200 @@ -75,6 +75,8 @@ return parameters_; } + void Close(); + bool Echo(); void Find(DicomFindAnswers& result, diff -r 5bba4d249422 -r 44bfcfdf42e8 NEWS --- a/NEWS Wed Apr 15 22:03:21 2020 +0200 +++ b/NEWS Mon Apr 20 14:04:14 2020 +0200 @@ -10,6 +10,19 @@ - "/modalities/{id}/store-straight": Synchronously send the DICOM instance in POST body to another modality (alternative to command-line tools such as "storescu") +Plugins +------- + +* New functions in the SDK: + - OrthancPluginRegisterIncomingDicomInstanceFilter() + - OrthancPluginGetInstanceTransferSyntaxUid() + - OrthancPluginHasInstancePixelData() + +Lua +--- + +* New "info" field in "ReceivedInstanceFilter()" callback, containing + "HasPixelData" and "TransferSyntaxUID" information Maintenance ----------- @@ -20,6 +33,8 @@ * Fix signature of "OrthancPluginRegisterStorageCommitmentScpCallback()" in plugins SDK * Error reporting on failure while initializing SSL * Fix unit test ParsedDicomFile.ToJsonFlags2 on big-endian architectures +* Avoid one memcpy of the DICOM buffer on "POST /instances" +* Default value of "HttpThreadsCount" reduced from 50 to 10 * Upgraded dependencies for static builds (notably on Windows): - civetweb 1.12 - openssl 1.1.1f diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/DicomInstanceToStore.cpp --- a/OrthancServer/DicomInstanceToStore.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/DicomInstanceToStore.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -150,18 +150,48 @@ { public: DicomInstanceOrigin origin_; - SmartContainer buffer_; + bool hasBuffer_; + std::unique_ptr ownBuffer_; + const void* bufferData_; + size_t bufferSize_; SmartContainer parsed_; SmartContainer summary_; SmartContainer json_; MetadataMap metadata_; + PImpl() : + hasBuffer_(false), + bufferData_(NULL), + bufferSize_(0) + { + } + private: std::unique_ptr hasher_; + void ParseDicomFile() + { + if (!parsed_.HasContent()) + { + if (!hasBuffer_) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (ownBuffer_.get() != NULL) + { + parsed_.TakeOwnership(new ParsedDicomFile(*ownBuffer_)); + } + else + { + parsed_.TakeOwnership(new ParsedDicomFile(bufferData_, bufferSize_)); + } + } + } + void ComputeMissingInformation() { - if (buffer_.HasContent() && + if (hasBuffer_ && summary_.HasContent() && json_.HasContent()) { @@ -169,7 +199,7 @@ return; } - if (!buffer_.HasContent()) + if (!hasBuffer_) { if (!parsed_.HasContent()) { @@ -186,13 +216,15 @@ } // Serialize the parsed DICOM file - buffer_.Allocate(); - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), + ownBuffer_.reset(new std::string); + if (!FromDcmtkBridge::SaveToMemoryBuffer(*ownBuffer_, *parsed_.GetContent().GetDcmtkObject().getDataset())) { throw OrthancException(ErrorCode_InternalError, "Unable to serialize a DICOM file to a memory buffer"); } + + hasBuffer_ = true; } if (summary_.HasContent() && @@ -205,10 +237,8 @@ // memory buffer, but that its summary or its JSON version is // missing - if (!parsed_.HasContent()) - { - parsed_.TakeOwnership(new ParsedDicomFile(buffer_.GetConstContent())); - } + ParseDicomFile(); + assert(parsed_.HasContent()); // At this point, we have parsed the DICOM file @@ -232,22 +262,38 @@ public: - const char* GetBufferData() + void SetBuffer(const void* data, + size_t size) + { + ownBuffer_.reset(NULL); + bufferData_ = data; + bufferSize_ = size; + hasBuffer_ = true; + } + + const void* GetBufferData() { ComputeMissingInformation(); - - if (!buffer_.HasContent()) + + if (!hasBuffer_) { throw OrthancException(ErrorCode_InternalError); } - if (buffer_.GetConstContent().size() == 0) + if (ownBuffer_.get() != NULL) { - return NULL; + if (ownBuffer_->empty()) + { + return NULL; + } + else + { + return ownBuffer_->c_str(); + } } else { - return buffer_.GetConstContent().c_str(); + return bufferData_; } } @@ -256,12 +302,19 @@ { ComputeMissingInformation(); - if (!buffer_.HasContent()) + if (!hasBuffer_) { throw OrthancException(ErrorCode_InternalError); } - return buffer_.GetConstContent().size(); + if (ownBuffer_.get() != NULL) + { + return ownBuffer_->size(); + } + else + { + return bufferSize_; + } } @@ -326,6 +379,22 @@ return false; } + + + bool HasPixelData() + { + ComputeMissingInformation(); + ParseDicomFile(); + + if (parsed_.HasContent()) + { + return parsed_.GetContent().HasTag(DICOM_TAG_PIXEL_DATA); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } }; @@ -347,9 +416,10 @@ } - void DicomInstanceToStore::SetBuffer(const std::string& dicom) + void DicomInstanceToStore::SetBuffer(const void* dicom, + size_t size) { - pimpl_->buffer_.SetConstReference(dicom); + pimpl_->SetBuffer(dicom, size); } @@ -391,15 +461,15 @@ } - const char* DicomInstanceToStore::GetBufferData() + const void* DicomInstanceToStore::GetBufferData() const { - return pimpl_->GetBufferData(); + return const_cast(*pimpl_).GetBufferData(); } - size_t DicomInstanceToStore::GetBufferSize() + size_t DicomInstanceToStore::GetBufferSize() const { - return pimpl_->GetBufferSize(); + return const_cast(*pimpl_).GetBufferSize(); } @@ -409,15 +479,15 @@ } - const Json::Value& DicomInstanceToStore::GetJson() + const Json::Value& DicomInstanceToStore::GetJson() const { - return pimpl_->GetJson(); + return const_cast(*pimpl_).GetJson(); } - bool DicomInstanceToStore::LookupTransferSyntax(std::string& result) + bool DicomInstanceToStore::LookupTransferSyntax(std::string& result) const { - return pimpl_->LookupTransferSyntax(result); + return const_cast(*pimpl_).LookupTransferSyntax(result); } @@ -425,4 +495,9 @@ { return pimpl_->GetHasher(); } + + bool DicomInstanceToStore::HasPixelData() const + { + return const_cast(*pimpl_).HasPixelData(); + } } diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/DicomInstanceToStore.h --- a/OrthancServer/DicomInstanceToStore.h Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/DicomInstanceToStore.h Mon Apr 20 14:04:14 2020 +0200 @@ -59,8 +59,11 @@ void SetOrigin(const DicomInstanceOrigin& origin); const DicomInstanceOrigin& GetOrigin() const; - - void SetBuffer(const std::string& dicom); + + // WARNING: The buffer is not copied, it must not be removed as + // long as the "DicomInstanceToStore" object is alive + void SetBuffer(const void* dicom, + size_t size); void SetParsedDicomFile(ParsedDicomFile& parsed); @@ -76,16 +79,18 @@ MetadataType metadata, const std::string& value); - const char* GetBufferData(); + const void* GetBufferData() const; - size_t GetBufferSize(); + size_t GetBufferSize() const; const DicomMap& GetSummary(); - const Json::Value& GetJson(); + const Json::Value& GetJson() const; - bool LookupTransferSyntax(std::string& result); + bool LookupTransferSyntax(std::string& result) const; DicomInstanceHasher& GetHasher(); + + bool HasPixelData() const; }; } diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/LuaScripting.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -874,6 +874,17 @@ instance.GetOrigin().Format(origin); call.PushJson(origin); + Json::Value info = Json::objectValue; + info["HasPixelData"] = instance.HasPixelData(); + + std::string s; + if (instance.LookupTransferSyntax(s)) + { + info["TransferSyntaxUID"] = s; + } + + call.PushJson(info); + if (!call.ExecutePredicate()) { return false; diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -121,22 +121,23 @@ "Received an empty DICOM file"); } + // The lifetime of "dicom" must be longer than "toStore", as the + // latter can possibly store a reference to the former (*) std::string dicom; + DicomInstanceToStore toStore; + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); + if (boost::iequals(call.GetHttpHeader("content-encoding", ""), "gzip")) { GzipCompressor compressor; compressor.Uncompress(dicom, call.GetBodyData(), call.GetBodySize()); + toStore.SetBuffer(dicom.c_str(), dicom.size()); // (*) } else { - // TODO Remove unneccessary memcpy - call.BodyToString(dicom); - } - - DicomInstanceToStore toStore; - toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); - toStore.SetBuffer(dicom); + toStore.SetBuffer(call.GetBodyData(), call.GetBodySize()); + } std::string publicId; StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default); diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/QueryRetrieveHandler.cpp --- a/OrthancServer/QueryRetrieveHandler.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/QueryRetrieveHandler.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -36,6 +36,7 @@ #include "OrthancConfiguration.h" +#include "../Core/DicomNetworking/DicomControlUserConnection.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/Logging.h" #include "LuaScripting.h" @@ -81,8 +82,7 @@ FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); { - DicomUserConnection connection(localAet_, modality_); - connection.Open(); + DicomControlUserConnection connection(localAet_, modality_); connection.Find(answers_, level_, fixed, findNormalized_); } diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/ServerJobs/DicomModalityStoreJob.cpp --- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -47,9 +47,7 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomUserConnection); - connection_->SetLocalApplicationEntityTitle(localAet_); - connection_->SetRemoteModality(remote_); + connection_.reset(new DicomUserConnection(localAet_, remote_)); } } diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/ServerJobs/DicomMoveScuJob.cpp --- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -96,8 +96,7 @@ { if (connection_.get() == NULL) { - connection_.reset(new DicomUserConnection(localAet_, remote_)); - connection_->Open(); + connection_.reset(new DicomControlUserConnection(localAet_, remote_)); } connection_->Move(targetAet_, findAnswer); diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/ServerJobs/DicomMoveScuJob.h --- a/OrthancServer/ServerJobs/DicomMoveScuJob.h Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h Mon Apr 20 14:04:14 2020 +0200 @@ -34,8 +34,8 @@ #pragma once #include "../../Core/Compatibility.h" +#include "../../Core/DicomNetworking/DicomControlUserConnection.h" #include "../../Core/JobsEngine/SetOfCommandsJob.h" -#include "../../Core/DicomNetworking/DicomUserConnection.h" #include "../QueryRetrieveHandler.h" @@ -49,13 +49,14 @@ class Command; class Unserializer; - ServerContext& context_; - std::string localAet_; - std::string targetAet_; - RemoteModalityParameters remote_; - std::unique_ptr connection_; - Json::Value query_; + ServerContext& context_; + std::string localAet_; + std::string targetAet_; + RemoteModalityParameters remote_; + Json::Value query_; + std::unique_ptr connection_; + void Retrieve(const DicomMap& findAnswer); public: diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp --- a/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -34,7 +34,7 @@ #include "../PrecompiledHeadersServer.h" #include "StorageCommitmentScpJob.h" -#include "../../Core/DicomNetworking/DicomUserConnection.h" +#include "../../Core/DicomNetworking/DicomAssociation.h" #include "../../Core/Logging.h" #include "../../Core/OrthancException.h" #include "../../Core/SerializationToolbox.h" @@ -347,9 +347,10 @@ { throw OrthancException(ErrorCode_InternalError); } - - DicomUserConnection scu(calledAet_, remoteModality_); - scu.ReportStorageCommitment(transactionUid_, sopClassUids_, sopInstanceUids_, failureReasons); + + DicomAssociationParameters parameters(calledAet_, remoteModality_); + DicomAssociation::ReportStorageCommitment( + parameters, transactionUid_, sopClassUids_, sopInstanceUids_, failureReasons); } diff -r 5bba4d249422 -r 44bfcfdf42e8 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/OrthancServer/main.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -82,7 +82,7 @@ DicomInstanceToStore toStore; toStore.SetOrigin(DicomInstanceOrigin::FromDicomProtocol (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str())); - toStore.SetBuffer(dicomFile); + toStore.SetBuffer(dicomFile.c_str(), dicomFile.size()); toStore.SetSummary(dicomSummary); toStore.SetJson(dicomJson); @@ -906,7 +906,7 @@ httpDescribeErrors = lock.GetConfiguration().GetBooleanParameter("HttpDescribeErrors", true); // HTTP server - httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 50)); + httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 10)); httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042)); httpServer.SetRemoteAccessAllowed(lock.GetConfiguration().GetBooleanParameter("RemoteAccessAllowed", false)); httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", defaultKeepAlive)); diff -r 5bba4d249422 -r 44bfcfdf42e8 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Wed Apr 15 22:03:21 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Apr 20 14:04:14 2020 +0200 @@ -825,6 +825,7 @@ typedef std::list OnChangeCallbacks; typedef std::list IncomingHttpRequestFilters; typedef std::list IncomingHttpRequestFilters2; + typedef std::list IncomingDicomInstanceFilters; typedef std::list DecodeImageCallbacks; typedef std::list JobsUnserializers; typedef std::list RefreshMetricsCallbacks; @@ -844,6 +845,7 @@ _OrthancPluginMoveCallback moveCallbacks_; IncomingHttpRequestFilters incomingHttpRequestFilters_; IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; + IncomingDicomInstanceFilters incomingDicomInstanceFilters_; RefreshMetricsCallbacks refreshMetricsCallbacks_; StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_; std::unique_ptr storageArea_; @@ -1782,7 +1784,33 @@ } - + bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance, + const Json::Value& simplified) + { + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + + for (PImpl::IncomingDicomInstanceFilters::const_iterator + filter = pimpl_->incomingDicomInstanceFilters_.begin(); + filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter) + { + int32_t allowed = (*filter) ( + reinterpret_cast(&instance)); + + if (allowed == 0) + { + return false; + } + else if (allowed != 1) + { + // The callback is only allowed to answer 0 or 1 + throw OrthancException(ErrorCode_Plugin); + } + } + + return true; + } + + void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resource) @@ -1967,6 +1995,16 @@ } + void OrthancPlugins::RegisterIncomingDicomInstanceFilter(const void* parameters) + { + const _OrthancPluginIncomingDicomInstanceFilter& p = + *reinterpret_cast(parameters); + + LOG(INFO) << "Plugin has registered a callback to filter incoming DICOM instances"; + pimpl_->incomingDicomInstanceFilters_.push_back(p.callback); + } + + void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters) { const _OrthancPluginRegisterRefreshMetricsCallback& p = @@ -2419,8 +2457,8 @@ const _OrthancPluginAccessDicomInstance& p = *reinterpret_cast(parameters); - DicomInstanceToStore& instance = - *reinterpret_cast(p.instance); + const DicomInstanceToStore& instance = + *reinterpret_cast(p.instance); switch (service) { @@ -2433,7 +2471,7 @@ return; case _OrthancPluginService_GetInstanceData: - *p.resultString = instance.GetBufferData(); + *p.resultString = reinterpret_cast(instance.GetBufferData()); return; case _OrthancPluginService_HasInstanceMetadata: @@ -2469,6 +2507,22 @@ *p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin()); return; + case _OrthancPluginService_GetInstanceTransferSyntaxUid: // New in Orthanc 1.6.1 + { + std::string s; + if (!instance.LookupTransferSyntax(s)) + { + s.clear(); + } + + *p.resultStringToFree = CopyString(s); + return; + } + + case _OrthancPluginService_HasInstancePixelData: // New in Orthanc 1.6.1 + *p.resultInt64 = instance.HasPixelData(); + return; + default: throw OrthancException(ErrorCode_InternalError); } @@ -3420,6 +3474,8 @@ case _OrthancPluginService_HasInstanceMetadata: case _OrthancPluginService_GetInstanceMetadata: case _OrthancPluginService_GetInstanceOrigin: + case _OrthancPluginService_GetInstanceTransferSyntaxUid: + case _OrthancPluginService_HasInstancePixelData: AccessDicomInstance(service, parameters); return true; @@ -4034,6 +4090,10 @@ RegisterIncomingHttpRequestFilter2(parameters); return true; + case _OrthancPluginService_RegisterIncomingDicomInstanceFilter: + RegisterIncomingDicomInstanceFilter(parameters); + return true; + case _OrthancPluginService_RegisterRefreshMetricsCallback: RegisterRefreshMetricsCallback(parameters); return true; @@ -4477,46 +4537,50 @@ getValues[i] = getArguments[i].second.c_str(); } - // Improved callback with support for GET arguments, since Orthanc 1.3.0 - for (PImpl::IncomingHttpRequestFilters2::const_iterator - filter = pimpl_->incomingHttpRequestFilters2_.begin(); - filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter) { - int32_t allowed = (*filter) (cMethod, uri, ip, - httpKeys.size(), - httpKeys.empty() ? NULL : &httpKeys[0], - httpValues.empty() ? NULL : &httpValues[0], - getKeys.size(), - getKeys.empty() ? NULL : &getKeys[0], - getValues.empty() ? NULL : &getValues[0]); - - if (allowed == 0) - { - return false; - } - else if (allowed != 1) + boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); + + // Improved callback with support for GET arguments, since Orthanc 1.3.0 + for (PImpl::IncomingHttpRequestFilters2::const_iterator + filter = pimpl_->incomingHttpRequestFilters2_.begin(); + filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter) { - // The callback is only allowed to answer 0 or 1 - throw OrthancException(ErrorCode_Plugin); + int32_t allowed = (*filter) (cMethod, uri, ip, + httpKeys.size(), + httpKeys.empty() ? NULL : &httpKeys[0], + httpValues.empty() ? NULL : &httpValues[0], + getKeys.size(), + getKeys.empty() ? NULL : &getKeys[0], + getValues.empty() ? NULL : &getValues[0]); + + if (allowed == 0) + { + return false; + } + else if (allowed != 1) + { + // The callback is only allowed to answer 0 or 1 + throw OrthancException(ErrorCode_Plugin); + } } - } - - for (PImpl::IncomingHttpRequestFilters::const_iterator - filter = pimpl_->incomingHttpRequestFilters_.begin(); - filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter) - { - int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(), - httpKeys.empty() ? NULL : &httpKeys[0], - httpValues.empty() ? NULL : &httpValues[0]); - - if (allowed == 0) + + for (PImpl::IncomingHttpRequestFilters::const_iterator + filter = pimpl_->incomingHttpRequestFilters_.begin(); + filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter) { - return false; - } - else if (allowed != 1) - { - // The callback is only allowed to answer 0 or 1 - throw OrthancException(ErrorCode_Plugin); + int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(), + httpKeys.empty() ? NULL : &httpKeys[0], + httpValues.empty() ? NULL : &httpValues[0]); + + if (allowed == 0) + { + return false; + } + else if (allowed != 1) + { + // The callback is only allowed to answer 0 or 1 + throw OrthancException(ErrorCode_Plugin); + } } } diff -r 5bba4d249422 -r 44bfcfdf42e8 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Wed Apr 15 22:03:21 2020 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Mon Apr 20 14:04:14 2020 +0200 @@ -124,6 +124,8 @@ void RegisterIncomingHttpRequestFilter2(const void* parameters); + void RegisterIncomingDicomInstanceFilter(const void* parameters); + void RegisterRefreshMetricsCallback(const void* parameters); void RegisterStorageCommitmentScpCallback(const void* parameters); @@ -252,10 +254,7 @@ const Json::Value& simplifiedTags) ORTHANC_OVERRIDE; virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance, - const Json::Value& simplified) ORTHANC_OVERRIDE - { - return true; // TODO Enable filtering of instances from plugins - } + const Json::Value& simplified) ORTHANC_OVERRIDE; bool HasStorageArea() const; diff -r 5bba4d249422 -r 44bfcfdf42e8 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Wed Apr 15 22:03:21 2020 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Mon Apr 20 14:04:14 2020 +0200 @@ -27,6 +27,7 @@ * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - Possibly register a callback to filter incoming DICOM instance using OrthancPluginRegisterIncomingDicomInstanceFilter(). * -# void OrthancPluginFinalize(): * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -124,7 +125,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 6 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 1 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -454,6 +455,7 @@ _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, + _OrthancPluginService_RegisterIncomingDicomInstanceFilter = 1014, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -498,6 +500,8 @@ _OrthancPluginService_HasInstanceMetadata = 4005, _OrthancPluginService_GetInstanceMetadata = 4006, _OrthancPluginService_GetInstanceOrigin = 4007, + _OrthancPluginService_GetInstanceTransferSyntaxUid = 4008, + _OrthancPluginService_HasInstancePixelData = 4009, /* Services for plugins implementing a database back-end */ _OrthancPluginService_RegisterDatabaseBackend = 5000, @@ -1104,11 +1108,11 @@ /** - * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. + * @brief Signature of a callback function that is triggered when Orthanc stores a new DICOM instance. * @ingroup Callbacks **/ typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( - OrthancPluginDicomInstance* instance, + const OrthancPluginDicomInstance* instance, const char* instanceId); @@ -2693,12 +2697,12 @@ typedef struct { - char** resultStringToFree; - const char** resultString; - int64_t* resultInt64; - const char* key; - OrthancPluginDicomInstance* instance; - OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + const OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ } _OrthancPluginAccessDicomInstance; @@ -2714,8 +2718,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { const char* result; @@ -2747,8 +2751,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { int64_t size; @@ -2780,8 +2784,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE const void* OrthancPluginGetInstanceData( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { const char* result; @@ -2816,8 +2820,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { char* result; @@ -2854,8 +2858,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { char* result; @@ -2893,9 +2897,9 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) { int64_t result; @@ -2934,9 +2938,9 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance, + const char* metadata) { const char* result; @@ -5106,8 +5110,8 @@ * @ingroup Callbacks **/ ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) { OrthancPluginInstanceOrigin origin; @@ -7412,6 +7416,128 @@ return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); } + + + /** + * @brief Callback to filter incoming DICOM instances received by Orthanc. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a new DICOM instance (e.g. through REST API or + * DICOM protocol), and that answers whether this DICOM instance + * should be accepted or discarded by Orthanc. + * + * 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 discard the instance, 1 to store the instance, -1 if error. + * @ingroup Callback + **/ + typedef int32_t (*OrthancPluginIncomingDicomInstanceFilter) ( + const OrthancPluginDicomInstance* instance); + + + typedef struct + { + OrthancPluginIncomingDicomInstanceFilter callback; + } _OrthancPluginIncomingDicomInstanceFilter; + + /** + * @brief Register a callback to filter incoming DICOM instance. + * + * This function registers a custom callback to filter incoming + * DICOM instances received by Orthanc (either through the REST API + * or through the DICOM protocol). + * + * @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 OrthancPluginRegisterIncomingDicomInstanceFilter( + OrthancPluginContext* context, + OrthancPluginIncomingDicomInstanceFilter callback) + { + _OrthancPluginIncomingDicomInstanceFilter params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterIncomingDicomInstanceFilter, ¶ms); + } + + + /** + * @brief Get the transfer syntax of a DICOM file. + * + * This function returns a pointer to a newly created string that + * contains the transfer syntax UID of the DICOM instance. The empty + * string might be returned if this information is unknown. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the + * transfer syntax UID. This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceTransferSyntaxUid( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether the DICOM file has pixel data. + * + * This function returns a Boolean value indicating whether the + * DICOM instance contains the pixel data (7FE0,0010) tag. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return "1" if the DICOM instance contains pixel data, or "0" if + * the tag is missing, or "-1" in the case of an error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginHasInstancePixelData( + OrthancPluginContext* context, + const OrthancPluginDicomInstance* instance) + { + int64_t hasPixelData; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &hasPixelData; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, ¶ms) != OrthancPluginErrorCode_Success || + hasPixelData < 0 || + hasPixelData > 1) + { + /* Error */ + return -1; + } + else + { + return hasPixelData; + } + } + + #ifdef __cplusplus } #endif diff -r 5bba4d249422 -r 44bfcfdf42e8 Plugins/Samples/Basic/Plugin.c --- a/Plugins/Samples/Basic/Plugin.c Wed Apr 15 22:03:21 2020 +0200 +++ b/Plugins/Samples/Basic/Plugin.c Mon Apr 20 14:04:14 2020 +0200 @@ -29,9 +29,9 @@ static OrthancPluginErrorCode customError; -ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback1(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode Callback1(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { char buffer[1024]; uint32_t i; @@ -83,9 +83,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback2(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode Callback2(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /* Answer with a sample 16bpp image. */ @@ -115,9 +115,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback3(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode Callback3(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { @@ -140,9 +140,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback4(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode Callback4(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /* Answer with a sample 8bpp image. */ @@ -172,9 +172,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode Callback5(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode Callback5(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { /** * Demonstration the difference between the @@ -222,9 +222,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode CallbackCreateDicom(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPluginErrorCode CallbackCreateDicom(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { const char* pathLocator = "\"Path\" : \""; char info[1024]; @@ -266,7 +266,7 @@ } -ORTHANC_PLUGINS_API void DicomWebBinaryCallback( +void DicomWebBinaryCallback( OrthancPluginDicomWebNode* node, OrthancPluginDicomWebSetBinaryNode setter, uint32_t levelDepth, @@ -281,8 +281,8 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode OnStoredCallback(OrthancPluginDicomInstance* instance, - const char* instanceId) +OrthancPluginErrorCode OnStoredCallback(const OrthancPluginDicomInstance* instance, + const char* instanceId) { char buffer[256]; FILE* fp; @@ -333,9 +333,9 @@ } -ORTHANC_PLUGINS_API OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId) +OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) { char info[1024]; @@ -391,12 +391,12 @@ } -ORTHANC_PLUGINS_API int32_t FilterIncomingHttpRequest(OrthancPluginHttpMethod method, - const char* uri, - const char* ip, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues) +int32_t FilterIncomingHttpRequest(OrthancPluginHttpMethod method, + const char* uri, + const char* ip, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) { uint32_t i; @@ -423,11 +423,31 @@ } -ORTHANC_PLUGINS_API void RefreshMetrics() +static void RefreshMetrics() { static unsigned int count = 0; OrthancPluginSetMetricsValue(context, "sample_counter", - (float) (count++), OrthancPluginMetricsType_Default); + (float) (count++), OrthancPluginMetricsType_Default); +} + + +static int32_t FilterIncomingDicomInstance(const OrthancPluginDicomInstance* instance) +{ + char buf[1024]; + char* s; + int32_t hasPixelData; + + s = OrthancPluginGetInstanceTransferSyntaxUid(context, instance); + sprintf(buf, "Incoming transfer syntax: %s", s); + OrthancPluginFreeString(context, s); + OrthancPluginLogWarning(context, buf); + + hasPixelData = OrthancPluginHasInstancePixelData(context, instance); + sprintf(buf, "Incoming has pixel data: %d", hasPixelData); + OrthancPluginLogWarning(context, buf); + + // Reject all instances without pixel data + return hasPixelData; } @@ -495,7 +515,8 @@ OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); OrthancPluginRegisterIncomingHttpRequestFilter(context, FilterIncomingHttpRequest); OrthancPluginRegisterRefreshMetricsCallback(context, RefreshMetrics); - + OrthancPluginRegisterIncomingDicomInstanceFilter(context, FilterIncomingDicomInstance); + /* Declare several properties of the plugin */ OrthancPluginSetRootUri(context, "/plugin/hello"); diff -r 5bba4d249422 -r 44bfcfdf42e8 Resources/Configuration.json --- a/Resources/Configuration.json Wed Apr 15 22:03:21 2020 +0200 +++ b/Resources/Configuration.json Mon Apr 20 14:04:14 2020 +0200 @@ -391,8 +391,11 @@ // caveats: https://eklitzke.org/the-caveats-of-tcp-nodelay "TcpNoDelay" : true, - // Number of threads that are used by the embedded HTTP server. - "HttpThreadsCount" : 50, + // Number of threads that are used by the embedded HTTP server. In + // Orthanc <= 1.6.0, the default value was 50. In Orthanc >= 1.6.1, + // default is 10 to prevent memory grow in basic setups. + // https://groups.google.com/d/msg/orthanc-users/qWqxpvCPv8g/Z8huoA5FDAAJ + "HttpThreadsCount" : 10, // If this option is set to "false", Orthanc will run in index-only // mode. The DICOM files will not be stored on the drive. Note that diff -r 5bba4d249422 -r 44bfcfdf42e8 Resources/DicomConformanceStatement.txt --- a/Resources/DicomConformanceStatement.txt Wed Apr 15 22:03:21 2020 +0200 +++ b/Resources/DicomConformanceStatement.txt Mon Apr 20 14:04:14 2020 +0200 @@ -257,8 +257,10 @@ The information above about the SCP support is readily extracted from the function "Orthanc::Internals::AcceptAssociation()" from file -"OrthancServer/Internals/CommandDispatcher.cpp". +"Core/DicomNetworking/Internals/CommandDispatcher.cpp". -The information above about the SCU support is derived from the class -"Orthanc::DicomUserConnection" from file -"OrthancServer/DicomProtocol/DicomUserConnection.cpp". +The information above about the SCU support is derived from the +classes "Orthanc::DicomControlUserConnection" and +"Orthanc::DicomStoreUserConnection" from file +"Core/DicomNetworking/DicomControlUserConnection.cpp" and +"Core/DicomNetworking/DicomStoreUserConnection.cpp".