changeset 151:566df919b286 cmove2

new more detailed C-Move SCP callbacks
author Alain Mazy <am@osimis.io>
date Tue, 27 Feb 2024 16:13:08 +0100
parents 3c98306828de
children a6b4e0abe532
files Sources/DicomScpCallbacks.cpp Sources/DicomScpCallbacks.h Sources/ICallbackRegistration.cpp Sources/ICallbackRegistration.h Sources/Plugin.cpp
diffstat 5 files changed, 317 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/Sources/DicomScpCallbacks.cpp	Mon Jan 22 17:58:48 2024 +0100
+++ b/Sources/DicomScpCallbacks.cpp	Tue Feb 27 16:13:08 2024 +0100
@@ -30,6 +30,12 @@
 static PyObject* moveScpCallback_ = NULL;
 static PyObject* worklistScpCallback_ = NULL;
 
+// version 2 of Move callbacks
+static PyObject* createMoveScpDriverCallback_ = NULL;
+static PyObject* getMoveSizeCallback_ = NULL;
+static PyObject* applyMoveCallback_ = NULL;
+static PyObject* freeMoveCallback_ = NULL;
+
 
 static PyObject *GetFindQueryTag(sdk_OrthancPluginFindQuery_Object* self,
                                  PyObject *args,
@@ -399,6 +405,227 @@
 }
 
 
+static void* CreateMoveCallback2(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)
+{
+  assert(createMoveScpDriverCallback_ != NULL);
+
+  try
+  {
+    std::string _patientId, _accessionNumber, _studyInstanceUid, _seriesInstanceUid, _sopInstanceUid, _originatorAet, _sourceAet, _targetAet;
+    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);
+    }
+
+    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.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _patientId);
+      PyDict_SetItemString(kw.GetPyObject(), "PatientID", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _accessionNumber);
+      PyDict_SetItemString(kw.GetPyObject(), "AccessionNumber", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _studyInstanceUid);
+      PyDict_SetItemString(kw.GetPyObject(), "StudyInstanceUID", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _seriesInstanceUid);
+      PyDict_SetItemString(kw.GetPyObject(), "SeriesInstanceUID", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _sopInstanceUid);
+      PyDict_SetItemString(kw.GetPyObject(), "SOPInstanceUID", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _originatorAet);
+      PyDict_SetItemString(kw.GetPyObject(), "OriginatorAET", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _sourceAet);
+      PyDict_SetItemString(kw.GetPyObject(), "SourceAET", tmp.GetPyObject());
+    }
+
+    {
+      PythonString tmp(lock, _targetAet);
+      PyDict_SetItemString(kw.GetPyObject(), "TargetAET", tmp.GetPyObject());
+    }
+
+    {
+      PythonObject tmp(lock, PyLong_FromUnsignedLong(originatorId));
+      PyDict_SetItemString(kw.GetPyObject(), "OriginatorID", tmp.GetPyObject());
+    }
+
+    PythonObject args(lock, PyTuple_New(0));
+
+    // Note: the result is not attached to the PythonLock because we want it to survive after this call since 
+    // the result is the python move driver that will be passed as first argument to GetMoveSize, Apply and Free
+    std::unique_ptr<PyObject> result(PyObject_Call(createMoveScpDriverCallback_, args.GetPyObject(), kw.GetPyObject()));
+
+    OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Create)");
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      throw OrthancPlugins::PluginException(code);
+    }
+
+    // make sure it survives after this call
+    Py_INCREF(result.get());
+
+    return result.release();
+  }
+  catch (OrthancPlugins::PluginException& e)
+  {
+    return NULL;
+  }
+}
+
+
+static uint32_t GetMoveSize2(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+  assert(getMoveSizeCallback_ != NULL);
+
+  PythonLock lock;
+
+  PythonObject args(lock, PyTuple_New(1));
+  PyTuple_SetItem(args.GetPyObject(), 0, reinterpret_cast<PyObject*>(moveDriver));
+  Py_INCREF(moveDriver);  // because PyTuple_SetItem steals a reference and we need to keep the object alive
+
+  PythonObject result(lock, PyObject_CallObject(getMoveSizeCallback_, args.GetPyObject()));
+
+  OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (GetMoveSize)");
+  if (code != OrthancPluginErrorCode_Success)
+  {
+    throw OrthancPlugins::PluginException(code);
+  }
+
+  if (!PyLong_Check(result.GetPyObject()))
+  {
+    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadParameterType);
+  }
+
+  return static_cast<uint32_t>(PyLong_AsLong(result.GetPyObject()));
+}
+
+
+OrthancPluginErrorCode ApplyMove2(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+  assert(applyMoveCallback_ != NULL);
+
+  PythonLock lock;
+
+  PythonObject args(lock, PyTuple_New(1));
+  PyTuple_SetItem(args.GetPyObject(), 0, reinterpret_cast<PyObject*>(moveDriver));
+  Py_INCREF(moveDriver);  // because PyTuple_SetItem steals a reference and we need to keep the object alive
+
+  PythonObject result(lock, PyObject_CallObject(applyMoveCallback_, args.GetPyObject()));
+
+  OrthancPluginErrorCode code = lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Apply)");
+  return code;
+}
+
+   
+void FreeMove2(void *moveDriver)
+{
+  assert(moveDriver != NULL);
+
+  PythonLock lock;
+
+  PythonObject args(lock, PyTuple_New(1));
+  PyTuple_SetItem(args.GetPyObject(), 0, reinterpret_cast<PyObject*>(moveDriver));
+  Py_INCREF(moveDriver);  // because PyTuple_SetItem steals a reference and we need to keep the object alive
+
+  assert(freeMoveCallback_ != NULL);
+  PythonObject result(lock, PyObject_CallObject(freeMoveCallback_, args.GetPyObject()));
+
+  lock.CheckCallbackSuccess("Python C-MOVE SCP callback (Free)");
+
+  Py_DECREF(moveDriver);
+}
+
 
 OrthancPluginErrorCode WorklistCallback(OrthancPluginWorklistAnswers *answers,
                                         const OrthancPluginWorklistQuery *query,
@@ -487,6 +714,25 @@
 }
 
 
