changeset 3848:44bfcfdf42e8 transcoding

integration mainline->transcoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 20 Apr 2020 14:04:14 +0200
parents 5bba4d249422 (current diff) 1491d501836a (diff)
children 67f988f42cef
files OrthancServer/OrthancRestApi/OrthancRestApi.cpp OrthancServer/main.cpp
diffstat 22 files changed, 519 insertions(+), 182 deletions(-) [+]
line wrap: on
line diff
--- 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<const uint8_t*>(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<const char*>(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<const char*>(dicom), size, position))
       {
         result.SetValue(tag, value, IsBinaryValueRepresentation(vr));
       }
--- 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<DicomTag>& 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;
--- 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);
     }
--- 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<ParsedDicomFile> 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;
--- 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,
--- 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
--- 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<std::string>          buffer_;
+    bool                                 hasBuffer_;
+    std::unique_ptr<std::string>         ownBuffer_;
+    const void*                          bufferData_;
+    size_t                               bufferSize_;
     SmartContainer<ParsedDicomFile>      parsed_;
     SmartContainer<DicomMap>             summary_;
     SmartContainer<Json::Value>          json_;
     MetadataMap                          metadata_;
 
+    PImpl() :
+      hasBuffer_(false),
+      bufferData_(NULL),
+      bufferSize_(0)
+    {
+    }
+
   private:
     std::unique_ptr<DicomInstanceHasher>  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&>(*pimpl_).GetBufferData();
   }
 
 
-  size_t DicomInstanceToStore::GetBufferSize()
+  size_t DicomInstanceToStore::GetBufferSize() const
   {
-    return pimpl_->GetBufferSize();
+    return const_cast<PImpl&>(*pimpl_).GetBufferSize();
   }
 
 
@@ -409,15 +479,15 @@
   }
 
     
-  const Json::Value& DicomInstanceToStore::GetJson()
+  const Json::Value& DicomInstanceToStore::GetJson() const
   {
-    return pimpl_->GetJson();
+    return const_cast<PImpl&>(*pimpl_).GetJson();
   }
 
 
-  bool DicomInstanceToStore::LookupTransferSyntax(std::string& result)
+  bool DicomInstanceToStore::LookupTransferSyntax(std::string& result) const
   {
-    return pimpl_->LookupTransferSyntax(result);
+    return const_cast<PImpl&>(*pimpl_).LookupTransferSyntax(result);
   }
 
 
@@ -425,4 +495,9 @@
   {
     return pimpl_->GetHasher();
   }
+
+  bool DicomInstanceToStore::HasPixelData() const
+  {
+    return const_cast<PImpl&>(*pimpl_).HasPixelData();
+  }
 }
--- 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;
   };
 }
--- 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;
--- 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);
--- 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_);
       }
 
--- 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_));
     }
   }
 
--- 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);
--- 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<DicomUserConnection>  connection_;
-    Json::Value                           query_;
+    ServerContext&            context_;
+    std::string               localAet_;
+    std::string               targetAet_;
+    RemoteModalityParameters  remote_;
+    Json::Value               query_;
 
+    std::unique_ptr<DicomControlUserConnection>  connection_;
+    
     void Retrieve(const DicomMap& findAnswer);
     
   public:
--- 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);
   }
     
 
--- 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));
--- 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<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
     typedef std::list<OrthancPluginIncomingHttpRequestFilter>  IncomingHttpRequestFilters;
     typedef std::list<OrthancPluginIncomingHttpRequestFilter2>  IncomingHttpRequestFilters2;
+    typedef std::list<OrthancPluginIncomingDicomInstanceFilter>  IncomingDicomInstanceFilters;
     typedef std::list<OrthancPluginDecodeImageCallback>  DecodeImageCallbacks;
     typedef std::list<OrthancPluginJobsUnserializer>  JobsUnserializers;
     typedef std::list<OrthancPluginRefreshMetricsCallback>  RefreshMetricsCallbacks;
@@ -844,6 +845,7 @@
     _OrthancPluginMoveCallback moveCallbacks_;
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
     IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
+    IncomingDicomInstanceFilters  incomingDicomInstanceFilters_;
     RefreshMetricsCallbacks refreshMetricsCallbacks_;
     StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
     std::unique_ptr<StorageAreaFactory>  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<const OrthancPluginDicomInstance*>(&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<const _OrthancPluginIncomingDicomInstanceFilter*>(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<const _OrthancPluginAccessDicomInstance*>(parameters);
 
-    DicomInstanceToStore& instance =
-      *reinterpret_cast<DicomInstanceToStore*>(p.instance);
+    const DicomInstanceToStore& instance =
+      *reinterpret_cast<const DicomInstanceToStore*>(p.instance);
 
     switch (service)
     {
@@ -2433,7 +2471,7 @@
         return;
 
       case _OrthancPluginService_GetInstanceData:
-        *p.resultString = instance.GetBufferData();
+        *p.resultString = reinterpret_cast<const char*>(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);
+        }
       }
     }
 
--- 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;
 
--- 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().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    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, &params);
   }
   
+
+
+  /**
+   * @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, &params);
+  }
+
+
+  /**
+   * @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(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceTransferSyntaxUid, &params) != 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(&params, 0, sizeof(params));
+    params.resultInt64 = &hasPixelData;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstancePixelData, &params) != OrthancPluginErrorCode_Success ||
+        hasPixelData < 0 ||
+        hasPixelData > 1)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return hasPixelData;
+    }
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- 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");
--- 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
--- 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".