# HG changeset patch # User Alain Mazy # Date 1709046788 -3600 # Node ID 566df919b286aed917da7ebd92c52ee3dec08f66 # Parent 3c98306828de089cc33386b68799616a0c3c80d6 new more detailed C-Move SCP callbacks diff -r 3c98306828de -r 566df919b286 Sources/DicomScpCallbacks.cpp --- 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 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(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(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(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(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_); } diff -r 3c98306828de -r 566df919b286 Sources/DicomScpCallbacks.h --- 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(); diff -r 3c98306828de -r 566df919b286 Sources/ICallbackRegistration.cpp --- 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) { diff -r 3c98306828de -r 566df919b286 Sources/ICallbackRegistration.h --- 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); }; diff -r 3c98306828de -r 566df919b286 Sources/Plugin.cpp --- 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);