diff --git a/efel/cppcore/cppcore.cpp b/efel/cppcore/cppcore.cpp index 4bd76bc0..c75b2931 100644 --- a/efel/cppcore/cppcore.cpp +++ b/efel/cppcore/cppcore.cpp @@ -40,6 +40,8 @@ #include #include +#include + #if PY_MAJOR_VERSION >= 3 #define IS_PY3K #endif @@ -47,6 +49,19 @@ extern cFeature* pFeature; +// Map of name -> feature +typedef std::map PyName2feature; +static PyName2feature PyFeaturesMap; + +static PyObject* +_getPyFunctionByName(string feature_name){ + PyName2feature::iterator py_feature = PyFeaturesMap.find(feature_name); + if(py_feature != PyFeaturesMap.end()){ + return py_feature->second; + } + return NULL; +} + static PyObject* CppCoreInitialize(PyObject* self, PyObject* args) { char* depfilename, *outfilename; @@ -112,7 +127,13 @@ static void PyList_from_vectorstring(vector input, PyObject* output) { } } -static PyObject* +static PyObject* +_call_python(string feature_name, PyObject *py_function, PyObject* output){ + PyObject* result = PyObject_CallFunctionObjArgs(py_function, output, NULL); + return result; +} + +static PyObject* _getfeature(PyObject* self, PyObject* args, const string &type) { char* feature_name; PyObject* py_values; @@ -122,24 +143,29 @@ _getfeature(PyObject* self, PyObject* args, const string &type) { return NULL; } - string feature_type = pFeature->featuretype(string(feature_name)); - - if (!type.empty() && feature_type != type){ - PyErr_SetString(PyExc_TypeError, "Feature type does not match"); - return NULL; - } - - if (feature_type == "int") { - vector values; - return_value = pFeature->getFeatureInt(string(feature_name), values); - PyList_from_vectorint(values, py_values); - } else if (feature_type == "double") { - vector values; - return_value = pFeature->getFeatureDouble(string(feature_name), values); - PyList_from_vectordouble(values, py_values); + PyObject* py_feature = _getPyFunctionByName(feature_name); + if (py_feature){ + return _call_python(feature_name, py_feature, py_values); } else { - PyErr_SetString(PyExc_TypeError, "Unknown feature name"); - return NULL; + string feature_type = pFeature->featuretype(string(feature_name)); + + if (!type.empty() && feature_type != type){ + PyErr_SetString(PyExc_TypeError, "Feature type does not match"); + return NULL; + } + + if (feature_type == "int") { + vector values; + return_value = pFeature->getFeatureInt(string(feature_name), values); + PyList_from_vectorint(values, py_values); + } else if (feature_type == "double") { + vector values; + return_value = pFeature->getFeatureDouble(string(feature_name), values); + PyList_from_vectordouble(values, py_values); + } else { + PyErr_SetString(PyExc_TypeError, "Unknown feature name"); + return NULL; + } } return Py_BuildValue("i", return_value); @@ -233,6 +259,34 @@ static PyObject* getgerrorstr(PyObject* self, PyObject* args) { return Py_BuildValue("s", pFeature->getGError().c_str()); } +static PyObject* +registerFeature(PyObject* self, PyObject* args) { + char *name = NULL; + PyObject *function = NULL; + PyObject *result = NULL; + + if (!PyArg_ParseTuple(args, "sO", &name, &function)) { + return NULL; + } + string feature_name = string(name); + + if (!PyCallable_Check(function)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return NULL; + } + + PyObject* py_feature = _getPyFunctionByName(feature_name); + if (py_feature){ + Py_DECREF(py_feature); + } + + Py_INCREF(function); + PyFeaturesMap[feature_name] = function; + + Py_INCREF(Py_None); + return Py_None; +} + static PyMethodDef CppCoreMethods[] = { {"Initialize", CppCoreInitialize, METH_VARARGS, "Initialise CppCore."}, @@ -258,6 +312,10 @@ static PyMethodDef CppCoreMethods[] = { {"getDistance", getDistance, METH_VARARGS, "Get the distance between a feature and experimental data"}, + + {"registerFeature", registerFeature, METH_VARARGS, + "Add a python function as an efeature"}, + {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/efel/tests/test_cppcore.py b/efel/tests/test_cppcore.py index b4216702..7b2c1683 100644 --- a/efel/tests/test_cppcore.py +++ b/efel/tests/test_cppcore.py @@ -154,3 +154,22 @@ def test_getFeature_non_existant(self): """cppcore: Testing failure exit code in getFeature""" import efel.cppcore efel.cppcore.getFeature("does_not_exist", list()) + + def test_registerFeature(self): + import efel.cppcore + + def getTwiceAP_Amp(output): + feature_values = list() + efel.cppcore.getFeature('AP_amplitude', feature_values) + output.extend(2*x for x in feature_values) + return len(feature_values) + + self.setup_data() + efel.cppcore.registerFeature('getTwiceAP_Amp', getTwiceAP_Amp) + features = list() + ret = efel.cppcore.getFeature('getTwiceAP_Amp', features) + nt.ok_(isinstance(features[0], float)) + nt.eq_(5, len(features)) + nt.ok_(np.allclose(2 * np.array([80.45724099440199, 80.46320199354948, 80.73300299176428, + 80.9965359926715, 81.87292599493423]), + features))