From 8c1d8eccdd73d24a83f096e9648aba2f8fe50eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Ma=C5=A1ek?= Date: Tue, 3 Apr 2018 20:16:34 +0200 Subject: [PATCH] Merge pull request #11207 from dan-masek:add_redirect_error * Add Python support for error message handlers. * Move the static variable to the only function that uses it. * Remove the optional param (user data), since this can already be handled by closures. * Correct the help string. * python: added redirectError test --- modules/python/src2/cv2.cpp | 52 ++++++++++++++++++++++++++++++++ modules/python/test/test_misc.py | 28 +++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 63b631d399..b65a3b2cda 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -1536,6 +1536,57 @@ PyObject* pyopencv_from(const Moments& m) #include "pyopencv_custom_headers.h" +static int OnError(int status, const char *func_name, const char *err_msg, const char *file_name, int line, void *userdata) +{ + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject *on_error = (PyObject*)userdata; + PyObject *args = Py_BuildValue("isssi", status, func_name, err_msg, file_name, line); + + PyObject *r = PyObject_Call(on_error, args, NULL); + if (r == NULL) { + PyErr_Print(); + } else { + Py_DECREF(r); + } + + Py_DECREF(args); + PyGILState_Release(gstate); + + return 0; // The return value isn't used +} + +static PyObject *pycvRedirectError(PyObject*, PyObject *args, PyObject *kw) +{ + const char *keywords[] = { "on_error", NULL }; + PyObject *on_error; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "O", (char**)keywords, &on_error)) + return NULL; + + if ((on_error != Py_None) && !PyCallable_Check(on_error)) { + PyErr_SetString(PyExc_TypeError, "on_error must be callable"); + return NULL; + } + + // Keep track of the previous handler parameter, so we can decref it when no longer used + static PyObject* last_on_error = NULL; + if (last_on_error) { + Py_DECREF(last_on_error); + last_on_error = NULL; + } + + if (on_error == Py_None) { + ERRWRAP2(redirectError(NULL)); + } else { + last_on_error = on_error; + Py_INCREF(last_on_error); + ERRWRAP2(redirectError(OnError, last_on_error)); + } + Py_RETURN_NONE; +} + static void OnMouse(int event, int x, int y, int flags, void* param) { PyGILState_STATE gstate; @@ -1683,6 +1734,7 @@ static int convert_to_char(PyObject *o, char *dst, const char *name = "no_name") #include "pyopencv_generated_funcs.h" static PyMethodDef special_methods[] = { + {"redirectError", (PyCFunction)pycvRedirectError, METH_VARARGS | METH_KEYWORDS, "redirectError(onError) -> None"}, #ifdef HAVE_OPENCV_HIGHGUI {"createTrackbar", pycvCreateTrackbar, METH_VARARGS, "createTrackbar(trackbarName, windowName, value, count, onChange) -> None"}, {"createButton", (PyCFunction)pycvCreateButton, METH_VARARGS | METH_KEYWORDS, "createButton(buttonName, onChange [, userData, buttonType, initialButtonState]) -> None"}, diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index 818c4537a9..abb66c13de 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -18,5 +18,33 @@ class Bindings(NewOpenCVTests): boost.getMaxDepth() # from ml::DTrees boost.isClassifier() # from ml::StatModel + + def test_redirectError(self): + try: + cv.imshow("", None) # This causes an assert + self.assertEqual("Dead code", 0) + except cv.error as e: + pass + + handler_called = [False] + def test_error_handler(status, func_name, err_msg, file_name, line): + handler_called[0] = True + + cv.redirectError(test_error_handler) + try: + cv.imshow("", None) # This causes an assert + self.assertEqual("Dead code", 0) + except cv.error as e: + self.assertEqual(handler_called[0], True) + pass + + cv.redirectError(None) + try: + cv.imshow("", None) # This causes an assert + self.assertEqual("Dead code", 0) + except cv.error as e: + pass + + if __name__ == '__main__': NewOpenCVTests.bootstrap()