changeset 63:32de70a1e4c7

New functions from the SDK wrapped in Python: CreateDicom, RegisterFindCallback, RegisterMoveCallback
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Jun 2021 18:19:27 +0200
parents d82eef8c98fc
children 091fb1903bfc
files CMakeLists.txt CodeAnalysis/Class.mustache CodeAnalysis/ParseOrthancSDK.py CodeAnalysis/sdk.h.mustache NEWS Sources/Autogenerated/sdk.h Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h Sources/Autogenerated/sdk_OrthancPluginImage.impl.h Sources/Autogenerated/sdk_OrthancPluginJob.impl.h Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h Sources/DicomScpCallbacks.cpp Sources/DicomScpCallbacks.h Sources/ICallbackRegistration.cpp Sources/ICallbackRegistration.h Sources/OnStoredInstanceCallback.cpp Sources/Plugin.cpp Sources/PythonLock.cpp Sources/PythonLock.h Sources/RestCallbacks.cpp
diffstat 28 files changed, 970 insertions(+), 217 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri May 28 10:53:18 2021 +0200
+++ b/CMakeLists.txt	Thu Jun 10 18:19:27 2021 +0200
@@ -159,6 +159,7 @@
 
 add_library(OrthancPython SHARED
   Sources/Autogenerated/sdk.cpp
+  Sources/DicomScpCallbacks.cpp
   Sources/IncomingHttpRequestFilter.cpp
   Sources/OnChangeCallback.cpp
   Sources/OnStoredInstanceCallback.cpp
@@ -169,6 +170,7 @@
   Sources/PythonObject.cpp
   Sources/PythonString.cpp
   Sources/RestCallbacks.cpp
+  Sources/ICallbackRegistration.cpp
 
   # Third-party sources
   ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp
--- a/CodeAnalysis/Class.mustache	Fri May 28 10:53:18 2021 +0200
+++ b/CodeAnalysis/Class.mustache	Thu Jun 10 18:19:27 2021 +0200
@@ -17,22 +17,20 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  {{class_name}}* object_;
-  bool borrowed_;
-} sdk_{{class_name}}_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 {{#methods}}
 static PyObject *sdk_{{class_name}}_{{c_function}}(
   sdk_{{class_name}}_Object* self, PyObject *args);
 {{/methods}}
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+{{#custom_methods}}
+extern PyObject *{{implementation}}(
+  sdk_{{class_name}}_Object* self, PyObject *args);
+{{/custom_methods}}
+// End of forward declarations
 
 
 static PyMethodDef sdk_{{class_name}}_Methods[] = {
@@ -41,6 +39,11 @@
     (PyCFunction) sdk_{{class_name}}_{{c_function}}, METH_VARARGS,
     "Generated from C function {{c_function}}()" },
 {{/methods}}
+{{#custom_methods}}
+  { "{{method_name}}",
+    (PyCFunction) {{implementation}}, METH_VARARGS,
+    "Generated from C function {{sdk_function}}()" },
+{{/custom_methods}}
   { NULL }  /* Sentinel */
 };
 
@@ -157,7 +160,7 @@
 }
 
 
-PyObject* Get{{class_name}}Type()
+PyTypeObject* Get{{class_name}}Type()
 {
-  return (PyObject*) &sdk_{{class_name}}_Type;
+  return &sdk_{{class_name}}_Type;
 }
--- a/CodeAnalysis/ParseOrthancSDK.py	Fri May 28 10:53:18 2021 +0200
+++ b/CodeAnalysis/ParseOrthancSDK.py	Thu Jun 10 18:19:27 2021 +0200
@@ -31,6 +31,42 @@
 
 
 ##
+## Configuration of the custom primitives that are manually
+## implemented (not autogenerated)
+##
+
+CUSTOM_FUNCTIONS = {    
+    'OrthancPluginCreateDicom',
+    'OrthancPluginFreeMemoryBuffer',
+    'OrthancPluginFreeString',
+    'OrthancPluginGetFindQueryTag',
+    'OrthancPluginRegisterFindCallback',
+    'OrthancPluginRegisterIncomingHttpRequestFilter',  # Implemented through v2
+    'OrthancPluginRegisterIncomingHttpRequestFilter2',
+    'OrthancPluginRegisterMoveCallback',
+    'OrthancPluginRegisterOnChangeCallback',
+    'OrthancPluginRegisterOnStoredInstanceCallback',
+    'OrthancPluginRegisterRestCallback',               # Implemented using OrthancPlugins::RegisterRestCallback
+    'OrthancPluginRegisterRestCallbackNoLock',         # Implemented using OrthancPlugins::RegisterRestCallback
+}
+
+CUSTOM_METHODS = [
+    {
+        'class_name' : 'OrthancPluginFindQuery',
+        'method_name' : 'GetFindQueryTagGroup',
+        'implementation' : 'GetFindQueryTagGroup',
+        'sdk_function' : 'OrthancPluginGetFindQueryTag',
+    },
+    {
+        'class_name' : 'OrthancPluginFindQuery',
+        'method_name' : 'GetFindQueryTagElement',
+        'implementation' : 'GetFindQueryTagElement',
+        'sdk_function' : 'OrthancPluginGetFindQueryTag',
+    },
+]
+
+
+##
 ## Parse the command-line arguments
 ##
 
@@ -40,7 +76,7 @@
                     help = 'manually provides the path to the libclang shared library')
 parser.add_argument('--source',
                     default = os.path.join(os.path.dirname(__file__),
-                                           '../Resources/Orthanc/Sdk-1.5.7/orthanc/OrthancCPlugin.h'),
+                                           '../Resources/Orthanc/Sdk-1.8.1/orthanc/OrthancCPlugin.h'),
                     help = 'Input C++ file')
 parser.add_argument('--target', 
                     default = os.path.join(os.path.dirname(__file__),
@@ -383,11 +419,13 @@
                   CheckOnlySupportedArguments(args[1:])):
                 className = args[0].type.get_pointee().spelling
 
+                print('Simple method of class %s: %s' % (className, node.spelling))
+                if node.spelling in CUSTOM_FUNCTIONS:
+                    raise Exception('Cannot overwrite an autogenerated method: %s()' % node.spelling)
+                
                 if className.startswith('const '):
                     className = className[len('const '):]
                 
-                print('Simple method of class %s: %s' % (className, node.spelling))
-
                 method = GenerateFunctionBodyTemplate(node.spelling, node.result_type, args[1:])
                 method['self'] = ', self->object_'
                 classes[className]['methods'].append(method)
@@ -397,12 +435,12 @@
                   IsTargetMemoryBufferType(args[0].type) and
                   IsClassType(args[1].type) and
                   CheckOnlySupportedArguments(args[2:])):
-                print('Simple method of class %s, returning bytes: %s' % (
-                    args[1].type.get_pointee().spelling,
-                    node.spelling))
-               
                 className = args[1].type.get_pointee().spelling
 
+                print('Simple method of class %s, returning bytes: %s' % (className, node.spelling))
+                if node.spelling in CUSTOM_FUNCTIONS:
+                    raise Exception('Cannot overwrite an autogenerated method: %s()' % node.spelling)
+                
                 if className.startswith('const '):
                     className = className[len('const '):]
 
@@ -410,6 +448,10 @@
                 method['self'] = ', self->object_'
                 classes[className]['methods'].append(method)
                 countSupportedFunctions += 1
+            
+            elif node.spelling in CUSTOM_FUNCTIONS:
+                print('Ignoring custom function that is manually implemented: %s()' % node.spelling)
+                countSupportedFunctions += 1                
 
             else:
                 print('*** UNSUPPORTED INPUT: %s' % node.spelling)
@@ -424,7 +466,8 @@
             classes[name] = {
                 'class_name' : name,
                 'short_name' : name[len('OrthancPlugin'):],
-                'methods' : [ ]
+                'methods' : [ ],
+                'custom_methods' : [ ],
             }
                     
 
@@ -443,6 +486,9 @@
 with open(os.path.join(ROOT, 'Class.mustache'), 'r') as f:
     template = f.read()
 
+    for method in CUSTOM_METHODS:
+        classes[method['class_name']]['custom_methods'].append(method)
+    
     for (key, value) in classes.items():
         with open(os.path.join(TARGET, 'sdk_%s.impl.h' % value['class_name']), 'w') as h:
             h.write(renderer.render(template, value))
--- a/CodeAnalysis/sdk.h.mustache	Fri May 28 10:53:18 2021 +0200
+++ b/CodeAnalysis/sdk.h.mustache	Thu Jun 10 18:19:27 2021 +0200
@@ -25,5 +25,19 @@
 PyMethodDef* GetOrthancSdkFunctions();
 
 {{#classes}}
-PyObject* Get{{class_name}}Type();
+PyTypeObject* Get{{class_name}}Type();
 {{/classes}}
+
+#include <orthanc/OrthancCPlugin.h>
+
+{{#classes}}
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  {{class_name}}* object_;
+  bool borrowed_;
+} sdk_{{class_name}}_Object;
+
+{{/classes}}
--- a/NEWS	Fri May 28 10:53:18 2021 +0200
+++ b/NEWS	Thu Jun 10 18:19:27 2021 +0200
@@ -1,6 +1,13 @@
 Pending changes in the mainline
 ===============================
 
+* New functions from the SDK wrapped in Python:
+  - orthanc.CreateDicom()
+  - orthanc.RegisterFindCallback()
+  - orthanc.RegisterMoveCallback()
+  - orthanc.FindQuery.GetFindQueryTagGroup()
+  - orthanc.FindQuery.GetFindQueryTagElement()
+
 
 Version 3.1 (2021-01-22)
 ========================
--- a/Sources/Autogenerated/sdk.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk.h	Thu Jun 10 18:19:27 2021 +0200
@@ -24,16 +24,136 @@
 void RegisterOrthancSdk(PyObject* module);
 PyMethodDef* GetOrthancSdkFunctions();
 
-PyObject* GetOrthancPluginRestOutputType();
-PyObject* GetOrthancPluginServerChunkedRequestReaderType();
-PyObject* GetOrthancPluginImageType();
-PyObject* GetOrthancPluginJobType();
-PyObject* GetOrthancPluginWorklistQueryType();
-PyObject* GetOrthancPluginStorageAreaType();
-PyObject* GetOrthancPluginFindMatcherType();
-PyObject* GetOrthancPluginDicomWebNodeType();
-PyObject* GetOrthancPluginWorklistAnswersType();
-PyObject* GetOrthancPluginFindAnswersType();
-PyObject* GetOrthancPluginPeersType();
-PyObject* GetOrthancPluginDicomInstanceType();
-PyObject* GetOrthancPluginFindQueryType();
+PyTypeObject* GetOrthancPluginRestOutputType();
+PyTypeObject* GetOrthancPluginServerChunkedRequestReaderType();
+PyTypeObject* GetOrthancPluginImageType();
+PyTypeObject* GetOrthancPluginJobType();
+PyTypeObject* GetOrthancPluginWorklistQueryType();
+PyTypeObject* GetOrthancPluginStorageAreaType();
+PyTypeObject* GetOrthancPluginFindMatcherType();
+PyTypeObject* GetOrthancPluginDicomWebNodeType();
+PyTypeObject* GetOrthancPluginWorklistAnswersType();
+PyTypeObject* GetOrthancPluginFindAnswersType();
+PyTypeObject* GetOrthancPluginPeersType();
+PyTypeObject* GetOrthancPluginDicomInstanceType();
+PyTypeObject* GetOrthancPluginFindQueryType();
+
+#include <orthanc/OrthancCPlugin.h>
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginRestOutput* object_;
+  bool borrowed_;
+} sdk_OrthancPluginRestOutput_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginServerChunkedRequestReader* object_;
+  bool borrowed_;
+} sdk_OrthancPluginServerChunkedRequestReader_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginImage* object_;
+  bool borrowed_;
+} sdk_OrthancPluginImage_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginJob* object_;
+  bool borrowed_;
+} sdk_OrthancPluginJob_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginWorklistQuery* object_;
+  bool borrowed_;
+} sdk_OrthancPluginWorklistQuery_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginStorageArea* object_;
+  bool borrowed_;
+} sdk_OrthancPluginStorageArea_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginFindMatcher* object_;
+  bool borrowed_;
+} sdk_OrthancPluginFindMatcher_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginDicomWebNode* object_;
+  bool borrowed_;
+} sdk_OrthancPluginDicomWebNode_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginWorklistAnswers* object_;
+  bool borrowed_;
+} sdk_OrthancPluginWorklistAnswers_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginFindAnswers* object_;
+  bool borrowed_;
+} sdk_OrthancPluginFindAnswers_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginPeers* object_;
+  bool borrowed_;
+} sdk_OrthancPluginPeers_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginDicomInstance* object_;
+  bool borrowed_;
+} sdk_OrthancPluginDicomInstance_Object;
+
+typedef struct 
+{
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  OrthancPluginFindQuery* object_;
+  bool borrowed_;
+} sdk_OrthancPluginFindQuery_Object;
+
--- a/Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomInstance.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,7 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginDicomInstance* object_;
-  bool borrowed_;
-} sdk_OrthancPluginDicomInstance_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginDicomInstance_OrthancPluginGetInstanceRemoteAet(
   sdk_OrthancPluginDicomInstance_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginDicomInstance_OrthancPluginGetInstanceSize(
@@ -57,6 +46,11 @@
   sdk_OrthancPluginDicomInstance_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginDicomInstance_OrthancPluginGetInstanceAdvancedJson(
   sdk_OrthancPluginDicomInstance_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginDicomInstance_Methods[] = {
@@ -595,7 +589,7 @@
 }
 
 
-PyObject* GetOrthancPluginDicomInstanceType()
+PyTypeObject* GetOrthancPluginDicomInstanceType()
 {
-  return (PyObject*) &sdk_OrthancPluginDicomInstance_Type;
+  return &sdk_OrthancPluginDicomInstance_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginDicomWebNode.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,12 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginDicomWebNode* object_;
-  bool borrowed_;
-} sdk_OrthancPluginDicomWebNode_Object;
+// Forward declaration of the autogenerated methods
+// End of forward declarations
 
 
-
-// Forward declaration of the methods
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginDicomWebNode_Methods[] = {
@@ -103,7 +97,7 @@
 }
 
 
-PyObject* GetOrthancPluginDicomWebNodeType()
+PyTypeObject* GetOrthancPluginDicomWebNodeType()
 {
-  return (PyObject*) &sdk_OrthancPluginDicomWebNode_Type;
+  return &sdk_OrthancPluginDicomWebNode_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindAnswers.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,22 +17,16 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginFindAnswers* object_;
-  bool borrowed_;
-} sdk_OrthancPluginFindAnswers_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginFindAnswers_OrthancPluginFindAddAnswer(
   sdk_OrthancPluginFindAnswers_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginFindAnswers_OrthancPluginFindMarkIncomplete(
   sdk_OrthancPluginFindAnswers_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginFindAnswers_Methods[] = {
@@ -182,7 +176,7 @@
 }
 
 
-PyObject* GetOrthancPluginFindAnswersType()
+PyTypeObject* GetOrthancPluginFindAnswersType()
 {
-  return (PyObject*) &sdk_OrthancPluginFindAnswers_Type;
+  return &sdk_OrthancPluginFindAnswers_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindMatcher.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,20 +17,14 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginFindMatcher* object_;
-  bool borrowed_;
-} sdk_OrthancPluginFindMatcher_Object;
+// Forward declaration of the autogenerated methods
+static PyObject *sdk_OrthancPluginFindMatcher_OrthancPluginFindMatcherIsMatch(
+  sdk_OrthancPluginFindMatcher_Object* self, PyObject *args);
+// End of forward declarations
 
 
-
-// Forward declaration of the methods
-static PyObject *sdk_OrthancPluginFindMatcher_OrthancPluginFindMatcherIsMatch(
-  sdk_OrthancPluginFindMatcher_Object* self, PyObject *args);
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginFindMatcher_Methods[] = {
@@ -157,7 +151,7 @@
 }
 
 
-PyObject* GetOrthancPluginFindMatcherType()
+PyTypeObject* GetOrthancPluginFindMatcherType()
 {
-  return (PyObject*) &sdk_OrthancPluginFindMatcher_Type;
+  return &sdk_OrthancPluginFindMatcher_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginFindQuery.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,24 +17,22 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginFindQuery* object_;
-  bool borrowed_;
-} sdk_OrthancPluginFindQuery_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginFindQuery_OrthancPluginGetFindQuerySize(
   sdk_OrthancPluginFindQuery_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginFindQuery_OrthancPluginGetFindQueryTagName(
   sdk_OrthancPluginFindQuery_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginFindQuery_OrthancPluginGetFindQueryValue(
   sdk_OrthancPluginFindQuery_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+extern PyObject *GetFindQueryTagGroup(
+  sdk_OrthancPluginFindQuery_Object* self, PyObject *args);
+extern PyObject *GetFindQueryTagElement(
+  sdk_OrthancPluginFindQuery_Object* self, PyObject *args);
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginFindQuery_Methods[] = {
@@ -47,6 +45,12 @@
   { "GetFindQueryValue",
     (PyCFunction) sdk_OrthancPluginFindQuery_OrthancPluginGetFindQueryValue, METH_VARARGS,
     "Generated from C function OrthancPluginGetFindQueryValue()" },
+  { "GetFindQueryTagGroup",
+    (PyCFunction) GetFindQueryTagGroup, METH_VARARGS,
+    "Generated from C function OrthancPluginGetFindQueryTag()" },
+  { "GetFindQueryTagElement",
+    (PyCFunction) GetFindQueryTagElement, METH_VARARGS,
+    "Generated from C function OrthancPluginGetFindQueryTag()" },
   { NULL }  /* Sentinel */
 };
 
@@ -211,7 +215,7 @@
 }
 
 
-PyObject* GetOrthancPluginFindQueryType()
+PyTypeObject* GetOrthancPluginFindQueryType()
 {
-  return (PyObject*) &sdk_OrthancPluginFindQuery_Type;
+  return &sdk_OrthancPluginFindQuery_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginImage.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginImage.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,7 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginImage* object_;
-  bool borrowed_;
-} sdk_OrthancPluginImage_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginImage_OrthancPluginGetImagePixelFormat(
   sdk_OrthancPluginImage_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginImage_OrthancPluginGetImageWidth(
@@ -41,6 +30,11 @@
   sdk_OrthancPluginImage_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginImage_OrthancPluginDrawText(
   sdk_OrthancPluginImage_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginImage_Methods[] = {
@@ -316,7 +310,7 @@
 }
 
 
-PyObject* GetOrthancPluginImageType()
+PyTypeObject* GetOrthancPluginImageType()
 {
-  return (PyObject*) &sdk_OrthancPluginImage_Type;
+  return &sdk_OrthancPluginImage_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginJob.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginJob.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,20 +17,14 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginJob* object_;
-  bool borrowed_;
-} sdk_OrthancPluginJob_Object;
+// Forward declaration of the autogenerated methods
+static PyObject *sdk_OrthancPluginJob_OrthancPluginSubmitJob(
+  sdk_OrthancPluginJob_Object* self, PyObject *args);
+// End of forward declarations
 
 
-
-// Forward declaration of the methods
-static PyObject *sdk_OrthancPluginJob_OrthancPluginSubmitJob(
-  sdk_OrthancPluginJob_Object* self, PyObject *args);
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginJob_Methods[] = {
@@ -168,7 +162,7 @@
 }
 
 
-PyObject* GetOrthancPluginJobType()
+PyTypeObject* GetOrthancPluginJobType()
 {
-  return (PyObject*) &sdk_OrthancPluginJob_Type;
+  return &sdk_OrthancPluginJob_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginPeers.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,7 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginPeers* object_;
-  bool borrowed_;
-} sdk_OrthancPluginPeers_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginPeers_OrthancPluginGetPeersCount(
   sdk_OrthancPluginPeers_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginPeers_OrthancPluginGetPeerName(
@@ -37,6 +26,11 @@
   sdk_OrthancPluginPeers_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginPeers_OrthancPluginGetPeerUserProperty(
   sdk_OrthancPluginPeers_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginPeers_Methods[] = {
@@ -268,7 +262,7 @@
 }
 
 
-PyObject* GetOrthancPluginPeersType()
+PyTypeObject* GetOrthancPluginPeersType()
 {
-  return (PyObject*) &sdk_OrthancPluginPeers_Type;
+  return &sdk_OrthancPluginPeers_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginRestOutput.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,7 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginRestOutput* object_;
-  bool borrowed_;
-} sdk_OrthancPluginRestOutput_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginRestOutput_OrthancPluginAnswerBuffer(
   sdk_OrthancPluginRestOutput_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginRestOutput_OrthancPluginCompressAndAnswerPngImage(
@@ -55,6 +44,11 @@
   sdk_OrthancPluginRestOutput_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginRestOutput_OrthancPluginSetHttpErrorDetails(
   sdk_OrthancPluginRestOutput_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginRestOutput_Methods[] = {
@@ -568,7 +562,7 @@
 }
 
 
-PyObject* GetOrthancPluginRestOutputType()
+PyTypeObject* GetOrthancPluginRestOutputType()
 {
-  return (PyObject*) &sdk_OrthancPluginRestOutput_Type;
+  return &sdk_OrthancPluginRestOutput_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginServerChunkedRequestReader.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,12 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginServerChunkedRequestReader* object_;
-  bool borrowed_;
-} sdk_OrthancPluginServerChunkedRequestReader_Object;
+// Forward declaration of the autogenerated methods
+// End of forward declarations
 
 
-
-// Forward declaration of the methods
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginServerChunkedRequestReader_Methods[] = {
@@ -103,7 +97,7 @@
 }
 
 
-PyObject* GetOrthancPluginServerChunkedRequestReaderType()
+PyTypeObject* GetOrthancPluginServerChunkedRequestReaderType()
 {
-  return (PyObject*) &sdk_OrthancPluginServerChunkedRequestReader_Type;
+  return &sdk_OrthancPluginServerChunkedRequestReader_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginStorageArea.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,18 +17,7 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginStorageArea* object_;
-  bool borrowed_;
-} sdk_OrthancPluginStorageArea_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginStorageArea_OrthancPluginStorageAreaCreate(
   sdk_OrthancPluginStorageArea_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginStorageArea_OrthancPluginStorageAreaRead(
@@ -37,6 +26,11 @@
   sdk_OrthancPluginStorageArea_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginStorageArea_OrthancPluginReconstructMainDicomTags(
   sdk_OrthancPluginStorageArea_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginStorageArea_Methods[] = {
@@ -279,7 +273,7 @@
 }
 
 
-PyObject* GetOrthancPluginStorageAreaType()
+PyTypeObject* GetOrthancPluginStorageAreaType()
 {
-  return (PyObject*) &sdk_OrthancPluginStorageArea_Type;
+  return &sdk_OrthancPluginStorageArea_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginWorklistAnswers.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,20 +17,14 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginWorklistAnswers* object_;
-  bool borrowed_;
-} sdk_OrthancPluginWorklistAnswers_Object;
+// Forward declaration of the autogenerated methods
+static PyObject *sdk_OrthancPluginWorklistAnswers_OrthancPluginWorklistMarkIncomplete(
+  sdk_OrthancPluginWorklistAnswers_Object* self, PyObject *args);
+// End of forward declarations
 
 
-
-// Forward declaration of the methods
-static PyObject *sdk_OrthancPluginWorklistAnswers_OrthancPluginWorklistMarkIncomplete(
-  sdk_OrthancPluginWorklistAnswers_Object* self, PyObject *args);
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginWorklistAnswers_Methods[] = {
@@ -139,7 +133,7 @@
 }
 
 
-PyObject* GetOrthancPluginWorklistAnswersType()
+PyTypeObject* GetOrthancPluginWorklistAnswersType()
 {
-  return (PyObject*) &sdk_OrthancPluginWorklistAnswers_Type;
+  return &sdk_OrthancPluginWorklistAnswers_Type;
 }
--- a/Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Autogenerated/sdk_OrthancPluginWorklistQuery.impl.h	Thu Jun 10 18:19:27 2021 +0200
@@ -17,22 +17,16 @@
  **/
 
 
-typedef struct 
-{
-  PyObject_HEAD
-
-  /* Type-specific fields go here. */
-  OrthancPluginWorklistQuery* object_;
-  bool borrowed_;
-} sdk_OrthancPluginWorklistQuery_Object;
-
-
-
-// Forward declaration of the methods
+// Forward declaration of the autogenerated methods
 static PyObject *sdk_OrthancPluginWorklistQuery_OrthancPluginWorklistIsMatch(
   sdk_OrthancPluginWorklistQuery_Object* self, PyObject *args);
 static PyObject *sdk_OrthancPluginWorklistQuery_OrthancPluginWorklistGetDicomQuery(
   sdk_OrthancPluginWorklistQuery_Object* self, PyObject *args);
+// End of forward declarations
+
+
+// Forward declaration of the custom methods
+// End of forward declarations
 
 
 static PyMethodDef sdk_OrthancPluginWorklistQuery_Methods[] = {
@@ -169,7 +163,7 @@
 }
 
 
-PyObject* GetOrthancPluginWorklistQueryType()
+PyTypeObject* GetOrthancPluginWorklistQueryType()
 {
-  return (PyObject*) &sdk_OrthancPluginWorklistQuery_Type;
+  return &sdk_OrthancPluginWorklistQuery_Type;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/DicomScpCallbacks.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -0,0 +1,396 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomScpCallbacks.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "Autogenerated/sdk.h"
+#include "ICallbackRegistration.h"
+#include "PythonString.h"
+
+
+static PyObject* findScpCallback_ = NULL;
+static PyObject* moveScpCallback_ = NULL;
+
+
+static PyObject *GetFindQueryTag(sdk_OrthancPluginFindQuery_Object* self,
+                                 PyObject *args,
+                                 bool isGroup)
+{
+  unsigned long index;
+  uint16_t group, element;
+  
+  if (self->object_ == NULL)
+  {
+    PyErr_SetString(PyExc_ValueError, "Invalid object");
+    return NULL;
+  }
+  else if (!PyArg_ParseTuple(args, "k", &index))
+  {
+    PyErr_SetString(PyExc_TypeError, "Index is missing");
+    return NULL;
+  }
+  else if (OrthancPluginGetFindQueryTag(OrthancPlugins::GetGlobalContext(), &group, &element, self->object_, index) !=
+           OrthancPluginErrorCode_Success)
+  {
+    PyErr_SetString(PyExc_ValueError, "Index is out of range");
+    return NULL;
+  }
+  else if (isGroup)
+  {
+    return PyLong_FromUnsignedLong(group);
+  }
+  else
+  {
+    return PyLong_FromUnsignedLong(element);
+  }
+}
+
+
+
+// Check out "CUSTOM_METHODS" in "../CodeAnalysis/ParseOrthancSDK.py"
+PyObject *GetFindQueryTagGroup(sdk_OrthancPluginFindQuery_Object* self, PyObject *args)
+{
+  return GetFindQueryTag(self, args, true);
+}
+
+
+PyObject *GetFindQueryTagElement(sdk_OrthancPluginFindQuery_Object* self, PyObject *args)
+{
+  return GetFindQueryTag(self, args, false);
+}
+
+
+static OrthancPluginErrorCode FindCallback(OrthancPluginFindAnswers *answers,
+                                           const OrthancPluginFindQuery *query,
+                                           const char *issuerAet,
+                                           const char *calledAet)
+{
+  try
+  {
+    PythonLock lock;
+
+    PyObject *pAnswers, *pQuery;
+    
+    {
+      PythonObject args(lock, PyTuple_New(2));
+      PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) answers));
+      PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */));
+      pAnswers = PyObject_CallObject((PyObject *) GetOrthancPluginFindAnswersType(), args.GetPyObject());
+    }
+    
+    {
+      PythonObject args(lock, PyTuple_New(2));
+      PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) query));
+      PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */));
+      pQuery = PyObject_CallObject((PyObject *) GetOrthancPluginFindQueryType(), args.GetPyObject());
+    }
+    
+    PythonString pIssuerAet(lock, issuerAet);
+    PythonString pCalledAet(lock, calledAet);
+
+    {
+      PythonObject args(lock, PyTuple_New(4));
+      PyTuple_SetItem(args.GetPyObject(), 0, pAnswers);
+      PyTuple_SetItem(args.GetPyObject(), 1, pQuery);
+      PyTuple_SetItem(args.GetPyObject(), 2, pIssuerAet.Release());
+      PyTuple_SetItem(args.GetPyObject(), 3, pCalledAet.Release());
+
+      assert(findScpCallback_ != NULL);
+      PythonObject result(lock, PyObject_CallObject(findScpCallback_, args.GetPyObject()));
+    }
+
+    return lock.CheckCallbackSuccess("Python C-FIND SCP callback");
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+
+
+class IMoveDriver : public boost::noncopyable
+{
+public:
+  virtual ~IMoveDriver()
+  {
+  }
+
+  virtual void Apply() = 0;
+};
+
+   
+static void* CreateMoveCallback(OrthancPluginResourceType resourceType,
+                                const char *patientId,
+                                const char *accessionNumber,
+                                const char *studyInstanceUid,
+                                const char *seriesInstanceUid,
+                                const char *sopInstanceUid,
+                                const char *originatorAet,
+                                const char *sourceAet,
+                                const char *targetAet,
+                                uint16_t originatorId)
+{
+  class Driver : public IMoveDriver
+  {
+  private:
+    OrthancPluginResourceType  resourceType_;
+    std::string                patientId_;
+    std::string                accessionNumber_;
+    std::string                studyInstanceUid_;
+    std::string                seriesInstanceUid_;
+    std::string                sopInstanceUid_;
+    std::string                originatorAet_;
+    std::string                sourceAet_;
+    std::string                targetAet_;
+    uint16_t                   originatorId_;
+    
+  public:
+    Driver(OrthancPluginResourceType resourceType,
+           const char *patientId,
+           const char *accessionNumber,
+           const char *studyInstanceUid,
+           const char *seriesInstanceUid,
+           const char *sopInstanceUid,
+           const char *originatorAet,
+           const char *sourceAet,
+           const char *targetAet,
+           uint16_t originatorId) :
+      resourceType_(resourceType),
+      originatorId_(originatorId)
+    {
+      if (patientId != NULL)
+      {
+        patientId_.assign(patientId);
+      }
+
+      if (accessionNumber != NULL)
+      {
+        accessionNumber_.assign(accessionNumber);
+      }
+      
+      if (studyInstanceUid != NULL)
+      {
+        studyInstanceUid_.assign(studyInstanceUid);
+      }
+
+      if (seriesInstanceUid != NULL)
+      {
+        seriesInstanceUid_.assign(seriesInstanceUid);
+      }
+
+      if (sopInstanceUid != NULL)
+      {
+        sopInstanceUid_.assign(sopInstanceUid);
+      }
+
+      if (originatorAet != NULL)
+      {
+        originatorAet_.assign(originatorAet);
+      }
+
+      if (sourceAet != NULL)
+      {
+        sourceAet_.assign(sourceAet);
+      }
+
+      if (targetAet != NULL)
+      {
+        targetAet_.assign(targetAet);
+      }
+    }
+
+    
+    virtual void Apply() ORTHANC_OVERRIDE
+    {
+      PythonLock lock;
+
+      PythonObject kw(lock, PyDict_New());
+
+      std::string level;
+      switch (resourceType_)
+      {
+        case OrthancPluginResourceType_Patient:
+          level = "PATIENT";
+          break;
+          
+        case OrthancPluginResourceType_Study:
+          level = "STUDY";
+          break;
+          
+        case OrthancPluginResourceType_Series:
+          level = "SERIES";
+          break;
+          
+        case OrthancPluginResourceType_Instance:
+          level = "INSTANCE";
+          break;
+
+        default:
+          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      }
+
+      {
+        PythonString tmp(lock, level);
+        PyDict_SetItemString(kw.GetPyObject(), "Level", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, patientId_);
+        PyDict_SetItemString(kw.GetPyObject(), "PatientID", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, accessionNumber_);
+        PyDict_SetItemString(kw.GetPyObject(), "AccessionNumber", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, studyInstanceUid_);
+        PyDict_SetItemString(kw.GetPyObject(), "StudyInstanceUID", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, seriesInstanceUid_);
+        PyDict_SetItemString(kw.GetPyObject(), "SeriesInstanceUID", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, sopInstanceUid_);
+        PyDict_SetItemString(kw.GetPyObject(), "SOPInstanceUID", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, originatorAet_);
+        PyDict_SetItemString(kw.GetPyObject(), "OriginatorAET", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, sourceAet_);
+        PyDict_SetItemString(kw.GetPyObject(), "SourceAET", tmp.Release());
+      }
+
+      {
+        PythonString tmp(lock, targetAet_);
+        PyDict_SetItemString(kw.GetPyObject(), "TargetAET", tmp.Release());
+      }
+
+      PyDict_SetItemString(kw.GetPyObject(), "OriginatorID", PyLong_FromUnsignedLong(originatorId_));
+
+      PythonObject args(lock, PyTuple_New(0));
+
+      assert(moveScpCallback_ != NULL);
+      PythonObject result(lock, PyObject_Call(moveScpCallback_, args.GetPyObject(), kw.GetPyObject()));
+
+      OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback");
+      if (code != OrthancPluginErrorCode_Success)
+      {
+        throw OrthancPlugins::PluginException(code);
+      }
+    }
+  };
+  
+  try
+  {
+    return new Driver(resourceType, patientId, accessionNumber, studyInstanceUid,
+                      seriesInstanceUid, sopInstanceUid, originatorAet, sourceAet,
+                      targetAet, originatorId);
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return NULL;
+  }
+}
+
+
+static uint32_t GetMoveSize(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+  return 1;
+}
+
+
+OrthancPluginErrorCode ApplyMove(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+  
+  try
+  {
+    reinterpret_cast<IMoveDriver*>(moveDriver)->Apply();
+    return OrthancPluginErrorCode_Success;
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return e.GetErrorCode();
+  }
+}
+
+   
+void FreeMove(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+  delete reinterpret_cast<IMoveDriver*>(moveDriver);
+}
+
+   
+PyObject* RegisterFindCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  class Registration : public ICallbackRegistration
+  {
+  public:
+    virtual void Register() ORTHANC_OVERRIDE
+    {
+      OrthancPluginRegisterFindCallback(OrthancPlugins::GetGlobalContext(), FindCallback);
+    }
+  };
+
+  Registration registration;
+  return ICallbackRegistration::Apply(
+    registration, args, findScpCallback_, "Python C-FIND SCP callback");
+}
+
+
+PyObject* RegisterMoveCallback(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+
+  class Registration : public ICallbackRegistration
+  {
+  public:
+    virtual void Register() ORTHANC_OVERRIDE
+    {
+      OrthancPluginRegisterMoveCallback(
+        OrthancPlugins::GetGlobalContext(), CreateMoveCallback, GetMoveSize, ApplyMove, FreeMove);
+    }
+  };
+
+  Registration registration;
+  return ICallbackRegistration::Apply(
+    registration, args, moveScpCallback_, "Python C-MOVE SCP callback");
+}
+
+
+void FinalizeDicomScpCallbacks()
+{
+  ICallbackRegistration::Unregister(findScpCallback_);
+  ICallbackRegistration::Unregister(moveScpCallback_);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/DicomScpCallbacks.h	Thu Jun 10 18:19:27 2021 +0200
@@ -0,0 +1,28 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PythonHeaderWrapper.h"
+
+PyObject* RegisterFindCallback(PyObject* module, PyObject* args);
+
+PyObject* RegisterMoveCallback(PyObject* module, PyObject* args);
+
+void FinalizeDicomScpCallbacks();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/ICallbackRegistration.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -0,0 +1,68 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ICallbackRegistration.h"
+
+#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+#include "PythonLock.h"
+
+PyObject *ICallbackRegistration::Apply(ICallbackRegistration& registration,
+                                       PyObject* args,
+                                       PyObject*& singletonCallback,
+                                       const std::string& details)
+{
+  // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
+  PyObject* callback = NULL;
+
+  if (!PyArg_ParseTuple(args, "O", &callback) ||
+      callback == NULL)
+  {
+    const std::string message = "Expected a callback function to register one " + details;
+    PyErr_SetString(PyExc_ValueError, message.c_str());
+    return NULL;
+  }
+  else if (singletonCallback != NULL)
+  {
+    const std::string message = "Can only register one " + details;
+    PyErr_SetString(PyExc_RuntimeError, message.c_str());
+    return NULL;
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("Registering one " + details);
+    registration.Register();
+    
+    singletonCallback = callback;
+    Py_XINCREF(singletonCallback);
+  
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+}
+
+
+void ICallbackRegistration::Unregister(PyObject*& singletonCallback)
+{
+  PythonLock lock;
+        
+  if (singletonCallback != NULL)
+  {
+    Py_XDECREF(singletonCallback);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sources/ICallbackRegistration.h	Thu Jun 10 18:19:27 2021 +0200
@@ -0,0 +1,43 @@
+/**
+ * Python plugin for Orthanc
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "PythonHeaderWrapper.h"
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+class ICallbackRegistration : public boost::noncopyable
+{
+public:
+  virtual ~ICallbackRegistration()
+  {
+  }
+
+  virtual void Register() = 0;
+
+  // The GIL must be locked
+  static PyObject *Apply(ICallbackRegistration& registration,
+                         PyObject* args,
+                         PyObject*& singletonCallback,
+                         const std::string& details);
+
+  static void Unregister(PyObject*& singletonCallback);
+};
--- a/Sources/OnStoredInstanceCallback.cpp	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/OnStoredInstanceCallback.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -43,7 +43,7 @@
     PythonObject args(lock, PyTuple_New(2));
     PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) instance));
     PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */));
-    PyObject *pInst = PyObject_CallObject(GetOrthancPluginDicomInstanceType(), args.GetPyObject());
+    PyObject *pInst = PyObject_CallObject((PyObject*) GetOrthancPluginDicomInstanceType(), args.GetPyObject());
     
     /**
      * Construct the arguments tuple (output, uri)
--- a/Sources/Plugin.cpp	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/Plugin.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -24,6 +24,7 @@
 // https://fr.slideshare.net/YiLungTsai/embed-python
 
 
+#include "DicomScpCallbacks.h"
 #include "IncomingHttpRequestFilter.h"
 #include "OnChangeCallback.h"
 #include "OnStoredInstanceCallback.h"
@@ -51,6 +52,54 @@
 
 
 
+PyObject* CreateDicom(PyObject* module, PyObject* args)
+{
+  // The GIL is locked at this point (no need to create "PythonLock")
+  const char* json = NULL;
+  PyObject* pixelData = NULL;
+  long int flags = 0;
+  
+  if (!PyArg_ParseTuple(args, "sOl", &json, &pixelData, &flags))
+  {
+    PyErr_SetString(PyExc_TypeError, "Please provide a JSON string, an orthanc.Image object, and a set of orthanc.CreateDicomFlags");
+    return NULL;
+  }
+  else
+  {
+    OrthancPluginImage* image = NULL;
+    
+    if (pixelData == Py_None)
+    {
+      // No pixel data
+    }
+    else if (Py_TYPE(pixelData) == GetOrthancPluginImageType())
+    {
+      image = reinterpret_cast<sdk_OrthancPluginImage_Object*>(pixelData)->object_;
+    }
+    else
+    {
+      PyErr_SetString(PyExc_TypeError, "Second parameter is not a valid orthanc.Image");
+      return NULL;
+    }
+
+    OrthancPlugins::MemoryBuffer buffer;
+    OrthancPluginErrorCode code = OrthancPluginCreateDicom(OrthancPlugins::GetGlobalContext(), *buffer, json, image,
+                                                           static_cast<OrthancPluginCreateDicomFlags>(flags));
+  
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return PyBytes_FromStringAndSize(buffer.GetData(), buffer.GetSize());
+    }
+    else
+    {
+      PyErr_SetString(PyExc_ValueError, "Cannot create the DICOM instance");
+      return NULL;  
+    }
+  }
+}
+
+
+
 static bool pythonEnabled_ = false;
 static std::string userScriptName_;
 static std::vector<PyMethodDef>  globalFunctions_;
@@ -91,11 +140,36 @@
     functions.push_back(f);
   }
 
+  
+  /**
+   * New in release 3.0
+   **/
+  
   {
-    // New in release 3.0
     PyMethodDef f = { "RegisterIncomingHttpRequestFilter", RegisterIncomingHttpRequestFilter, METH_VARARGS, "" };
     functions.push_back(f);
   }
+
+
+  /**
+   * New in release 3.1
+   **/
+  
+  {
+    PyMethodDef f = { "CreateDicom", CreateDicom, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+  
+  {
+    PyMethodDef f = { "RegisterFindCallback", RegisterFindCallback, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+  
+  {
+    PyMethodDef f = { "RegisterMoveCallback", RegisterMoveCallback, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+  
   
   /**
    * Append all the global functions that were automatically generated
@@ -298,6 +372,7 @@
       FinalizeRestCallbacks();
       FinalizeOnStoredInstanceCallback();
       FinalizeIncomingHttpRequestFilter();
+      FinalizeDicomScpCallbacks();
       
       PythonLock::GlobalFinalize();
     }
--- a/Sources/PythonLock.cpp	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/PythonLock.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -291,6 +291,23 @@
 
 
 
+OrthancPluginErrorCode PythonLock::CheckCallbackSuccess(const std::string& callbackDetails)
+{
+  std::string traceback;
+  
+  if (HasErrorOccurred(traceback))
+  {
+    OrthancPlugins::LogError("Error in the " + callbackDetails + ", traceback:\n" + traceback);
+    return OrthancPluginErrorCode_Plugin;
+  }
+  else
+  {
+    return OrthancPluginErrorCode_Success;
+  }
+}
+
+
+
 void PythonLock::GlobalInitialize(const std::string& moduleName,
                                   const std::string& exceptionName,
                                   ModuleFunctionsInstaller moduleFunctions,
--- a/Sources/PythonLock.h	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/PythonLock.h	Thu Jun 10 18:19:27 2021 +0200
@@ -47,6 +47,8 @@
 
   bool HasErrorOccurred(std::string& traceback);
 
+  OrthancPluginErrorCode CheckCallbackSuccess(const std::string& callbackDetails);
+
   static void GlobalInitialize(const std::string& moduleName,
                                const std::string& exceptionName,
                                ModuleFunctionsInstaller moduleFunctions,
--- a/Sources/RestCallbacks.cpp	Fri May 28 10:53:18 2021 +0200
+++ b/Sources/RestCallbacks.cpp	Thu Jun 10 18:19:27 2021 +0200
@@ -84,7 +84,7 @@
       PythonObject args(lock, PyTuple_New(2));
       PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) output));
       PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */));
-      PyObject *pInst = PyObject_CallObject(GetOrthancPluginRestOutputType(), args.GetPyObject());
+      PyObject *pInst = PyObject_CallObject((PyObject*) GetOrthancPluginRestOutputType(), args.GetPyObject());
 
 
       /**