+PyObject* RegisterMoveCallback2(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(), CreateMoveCallback2, GetMoveSize2, ApplyMove2, FreeMove2);
+    }
+  };
+
+  Registration registration;
+  return ICallbackRegistration::Apply4(
+    registration, args, createMoveScpDriverCallback_, getMoveSizeCallback_, applyMoveCallback_, freeMoveCallback_, "Python C-MOVE SCP callback (2)");
+}
+
 PyObject* RegisterWorklistCallback(PyObject* module, PyObject* args)
 {
   // The GIL is locked at this point (no need to create "PythonLock")
@@ -511,4 +757,9 @@
   ICallbackRegistration::Unregister(findScpCallback_);
   ICallbackRegistration::Unregister(moveScpCallback_);
   ICallbackRegistration::Unregister(worklistScpCallback_);
+
+  ICallbackRegistration::Unregister(createMoveScpDriverCallback_);
+  ICallbackRegistration::Unregister(getMoveSizeCallback_);
+  ICallbackRegistration::Unregister(applyMoveCallback_);
+  ICallbackRegistration::Unregister(freeMoveCallback_);
 }
--- a/Sources/DicomScpCallbacks.h	Mon Jan 22 17:58:48 2024 +0100
+++ b/Sources/DicomScpCallbacks.h	Tue Feb 27 16:13:08 2024 +0100
@@ -26,6 +26,8 @@
 
 PyObject* RegisterMoveCallback(PyObject* module, PyObject* args);
 
+PyObject* RegisterMoveCallback2(PyObject* module, PyObject* args);
+
 PyObject* RegisterWorklistCallback(PyObject* module, PyObject* args);
 
 void FinalizeDicomScpCallbacks();
--- a/Sources/ICallbackRegistration.cpp	Mon Jan 22 17:58:48 2024 +0100
+++ b/Sources/ICallbackRegistration.cpp	Tue Feb 27 16:13:08 2024 +0100
@@ -97,6 +97,55 @@
   }
 }
 
+PyObject *ICallbackRegistration::Apply4(ICallbackRegistration& registration,
+                                        PyObject* args,
+                                        PyObject*& singletonCallback1,
+                                        PyObject*& singletonCallback2,
+                                        PyObject*& singletonCallback3,
+                                        PyObject*& singletonCallback4,
+                                        const std::string& details)
+{
+  // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
+  PyObject* callback1 = NULL;
+  PyObject* callback2 = NULL;
+  PyObject* callback3 = NULL;
+  PyObject* callback4 = NULL;
+
+  if (!PyArg_ParseTuple(args, "OOOO", &callback1, &callback2, &callback3, &callback4) ||
+      callback1 == NULL || callback2 == NULL || callback3 == NULL || callback4 == NULL)
+  {
+    const std::string message = "Expected 4 callback functions to register " + details;
+    PyErr_SetString(PyExc_ValueError, message.c_str());
+    return NULL;
+  }
+  else if (singletonCallback1 != NULL || singletonCallback2 != NULL || singletonCallback3 != NULL || singletonCallback4 != NULL)
+  {
+    const std::string message = "Can only register once for " + details;
+    PyErr_SetString(PyExc_RuntimeError, message.c_str());
+    return NULL;
+  }
+  else
+  {
+    OrthancPlugins::LogInfo("Registering callbacks " + details);
+    registration.Register();
+
+    singletonCallback1 = callback1;
+    Py_XINCREF(singletonCallback1);
+
+    singletonCallback2 = callback2;
+    Py_XINCREF(singletonCallback2);
+
+    singletonCallback3 = callback3;
+    Py_XINCREF(singletonCallback3);
+
+    singletonCallback4 = callback4;
+    Py_XINCREF(singletonCallback4);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+}
+
 
 void ICallbackRegistration::Unregister(PyObject*& singletonCallback)
 {
--- a/Sources/ICallbackRegistration.h	Mon Jan 22 17:58:48 2024 +0100
+++ b/Sources/ICallbackRegistration.h	Tue Feb 27 16:13:08 2024 +0100
@@ -47,5 +47,14 @@
                           PyObject*& singletonCallback2,
                           const std::string& details);
 
+  // The GIL must be locked
+  static PyObject *Apply4(ICallbackRegistration& registration,
+                          PyObject* args,
+                          PyObject*& singletonCallback1,
+                          PyObject*& singletonCallback2,
+                          PyObject*& singletonCallback3,
+                          PyObject*& singletonCallback4,
+                          const std::string& details);
+
   static void Unregister(PyObject*& singletonCallback);
 };
--- a/Sources/Plugin.cpp	Mon Jan 22 17:58:48 2024 +0100
+++ b/Sources/Plugin.cpp	Tue Feb 27 16:13:08 2024 +0100
@@ -384,7 +384,12 @@
     PyMethodDef f = { "RegisterMoveCallback", RegisterMoveCallback, METH_VARARGS, "" };
     functions.push_back(f);
   }
-  
+
+  {
+    PyMethodDef f = { "RegisterMoveCallback2", RegisterMoveCallback2, METH_VARARGS, "" };
+    functions.push_back(f);
+  }
+
   {
     PyMethodDef f = { "RegisterWorklistCallback", RegisterWorklistCallback, METH_VARARGS, "" };
     functions.push_back(f);