Mercurial > hg > vampy
changeset 71:40a01bb24209 vampyhost
Pull apart some type conversion classes for possible use in VamPy Host
author | Chris Cannam |
---|---|
date | Thu, 20 Nov 2014 13:02:50 +0000 |
parents | 6c755f3e1173 |
children | ffaa1fb3d7de |
files | Makefile.linux PyPlugin.cpp PyPlugin.h PyTypeConversions.cpp PyTypeConversions.h PyTypeInterface.cpp PyTypeInterface.h |
diffstat | 7 files changed, 1244 insertions(+), 1176 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.linux Mon Nov 17 14:07:00 2014 +0000 +++ b/Makefile.linux Thu Nov 20 13:02:50 2014 +0000 @@ -7,16 +7,8 @@ LDFLAGS := -shared -Wl,-Bstatic -lvamp-sdk -Wl,-Bdynamic -lpython2.7 -lpthread -Wl,--version-script=vamp-plugin.map default: vampy.so -all: vampy.so vampymod.so -PyExtensionModule.a: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o - ar cr $@ $^ - -# The standard python extension is .so (even on the Mac) -vampymod.so: PyExtensionModule.o PyRealTime.o PyFeature.o PyParameterDescriptor.o PyOutputDescriptor.o PyFeatureSet.o - g++ $^ -o $@ $(LDFLAGS) - -vampy.so: PyPlugin.o PyPlugScanner.o vampy-main.o Mutex.o PyTypeInterface.o PyExtensionModule.a PyExtensionManager.o +vampy.so: PyRealTime.o PyFeature.o PyFeatureSet.o PyParameterDescriptor.o PyOutputDescriptor.o PyTypeConversions.o PyExtensionModule.o PyPlugin.o PyPlugScanner.o Mutex.o PyExtensionManager.o PyTypeInterface.o vampy-main.o g++ $^ -o $@ $(LDFLAGS) # Install plugin @@ -33,9 +25,52 @@ installplug : install cleanplug : clean +depend: + makedepend -Y -fMakefile.linux *.cpp *.h clean: rm -f *.o rm -f *.a rm -f *$(PLUGIN_EXT) +# DO NOT DELETE + +Mutex.o: Mutex.h +PyExtensionManager.o: PyExtensionModule.h PyRealTime.h PyFeature.h +PyExtensionManager.o: PyFeatureSet.h PyParameterDescriptor.h +PyExtensionManager.o: PyOutputDescriptor.h PyExtensionManager.h Debug.h +PyExtensionModule.o: PyExtensionModule.h PyRealTime.h PyFeature.h +PyExtensionModule.o: PyFeatureSet.h PyParameterDescriptor.h +PyExtensionModule.o: PyOutputDescriptor.h Debug.h +PyFeature.o: PyExtensionModule.h PyRealTime.h PyFeature.h PyFeatureSet.h +PyFeature.o: PyParameterDescriptor.h PyOutputDescriptor.h +PyFeatureSet.o: PyFeatureSet.h +PyOutputDescriptor.o: PyOutputDescriptor.h PyTypeInterface.h +PyOutputDescriptor.o: PyExtensionModule.h PyRealTime.h PyFeature.h +PyOutputDescriptor.o: PyFeatureSet.h PyParameterDescriptor.h +PyParameterDescriptor.o: PyParameterDescriptor.h PyTypeInterface.h +PyParameterDescriptor.o: PyExtensionModule.h PyRealTime.h PyFeature.h +PyParameterDescriptor.o: PyFeatureSet.h PyOutputDescriptor.h +PyPlugin.o: PyPlugin.h PyExtensionModule.h PyRealTime.h PyFeature.h +PyPlugin.o: PyFeatureSet.h PyParameterDescriptor.h PyOutputDescriptor.h +PyPlugin.o: PyTypeInterface.h Mutex.h Debug.h +PyPlugScanner.o: PyPlugScanner.h +PyRealTime.o: PyRealTime.h +PyTypeConversions.o: PyTypeConversions.h PyRealTime.h PyExtensionModule.h +PyTypeConversions.o: PyFeature.h PyFeatureSet.h PyParameterDescriptor.h +PyTypeConversions.o: PyOutputDescriptor.h +PyTypeInterface.o: PyTypeInterface.h PyExtensionModule.h PyRealTime.h +PyTypeInterface.o: PyFeature.h PyFeatureSet.h PyParameterDescriptor.h +PyTypeInterface.o: PyOutputDescriptor.h +vampy-main.o: PyPlugScanner.h PyPlugin.h PyExtensionModule.h PyRealTime.h +vampy-main.o: PyFeature.h PyFeatureSet.h PyParameterDescriptor.h +vampy-main.o: PyOutputDescriptor.h PyTypeInterface.h Mutex.h +vampy-main.o: PyExtensionManager.h Debug.h +PyExtensionModule.o: PyRealTime.h PyFeature.h PyFeatureSet.h +PyExtensionModule.o: PyParameterDescriptor.h PyOutputDescriptor.h +PyPlugin.o: PyExtensionModule.h PyRealTime.h PyFeature.h PyFeatureSet.h +PyPlugin.o: PyParameterDescriptor.h PyOutputDescriptor.h PyTypeInterface.h +PyPlugin.o: Mutex.h +PyTypeInterface.o: PyExtensionModule.h PyRealTime.h PyFeature.h +PyTypeInterface.o: PyFeatureSet.h PyParameterDescriptor.h +PyTypeInterface.o: PyOutputDescriptor.h
--- a/PyPlugin.cpp Mon Nov 17 14:07:00 2014 +0000 +++ b/PyPlugin.cpp Thu Nov 20 13:02:50 2014 +0000 @@ -87,8 +87,12 @@ if (m_debugFlag && m_quitOnErrorFlag) cerr << "Quit on type error ON for: " << m_class << endl; if (m_debugFlag && st_flag) cerr << "Strict type conversion ON for: " << m_class << endl; + m_ti.setStrictTypingFlag(st_flag); + m_tc.setStrictTypingFlag(st_flag); + m_ti.setNumpyInstalled(m_numpyInstalled); + m_tc.setNumpyInstalled(m_numpyInstalled); } @@ -318,8 +322,8 @@ { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} } else { - rValue = m_ti.PyValue_To_Bool(pyValue); - if (m_ti.error) { + rValue = m_tc.PyValue_To_Bool(pyValue); + if (m_tc.error) { Py_CLEAR(pyValue); typeErrorHandler(flagName); rValue = defValue; @@ -341,8 +345,8 @@ { if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} } else { - rValue |= (int) m_ti.PyValue_To_Size_t(pyValue); - if (m_ti.error) { + rValue |= (int) m_tc.PyValue_To_Size_t(pyValue); + if (m_tc.error) { Py_CLEAR(pyValue); typeErrorHandler(flagName); rValue = defValue; @@ -430,8 +434,10 @@ PyPlugin::typeErrorHandler(const char *method, bool process) const { bool strict = false; - while (m_ti.error) { - PyTypeInterface::ValueError e = m_ti.getError(); + while (m_tc.error || m_ti.error) { + ValueError e; + if (m_tc.error) e = m_tc.getError(); + else e = m_ti.getError(); #ifdef HAVE_NUMPY // disable the process completely if numpy types are returned // but a compatible version was not loaded.
--- a/PyPlugin.h Mon Nov 17 14:07:00 2014 +0000 +++ b/PyPlugin.h Thu Nov 20 13:02:50 2014 +0000 @@ -9,40 +9,6 @@ */ -/* - Vamp - - An API for audio analysis and feature extraction plugins. - - Centre for Digital Music, Queen Mary, University of London. - Copyright 2006 Chris Cannam. - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, copy, - modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR - ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the names of the Centre for - Digital Music; Queen Mary, University of London; and Chris Cannam - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in this Software without prior written - authorization. -*/ - #ifndef _PYTHON_WRAPPER_PLUGIN_H_ #define _PYTHON_WRAPPER_PLUGIN_H_ @@ -55,6 +21,7 @@ #include <Python.h> #include "PyExtensionModule.h" #include "PyTypeInterface.h" +#include "PyTypeConversions.h" #include "vamp-sdk/Plugin.h" #include "Mutex.h" @@ -117,6 +84,7 @@ PyObject *m_pyProcess; PyObject *m_pyProcessCallable; mutable InputDomain m_inputDomain; + PyTypeConversions m_tc; PyTypeInterface m_ti; int m_vampyFlags; bool m_quitOnErrorFlag; @@ -187,7 +155,7 @@ } /// prepare arguments for fast method call - PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyMethod = m_tc.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(1); if (!(pyArgs && pyCallable && pyMethod)) { @@ -198,8 +166,8 @@ return rValue; } - PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); - if (m_ti.error) { + PyObject *pyArg1 = m_tc.PyValue_From_CValue(arg1); + if (m_tc.error) { cerr << PLUGIN_ERROR << "Failed to convert argument for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod); @@ -253,7 +221,7 @@ } /// prepare arguments for fast method call - PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyMethod = m_tc.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(2); if (!(pyArgs && pyCallable && pyMethod)) { @@ -264,9 +232,9 @@ return rValue; } - PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); - PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); - if (m_ti.error) { + PyObject *pyArg1 = m_tc.PyValue_From_CValue(arg1); + PyObject *pyArg2 = m_tc.PyValue_From_CValue(arg2); + if (m_tc.error) { cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod); @@ -325,7 +293,7 @@ } /// prepare arguments for fast method call - PyObject *pyMethod = m_ti.PyValue_From_CValue(method); + PyObject *pyMethod = m_tc.PyValue_From_CValue(method); PyObject *pyCallable = PyObject_GetAttr(m_pyInstance,pyMethod); PyObject* pyArgs = PyTuple_New(3); if (!(pyArgs && pyCallable && pyMethod)) { @@ -336,10 +304,10 @@ return rValue; } - PyObject *pyArg1 = m_ti.PyValue_From_CValue(arg1); - PyObject *pyArg2 = m_ti.PyValue_From_CValue(arg2); - PyObject *pyArg3 = m_ti.PyValue_From_CValue(arg3); - if (m_ti.error) { + PyObject *pyArg1 = m_tc.PyValue_From_CValue(arg1); + PyObject *pyArg2 = m_tc.PyValue_From_CValue(arg2); + PyObject *pyArg3 = m_tc.PyValue_From_CValue(arg3); + if (m_tc.error) { cerr << PLUGIN_ERROR << "Failed to convert arguments for calling method." << endl; typeErrorHandler(method); Py_CLEAR(pyMethod);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyTypeConversions.cpp Thu Nov 20 13:02:50 2014 +0000 @@ -0,0 +1,818 @@ +/* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +#include <Python.h> + +#include "PyTypeConversions.h" + +#include <math.h> +#include <float.h> +#include <limits.h> +#ifndef SIZE_T_MAX +#define SIZE_T_MAX ((size_t) -1) +#endif + +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +/* Note: NO FUNCTION IN THIS CLASS SHOULD ALTER REFERENCE COUNTS + (EXCEPT FOR TEMPORARY PYTHON OBJECTS)! */ + +PyTypeConversions::PyTypeConversions() : + m_strict(false), + m_error(false), + m_numpyInstalled(false), + error(m_error) // const public reference for easy access +{ +} + +PyTypeConversions::~PyTypeConversions() +{ +} + +/// floating point numbers (TODO: check numpy.float128) +float +PyTypeConversions::PyValue_To_Float(PyObject* pyValue) const +{ + // convert float + if (pyValue && PyFloat_Check(pyValue)) + //TODO: check for limits here (same on most systems) + return (float) PyFloat_AS_DOUBLE(pyValue); + + if (pyValue == NULL) + { + setValueError("Error while converting object " + PyValue_Get_TypeName(pyValue) + " to float. ",m_strict); + return 0.0; + } + + // in strict mode we will not try harder + if (m_strict) { + setValueError("Strict conversion error: object" + PyValue_Get_TypeName(pyValue) +" is not float.",m_strict); + return 0.0; + } + + // convert other objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + PyObject* pyFloat = PyNumber_Float(pyValue); // new ref + if (!pyFloat) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting " + PyValue_Get_TypeName(pyValue) + " object to float.",m_strict); + return 0.0; + } + float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); + Py_DECREF(pyFloat); + return rValue; + } +/* + // convert other objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + // PEP353: Py_ssize_t is size_t but signed ! + // This will work up to numpy.float64 + Py_ssize_t rValue = PyNumber_AsSsize_t(pyValue,NULL); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0.0; + } + if (rValue > (Py_ssize_t)FLT_MAX || rValue < (Py_ssize_t)FLT_MIN) + { + setValueError("Overflow error. Object can not be converted to float.",m_strict); + return 0.0; + } + return (float) rValue; + } +*/ + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyFloat = PyFloat_FromString(pyValue,NULL); + if (!pyFloat) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String value can not be converted to float.",m_strict); + return 0.0; + } + float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + Py_CLEAR(pyFloat); + setValueError("Error while converting float object.",m_strict); + return 0.0; + } + Py_DECREF(pyFloat); + return rValue; + } + + // convert the first element of any iterable sequence (for convenience and backwards compatibility) + if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + float rValue = this->PyValue_To_Float(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + std::string msg = "Could not convert sequence element to float. "; + setValueError(msg,m_strict); + return 0.0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + PyValue_Get_TypeName(pyValue) + " to float is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_Float failed. " << msg << endl; +#endif + return 0.0; +} + +/// size_t (unsigned integer types) +size_t +PyTypeConversions::PyValue_To_Size_t(PyObject* pyValue) const +{ + // convert objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + if (m_strict && !PyInt_Check(pyValue) && !PyLong_Check(pyValue)) + setValueError("Strict conversion error: object is not integer type.",m_strict); + // Note: this function handles Bool,Int,Long,Float + // speed is not critical in the use of this type by Vamp + // PEP353: Py_ssize_t is size_t but signed ! + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0; + } + // this test is nonsense -- neither part can occur + // owing to range of data types -- size_t is at least + // as big as long, and unsigned is always non-negative +/* + if ((unsigned long)rValue > SIZE_T_MAX || (unsigned long)rValue < 0) + { + setValueError("Overflow error. Object can not be converted to size_t.",m_strict); + return 0; + } +*/ + return (size_t) rValue; + } + + // in strict mode we will not try harder and throw an exception + // then the caller should decide what to do with it + if (m_strict) { + setValueError("Strict conversion error: object is not integer.",m_strict); + return 0; + } + + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyLong = PyNumber_Long(pyValue); + if (!pyLong) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String object can not be converted to size_t.",m_strict); + return 0; + } + size_t rValue = this->PyValue_To_Size_t(pyLong); + if (!m_error) { + Py_DECREF(pyLong); + return rValue; + } else { + Py_CLEAR(pyLong); + setValueError ("Error converting string to size_t.",m_strict); + return 0; + } + } + + // convert the first element of iterable sequences + if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + size_t rValue = this->PyValue_To_Size_t(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to size_t. ",m_strict); + return 0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to size_t is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_Size_t failed. " << msg << endl; +#endif + return 0; +} + +/// long and int +long +PyTypeConversions::PyValue_To_Long(PyObject* pyValue) const +{ + // most common case: convert int (faster) + if (pyValue && PyInt_Check(pyValue)) { + // if the object is not NULL and verified, this macro just extracts the value. + return PyInt_AS_LONG(pyValue); + } + + // long + if (PyLong_Check(pyValue)) { + long rValue = PyLong_AsLong(pyValue); + if (PyErr_Occurred()) { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting long object.",m_strict); + return 0; + } + return rValue; + } + + if (m_strict) { + setValueError("Strict conversion error: object is not integer or long integer.",m_strict); + return 0; + } + + // convert all objects supporting the number protocol + if (PyNumber_Check(pyValue)) + { + // Note: this function handles Bool,Int,Long,Float + // PEP353: Py_ssize_t is size_t but signed ! + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError("Error while converting integer object.",m_strict); + return 0; + } + if (rValue > LONG_MAX || rValue < LONG_MIN) + { + setValueError("Overflow error. Object can not be converted to size_t.",m_strict); + return 0; + } + return (long) rValue; + } + + // convert string + if (PyString_Check(pyValue)) + { + PyObject* pyLong = PyNumber_Long(pyValue); + if (!pyLong) + { + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + setValueError("String object can not be converted to long.",m_strict); + return 0; + } + long rValue = this->PyValue_To_Long(pyLong); + if (!m_error) { + Py_DECREF(pyLong); + return rValue; + } else { + Py_CLEAR(pyLong); + setValueError ("Error converting string to long.",m_strict); + return 0; + } + } + + // convert the first element of iterable sequences + if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) + { + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + size_t rValue = this->PyValue_To_Long(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to long. ",m_strict); + return 0; + } + } + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to long is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_Long failed. " << msg << endl; +#endif + return 0; +} + + +bool +PyTypeConversions::PyValue_To_Bool(PyObject* pyValue) const +{ + // convert objects supporting the number protocol + // Note: PyBool is a subclass of PyInt + if (PyNumber_Check(pyValue)) + { + if (m_strict && !PyBool_Check(pyValue)) + setValueError + ("Strict conversion error: object is not boolean type.",m_strict); + + // Note: this function handles Bool,Int,Long,Float + Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); + if (PyErr_Occurred()) + { + PyErr_Print(); PyErr_Clear(); + setValueError ("Error while converting boolean object.",m_strict); + } + if (rValue != 1 && rValue != 0) + { + setValueError ("Overflow error. Object can not be converted to boolean.",m_strict); + } + return (bool) rValue; + } + + if (m_strict) { + setValueError ("Strict conversion error: object is not numerical type.",m_strict); + return false; + } + + // convert iterables: the rule is the same as in the interpreter: + // empty sequence evaluates to False, anything else is True + if (PySequence_Check(pyValue)) + { + return PySequence_Size(pyValue)?true:false; + } + + // give up + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to boolean is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_Bool failed. " << msg << endl; +#endif + return false; +} + +/// string and objects that support .__str__() +/// TODO: check unicode objects +std::string +PyTypeConversions::PyValue_To_String(PyObject* pyValue) const +{ + // convert string + if (PyString_Check(pyValue)) + { + char *cstr = PyString_AS_STRING(pyValue); + if (!cstr) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting string object.",m_strict); + return std::string(); + } + return std::string(cstr); + } + // TODO: deal with unicode here (argh!) + + // in strict mode we will not try harder + if (m_strict) { + setValueError("Strict conversion error: object is not string.",m_strict); + return std::string(); + } + + // accept None as empty string + if (pyValue == Py_None) return std::string(); + + // convert list or tuple: empties are turned into empty strings conventionally + if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) + { + if (!PySequence_Size(pyValue)) return std::string(); + PyObject* item = PySequence_GetItem(pyValue,0); + if (item) + { + std::string rValue = this->PyValue_To_String(item); + if (!m_error) { + Py_DECREF(item); + return rValue; + } else { + Py_CLEAR(item); + setValueError("Could not convert sequence element to string.",m_strict); + return std::string(); + } + } + } + + // convert any other object that has .__str__() or .__repr__() + PyObject* pyString = PyObject_Str(pyValue); + if (pyString && !PyErr_Occurred()) + { + std::string rValue = this->PyValue_To_String(pyString); + if (!m_error) { + Py_DECREF(pyString); + return rValue; + } else { + Py_CLEAR(pyString); + std::string msg = "Object " + this->PyValue_Get_TypeName(pyValue) +" can not be represented as string. "; + setValueError (msg,m_strict); + return std::string(); + } + } + + // give up + PyErr_Print(); PyErr_Clear(); + std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to string is not possible."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_String failed. " << msg << endl; +#endif + return std::string(); +} + +/* C Values to Py Values */ + + +PyObject* +PyTypeConversions::PyValue_From_CValue(const char* cValue) const +{ + // returns new reference +#ifdef _DEBUG + if (!cValue) { + std::string msg = "PyTypeConversions::PyValue_From_CValue: Null pointer encountered while converting from const char* ."; + cerr << msg << endl; + setValueError(msg,m_strict); + return NULL; + } +#endif + PyObject *pyValue = PyString_FromString(cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from char* or string.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_From_CValue: Interpreter failed to convert from const char*" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeConversions::PyValue_From_CValue(size_t cValue) const +{ + // returns new reference + PyObject *pyValue = PyInt_FromSsize_t((Py_ssize_t)cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from size_t.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_From_CValue: Interpreter failed to convert from size_t" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeConversions::PyValue_From_CValue(double cValue) const +{ + // returns new reference + PyObject *pyValue = PyFloat_FromDouble(cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from float or double.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_From_CValue: Interpreter failed to convert from float or double" << endl; +#endif + return NULL; + } + return pyValue; +} + +PyObject* +PyTypeConversions::PyValue_From_CValue(bool cValue) const +{ + // returns new reference + PyObject *pyValue = PyBool_FromLong((long)cValue); + if (!pyValue) + { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + setValueError("Error while converting from bool.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_From_CValue: Interpreter failed to convert from bool" << endl; +#endif + return NULL; + } + return pyValue; +} + + +/* Sequence Types to C++ Types */ + +//convert Python list to C++ vector of strings +std::vector<std::string> +PyTypeConversions::PyValue_To_StringVector (PyObject *pyList) const +{ + + std::vector<std::string> Output; + std::string ListElement; + PyObject *pyString = NULL; + + if (PyList_Check(pyList)) { + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) { + //Get next list item (Borrowed Reference) + pyString = PyList_GET_ITEM(pyList,i); + ListElement = (string) PyString_AsString(PyObject_Str(pyString)); + Output.push_back(ListElement); + } + return Output; + } + +// #ifdef _DEBUG +// cerr << "PyTypeConversions::PyValue_To_StringVector: Warning: Value is not list of strings." << endl; +// #endif + + /// Assume a single value that can be casted as string + /// this allows to write e.g. Feature.label = 5.2 instead of ['5.2'] + Output.push_back(PyValue_To_String(pyList)); + if (m_error) { + std::string msg = "Value is not list of strings nor can be casted as string. "; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_StringVector failed. " << msg << endl; +#endif + } + return Output; +} + +//convert PyFeature.value (typically a list or numpy array) to C++ vector of floats +std::vector<float> +PyTypeConversions::PyValue_To_FloatVector (PyObject *pyValue) const +{ + +#ifdef HAVE_NUMPY +if (m_numpyInstalled) +{ + // there are four types of values we may receive from a numpy process: + // * a python scalar, + // * an array scalar, (e.g. numpy.float32) + // * an array with nd = 0 (0D array) + // * an array with nd > 0 + + /// check for scalars + if (PyArray_CheckScalar(pyValue) || PyFloat_Check(pyValue)) { + + std::vector<float> Output; + + // we rely on the behaviour the scalars are either floats + // or support the number protocol + // TODO: a potential optimisation is to handle them directly + Output.push_back(PyValue_To_Float(pyValue)); + return Output; + } + + /// numpy array + if (PyArray_CheckExact(pyValue)) + return PyArray_To_FloatVector(pyValue); +} +#endif + + /// python list of floats (backward compatible) + if (PyList_Check(pyValue)) { + return PyList_To_FloatVector(pyValue); + } + + std::vector<float> Output; + + /// finally assume a single value supporting the number protocol + /// this allows to write e.g. Feature.values = 5 instead of [5.00] + Output.push_back(PyValue_To_Float(pyValue)); + if (m_error) { + std::string msg = "Value is not list or array of floats nor can be casted as float. "; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyValue_To_FloatVector failed. " << msg << endl; +#endif + } + return Output; +} + +//convert a list of python floats +std::vector<float> +PyTypeConversions::PyList_To_FloatVector (PyObject *inputList) const +{ + std::vector<float> Output; + +#ifdef _DEBUG + // This is a low level function normally called from + // PyValue_To_FloatVector(). Checking for list is not required. + if (!PyList_Check(inputList)) { + std::string msg = "Value is not list."; + setValueError(msg,true); + cerr << "PyTypeConversions::PyList_To_FloatVector failed. " << msg << endl; + return Output; + } +#endif + + float ListElement; + PyObject *pyFloat = NULL; + PyObject **pyObjectArray = PySequence_Fast_ITEMS(inputList); + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(inputList); ++i) { + + // pyFloat = PyList_GET_ITEM(inputList,i); + pyFloat = pyObjectArray[i]; + +#ifdef _DEBUG + if (!pyFloat) { + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + cerr << "PyTypeConversions::PyList_To_FloatVector: Could not obtain list element: " + << i << " PyList_GetItem returned NULL! Skipping value." << endl; + continue; + } +#endif + + // ListElement = (float) PyFloat_AS_DOUBLE(pyFloat); + ListElement = PyValue_To_Float(pyFloat); + + +#ifdef _DEBUG_VALUES + cerr << "value: " << ListElement << endl; +#endif + Output.push_back(ListElement); + } + return Output; +} + +// if numpy is not installed this will not be called, +// therefor we do not check again +#ifdef HAVE_NUMPY +std::vector<float> +PyTypeConversions::PyArray_To_FloatVector (PyObject *pyValue) const +{ + std::vector<float> Output; + +#ifdef _DEBUG + // This is a low level function, normally called from + // PyValue_To_FloatVector(). Checking the array here is not required. + if (!PyArray_Check(pyValue)) { + std::string msg = "Object has no array conversions."; + setValueError(msg,true); + cerr << "PyTypeConversions::PyArray_To_FloatVector failed. " << msg << endl; + return Output; + } +#endif + + PyArrayObject* pyArray = (PyArrayObject*) pyValue; + PyArray_Descr* descr = PyArray_DESCR(pyArray); + + /// check raw data and descriptor pointers + if (PyArray_DATA(pyArray) == 0 || descr == 0) { + std::string msg = "NumPy array with NULL data or descriptor pointer encountered."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyArray_To_FloatVector failed. Error: " << msg << endl; +#endif + return Output; + } + + /// check dimensions + if (PyArray_NDIM(pyArray) != 1) { + std::string msg = "NumPy array must be a one dimensional vector."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyArray_To_FloatVector failed. Error: " << msg << " Dims: " << (int) PyArray_NDIM(pyArray) << endl; +#endif + return Output; + } + +#ifdef _DEBUG_VALUES + cerr << "PyTypeConversions::PyArray_To_FloatVector: Numpy array verified." << endl; +#endif + + /// check strides (useful if array is not continuous) + size_t strides = *((size_t*) PyArray_STRIDES(pyArray)); + + /// convert the array + switch (descr->type_num) + { + case NPY_FLOAT : // dtype='float32' + return PyArray_Convert<float,float>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); + case NPY_DOUBLE : // dtype='float64' + return PyArray_Convert<float,double>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); + case NPY_INT : // dtype='int' + return PyArray_Convert<float,int>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); + case NPY_LONG : // dtype='long' + return PyArray_Convert<float,long>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); + default : + std::string msg = "Unsupported value type in NumPy array object."; + setValueError(msg,m_strict); +#ifdef _DEBUG + cerr << "PyTypeConversions::PyArray_To_FloatVector failed. Error: " << msg << endl; +#endif + return Output; + } +} +#endif + + + +/* Error handling */ + +void +PyTypeConversions::setValueError (std::string message, bool strict) const +{ + m_error = true; + m_errorQueue.push(ValueError(message,strict)); +} + +/// return a reference to the last error or creates a new one. +ValueError& +PyTypeConversions::lastError() const +{ + m_error = false; + if (!m_errorQueue.empty()) return m_errorQueue.back(); + else { + m_errorQueue.push(ValueError("Type conversion error.",m_strict)); + return m_errorQueue.back(); + } +} + +/// helper function to iterate over the error message queue: +/// pops the oldest item +ValueError +PyTypeConversions::getError() const +{ + if (!m_errorQueue.empty()) { + ValueError e = m_errorQueue.front(); + m_errorQueue.pop(); + if (m_errorQueue.empty()) m_error = false; + return e; + } + else { + m_error = false; + return ValueError(); + } +} + +/* Utilities */ + +/// get the type name of an object +std::string +PyTypeConversions::PyValue_Get_TypeName(PyObject* pyValue) const +{ + PyObject *pyType = PyObject_Type(pyValue); + if (!pyType) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + return std::string ("< unknown type >"); + } + PyObject *pyString = PyObject_Str(pyType); + if (!pyString) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_CLEAR(pyType); + return std::string ("< unknown type >"); + } + char *cstr = PyString_AS_STRING(pyString); + if (!cstr) + { + cerr << "Warning: Object type name could not be found." << endl; + if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} + Py_DECREF(pyType); + Py_CLEAR(pyString); + return std::string("< unknown type >"); + } + Py_DECREF(pyType); + Py_DECREF(pyString); + return std::string(cstr); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyTypeConversions.h Thu Nov 20 13:02:50 2014 +0000 @@ -0,0 +1,168 @@ +/* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ +/* + + * Vampy : This plugin is a wrapper around the Vamp plugin API. + * It allows for writing Vamp plugins in Python. + + * Centre for Digital Music, Queen Mary University of London. + * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources + * for licence information.) + +*/ + +/* +PyTypeConversions: Type safe conversion utilities between Python types +and basic C/C++ types. +*/ + +#ifndef _PY_TYPE_CONVERSIONS_H_ +#define _PY_TYPE_CONVERSIONS_H_ +#include <Python.h> +#ifdef HAVE_NUMPY +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API +#define NO_IMPORT_ARRAY +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include "numpy/arrayobject.h" +#endif + +#include <vector> +#include <queue> +#include <string> +#include <sstream> +#include <iostream> + +using std::cerr; +using std::endl; + +#ifdef HAVE_NUMPY +enum eArrayDataType { + dtype_float32 = (int) NPY_FLOAT, + dtype_complex64 = (int) NPY_CFLOAT + }; +#endif + +/* C++ mapping of PyNone Type */ +struct NoneType {}; + +// Data +class ValueError +{ +public: + ValueError() {} + ValueError(std::string m, bool s) : message(m),strict(s) {} + std::string location; + std::string message; + bool strict; + std::string str() const { + return (location.empty()) ? message : message + "\nLocation: " + location;} + void print() const { cerr << str() << endl; } + template<typename V> ValueError &operator<< (const V& v) + { + std::ostringstream ss; + ss << v; + location += ss.str(); + return *this; + } +}; + +class PyTypeConversions +{ +public: + PyTypeConversions(); + ~PyTypeConversions(); + + // Utilities + void setStrictTypingFlag(bool b) {m_strict = b;} + void setNumpyInstalled(bool b) {m_numpyInstalled = b;} + ValueError getError() const; + std::string PyValue_Get_TypeName(PyObject*) const; + + // Basic type conversion: Python to C++ + float PyValue_To_Float(PyObject*) const; + size_t PyValue_To_Size_t(PyObject*) const; + bool PyValue_To_Bool(PyObject*) const; + std::string PyValue_To_String(PyObject*) const; + long PyValue_To_Long(PyObject*) const; + // int PyValue_To_Int(PyObject* pyValue) const; + + // C++ to Python + PyObject *PyValue_From_CValue(const char*) const; + PyObject *PyValue_From_CValue(const std::string& x) const { return PyValue_From_CValue(x.c_str()); } + PyObject *PyValue_From_CValue(size_t) const; + PyObject *PyValue_From_CValue(double) const; + PyObject *PyValue_From_CValue(float x) const { return PyValue_From_CValue((double)x); } + PyObject *PyValue_From_CValue(bool) const; + + // Sequence types + std::vector<std::string> PyValue_To_StringVector (PyObject*) const; + std::vector<float> PyValue_To_FloatVector (PyObject*) const; + std::vector<float> PyList_To_FloatVector (PyObject*) const; + + // Numpy types +#ifdef HAVE_NUMPY + std::vector<float> PyArray_To_FloatVector (PyObject *pyValue) const; +#endif + + /// Convert DTYPE type 1D NumpyArray to std::vector<RET> + template<typename RET, typename DTYPE> + std::vector<RET> PyArray_Convert(void* raw_data_ptr, long length, size_t strides) const + { + std::vector<RET> rValue; + + /// check if the array is continuous, if not use strides info + if (sizeof(DTYPE)!=strides) { +#ifdef _DEBUG_VALUES + cerr << "Warning: discontinuous numpy array. Strides: " << strides << " bytes. sizeof(dtype): " << sizeof(DTYPE) << endl; +#endif + char* data = (char*) raw_data_ptr; + for (long i = 0; i<length; ++i){ + rValue.push_back((RET)(*((DTYPE*)data))); +#ifdef _DEBUG_VALUES + cerr << "value: " << (RET)(*((DTYPE*)data)) << endl; +#endif + data+=strides; + } + return rValue; + } + + DTYPE* data = (DTYPE*) raw_data_ptr; + for (long i = 0; i<length; ++i){ +#ifdef _DEBUG_VALUES + cerr << "value: " << (RET)data[i] << endl; +#endif + rValue.push_back((RET)data[i]); + } + return rValue; + } + + /// this is a special case. numpy.float64 has an array conversions but no array descriptor + inline std::vector<float> PyArray0D_Convert(PyArrayInterface *ai) const + { + std::vector<float> rValue; + if ((ai->typekind) == *"f") + rValue.push_back((float)*(double*)(ai->data)); + else { + setValueError("Unsupported NumPy data type.",m_strict); + return rValue; + } +#ifdef _DEBUG_VALUES + cerr << "value: " << rValue[0] << endl; +#endif + return rValue; + } + +private: + bool m_strict; + mutable bool m_error; + mutable std::queue<ValueError> m_errorQueue; + bool m_numpyInstalled; + + void setValueError(std::string,bool) const; + ValueError& lastError() const; + +public: + const bool& error; + +}; + +#endif
--- a/PyTypeInterface.cpp Mon Nov 17 14:07:00 2014 +0000 +++ b/PyTypeInterface.cpp Thu Nov 20 13:02:50 2014 +0000 @@ -56,707 +56,6 @@ { } -/// floating point numbers (TODO: check numpy.float128) -float -PyTypeInterface::PyValue_To_Float(PyObject* pyValue) const -{ - // convert float - if (pyValue && PyFloat_Check(pyValue)) - //TODO: check for limits here (same on most systems) - return (float) PyFloat_AS_DOUBLE(pyValue); - - if (pyValue == NULL) - { - setValueError("Error while converting object " + PyValue_Get_TypeName(pyValue) + " to float. ",m_strict); - return 0.0; - } - - // in strict mode we will not try harder - if (m_strict) { - setValueError("Strict conversion error: object" + PyValue_Get_TypeName(pyValue) +" is not float.",m_strict); - return 0.0; - } - - // convert other objects supporting the number protocol - if (PyNumber_Check(pyValue)) - { - PyObject* pyFloat = PyNumber_Float(pyValue); // new ref - if (!pyFloat) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting " + PyValue_Get_TypeName(pyValue) + " object to float.",m_strict); - return 0.0; - } - float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); - Py_DECREF(pyFloat); - return rValue; - } -/* - // convert other objects supporting the number protocol - if (PyNumber_Check(pyValue)) - { - // PEP353: Py_ssize_t is size_t but signed ! - // This will work up to numpy.float64 - Py_ssize_t rValue = PyNumber_AsSsize_t(pyValue,NULL); - if (PyErr_Occurred()) - { - PyErr_Print(); PyErr_Clear(); - setValueError("Error while converting integer object.",m_strict); - return 0.0; - } - if (rValue > (Py_ssize_t)FLT_MAX || rValue < (Py_ssize_t)FLT_MIN) - { - setValueError("Overflow error. Object can not be converted to float.",m_strict); - return 0.0; - } - return (float) rValue; - } -*/ - // convert string - if (PyString_Check(pyValue)) - { - PyObject* pyFloat = PyFloat_FromString(pyValue,NULL); - if (!pyFloat) - { - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - setValueError("String value can not be converted to float.",m_strict); - return 0.0; - } - float rValue = (float) PyFloat_AS_DOUBLE(pyFloat); - if (PyErr_Occurred()) - { - PyErr_Print(); PyErr_Clear(); - Py_CLEAR(pyFloat); - setValueError("Error while converting float object.",m_strict); - return 0.0; - } - Py_DECREF(pyFloat); - return rValue; - } - - // convert the first element of any iterable sequence (for convenience and backwards compatibility) - if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) - { - PyObject* item = PySequence_GetItem(pyValue,0); - if (item) - { - float rValue = this->PyValue_To_Float(item); - if (!m_error) { - Py_DECREF(item); - return rValue; - } else { - Py_CLEAR(item); - std::string msg = "Could not convert sequence element to float. "; - setValueError(msg,m_strict); - return 0.0; - } - } - } - - // give up - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - std::string msg = "Conversion from " + PyValue_Get_TypeName(pyValue) + " to float is not possible."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_Float failed. " << msg << endl; -#endif - return 0.0; -} - -/// size_t (unsigned integer types) -size_t -PyTypeInterface::PyValue_To_Size_t(PyObject* pyValue) const -{ - // convert objects supporting the number protocol - if (PyNumber_Check(pyValue)) - { - if (m_strict && !PyInt_Check(pyValue) && !PyLong_Check(pyValue)) - setValueError("Strict conversion error: object is not integer type.",m_strict); - // Note: this function handles Bool,Int,Long,Float - // speed is not critical in the use of this type by Vamp - // PEP353: Py_ssize_t is size_t but signed ! - Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); - if (PyErr_Occurred()) - { - PyErr_Print(); PyErr_Clear(); - setValueError("Error while converting integer object.",m_strict); - return 0; - } - // this test is nonsense -- neither part can occur - // owing to range of data types -- size_t is at least - // as big as long, and unsigned is always non-negative -/* - if ((unsigned long)rValue > SIZE_T_MAX || (unsigned long)rValue < 0) - { - setValueError("Overflow error. Object can not be converted to size_t.",m_strict); - return 0; - } -*/ - return (size_t) rValue; - } - - // in strict mode we will not try harder and throw an exception - // then the caller should decide what to do with it - if (m_strict) { - setValueError("Strict conversion error: object is not integer.",m_strict); - return 0; - } - - // convert string - if (PyString_Check(pyValue)) - { - PyObject* pyLong = PyNumber_Long(pyValue); - if (!pyLong) - { - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - setValueError("String object can not be converted to size_t.",m_strict); - return 0; - } - size_t rValue = this->PyValue_To_Size_t(pyLong); - if (!m_error) { - Py_DECREF(pyLong); - return rValue; - } else { - Py_CLEAR(pyLong); - setValueError ("Error converting string to size_t.",m_strict); - return 0; - } - } - - // convert the first element of iterable sequences - if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) - { - PyObject* item = PySequence_GetItem(pyValue,0); - if (item) - { - size_t rValue = this->PyValue_To_Size_t(item); - if (!m_error) { - Py_DECREF(item); - return rValue; - } else { - Py_CLEAR(item); - setValueError("Could not convert sequence element to size_t. ",m_strict); - return 0; - } - } - } - - // give up - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to size_t is not possible."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_Size_t failed. " << msg << endl; -#endif - return 0; -} - -/// long and int -long -PyTypeInterface::PyValue_To_Long(PyObject* pyValue) const -{ - // most common case: convert int (faster) - if (pyValue && PyInt_Check(pyValue)) { - // if the object is not NULL and verified, this macro just extracts the value. - return PyInt_AS_LONG(pyValue); - } - - // long - if (PyLong_Check(pyValue)) { - long rValue = PyLong_AsLong(pyValue); - if (PyErr_Occurred()) { - PyErr_Print(); PyErr_Clear(); - setValueError("Error while converting long object.",m_strict); - return 0; - } - return rValue; - } - - if (m_strict) { - setValueError("Strict conversion error: object is not integer or long integer.",m_strict); - return 0; - } - - // convert all objects supporting the number protocol - if (PyNumber_Check(pyValue)) - { - // Note: this function handles Bool,Int,Long,Float - // PEP353: Py_ssize_t is size_t but signed ! - Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); - if (PyErr_Occurred()) - { - PyErr_Print(); PyErr_Clear(); - setValueError("Error while converting integer object.",m_strict); - return 0; - } - if (rValue > LONG_MAX || rValue < LONG_MIN) - { - setValueError("Overflow error. Object can not be converted to size_t.",m_strict); - return 0; - } - return (long) rValue; - } - - // convert string - if (PyString_Check(pyValue)) - { - PyObject* pyLong = PyNumber_Long(pyValue); - if (!pyLong) - { - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - setValueError("String object can not be converted to long.",m_strict); - return 0; - } - long rValue = this->PyValue_To_Long(pyLong); - if (!m_error) { - Py_DECREF(pyLong); - return rValue; - } else { - Py_CLEAR(pyLong); - setValueError ("Error converting string to long.",m_strict); - return 0; - } - } - - // convert the first element of iterable sequences - if (PySequence_Check(pyValue) && PySequence_Size(pyValue) > 0) - { - PyObject* item = PySequence_GetItem(pyValue,0); - if (item) - { - size_t rValue = this->PyValue_To_Long(item); - if (!m_error) { - Py_DECREF(item); - return rValue; - } else { - Py_CLEAR(item); - setValueError("Could not convert sequence element to long. ",m_strict); - return 0; - } - } - } - - // give up - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to long is not possible."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_Long failed. " << msg << endl; -#endif - return 0; -} - - -bool -PyTypeInterface::PyValue_To_Bool(PyObject* pyValue) const -{ - // convert objects supporting the number protocol - // Note: PyBool is a subclass of PyInt - if (PyNumber_Check(pyValue)) - { - if (m_strict && !PyBool_Check(pyValue)) - setValueError - ("Strict conversion error: object is not boolean type.",m_strict); - - // Note: this function handles Bool,Int,Long,Float - Py_ssize_t rValue = PyInt_AsSsize_t(pyValue); - if (PyErr_Occurred()) - { - PyErr_Print(); PyErr_Clear(); - setValueError ("Error while converting boolean object.",m_strict); - } - if (rValue != 1 && rValue != 0) - { - setValueError ("Overflow error. Object can not be converted to boolean.",m_strict); - } - return (bool) rValue; - } - - if (m_strict) { - setValueError ("Strict conversion error: object is not numerical type.",m_strict); - return false; - } - - // convert iterables: the rule is the same as in the interpreter: - // empty sequence evaluates to False, anything else is True - if (PySequence_Check(pyValue)) - { - return PySequence_Size(pyValue)?true:false; - } - - // give up - if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } - std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to boolean is not possible."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_Bool failed. " << msg << endl; -#endif - return false; -} - -/// string and objects that support .__str__() -/// TODO: check unicode objects -std::string -PyTypeInterface::PyValue_To_String(PyObject* pyValue) const -{ - // convert string - if (PyString_Check(pyValue)) - { - char *cstr = PyString_AS_STRING(pyValue); - if (!cstr) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting string object.",m_strict); - return std::string(); - } - return std::string(cstr); - } - // TODO: deal with unicode here (argh!) - - // in strict mode we will not try harder - if (m_strict) { - setValueError("Strict conversion error: object is not string.",m_strict); - return std::string(); - } - - // accept None as empty string - if (pyValue == Py_None) return std::string(); - - // convert list or tuple: empties are turned into empty strings conventionally - if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) - { - if (!PySequence_Size(pyValue)) return std::string(); - PyObject* item = PySequence_GetItem(pyValue,0); - if (item) - { - std::string rValue = this->PyValue_To_String(item); - if (!m_error) { - Py_DECREF(item); - return rValue; - } else { - Py_CLEAR(item); - setValueError("Could not convert sequence element to string.",m_strict); - return std::string(); - } - } - } - - // convert any other object that has .__str__() or .__repr__() - PyObject* pyString = PyObject_Str(pyValue); - if (pyString && !PyErr_Occurred()) - { - std::string rValue = this->PyValue_To_String(pyString); - if (!m_error) { - Py_DECREF(pyString); - return rValue; - } else { - Py_CLEAR(pyString); - std::string msg = "Object " + this->PyValue_Get_TypeName(pyValue) +" can not be represented as string. "; - setValueError (msg,m_strict); - return std::string(); - } - } - - // give up - PyErr_Print(); PyErr_Clear(); - std::string msg = "Conversion from " + this->PyValue_Get_TypeName(pyValue) + " to string is not possible."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_String failed. " << msg << endl; -#endif - return std::string(); -} - -/* C Values to Py Values */ - - -PyObject* -PyTypeInterface::PyValue_From_CValue(const char* cValue) const -{ - // returns new reference -#ifdef _DEBUG - if (!cValue) { - std::string msg = "PyTypeInterface::PyValue_From_CValue: Null pointer encountered while converting from const char* ."; - cerr << msg << endl; - setValueError(msg,m_strict); - return NULL; - } -#endif - PyObject *pyValue = PyString_FromString(cValue); - if (!pyValue) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting from char* or string.",m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from const char*" << endl; -#endif - return NULL; - } - return pyValue; -} - -PyObject* -PyTypeInterface::PyValue_From_CValue(size_t cValue) const -{ - // returns new reference - PyObject *pyValue = PyInt_FromSsize_t((Py_ssize_t)cValue); - if (!pyValue) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting from size_t.",m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from size_t" << endl; -#endif - return NULL; - } - return pyValue; -} - -PyObject* -PyTypeInterface::PyValue_From_CValue(double cValue) const -{ - // returns new reference - PyObject *pyValue = PyFloat_FromDouble(cValue); - if (!pyValue) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting from float or double.",m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from float or double" << endl; -#endif - return NULL; - } - return pyValue; -} - -PyObject* -PyTypeInterface::PyValue_From_CValue(bool cValue) const -{ - // returns new reference - PyObject *pyValue = PyBool_FromLong((long)cValue); - if (!pyValue) - { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - setValueError("Error while converting from bool.",m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_From_CValue: Interpreter failed to convert from bool" << endl; -#endif - return NULL; - } - return pyValue; -} - - -/* Sequence Types to C++ Types */ - -//convert Python list to C++ vector of strings -std::vector<std::string> -PyTypeInterface::PyValue_To_StringVector (PyObject *pyList) const -{ - - std::vector<std::string> Output; - std::string ListElement; - PyObject *pyString = NULL; - - if (PyList_Check(pyList)) { - - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) { - //Get next list item (Borrowed Reference) - pyString = PyList_GET_ITEM(pyList,i); - ListElement = (string) PyString_AsString(PyObject_Str(pyString)); - Output.push_back(ListElement); - } - return Output; - } - -// #ifdef _DEBUG -// cerr << "PyTypeInterface::PyValue_To_StringVector: Warning: Value is not list of strings." << endl; -// #endif - - /// Assume a single value that can be casted as string - /// this allows to write e.g. Feature.label = 5.2 instead of ['5.2'] - Output.push_back(PyValue_To_String(pyList)); - if (m_error) { - std::string msg = "Value is not list of strings nor can be casted as string. "; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_StringVector failed. " << msg << endl; -#endif - } - return Output; -} - -//convert PyFeature.value (typically a list or numpy array) to C++ vector of floats -std::vector<float> -PyTypeInterface::PyValue_To_FloatVector (PyObject *pyValue) const -{ - -#ifdef HAVE_NUMPY -if (m_numpyInstalled) -{ - // there are four types of values we may receive from a numpy process: - // * a python scalar, - // * an array scalar, (e.g. numpy.float32) - // * an array with nd = 0 (0D array) - // * an array with nd > 0 - - /// check for scalars - if (PyArray_CheckScalar(pyValue) || PyFloat_Check(pyValue)) { - - std::vector<float> Output; - - // we rely on the behaviour the scalars are either floats - // or support the number protocol - // TODO: a potential optimisation is to handle them directly - Output.push_back(PyValue_To_Float(pyValue)); - return Output; - } - - /// numpy array - if (PyArray_CheckExact(pyValue)) - return PyArray_To_FloatVector(pyValue); -} -#endif - - /// python list of floats (backward compatible) - if (PyList_Check(pyValue)) { - return PyList_To_FloatVector(pyValue); - } - - std::vector<float> Output; - - /// finally assume a single value supporting the number protocol - /// this allows to write e.g. Feature.values = 5 instead of [5.00] - Output.push_back(PyValue_To_Float(pyValue)); - if (m_error) { - std::string msg = "Value is not list or array of floats nor can be casted as float. "; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_FloatVector failed. " << msg << endl; -#endif - } - return Output; -} - -//convert a list of python floats -std::vector<float> -PyTypeInterface::PyList_To_FloatVector (PyObject *inputList) const -{ - std::vector<float> Output; - -#ifdef _DEBUG - // This is a low level function normally called from - // PyValue_To_FloatVector(). Checking for list is not required. - if (!PyList_Check(inputList)) { - std::string msg = "Value is not list."; - setValueError(msg,true); - cerr << "PyTypeInterface::PyList_To_FloatVector failed. " << msg << endl; - return Output; - } -#endif - - float ListElement; - PyObject *pyFloat = NULL; - PyObject **pyObjectArray = PySequence_Fast_ITEMS(inputList); - - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(inputList); ++i) { - - // pyFloat = PyList_GET_ITEM(inputList,i); - pyFloat = pyObjectArray[i]; - -#ifdef _DEBUG - if (!pyFloat) { - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - cerr << "PyTypeInterface::PyList_To_FloatVector: Could not obtain list element: " - << i << " PyList_GetItem returned NULL! Skipping value." << endl; - continue; - } -#endif - - // ListElement = (float) PyFloat_AS_DOUBLE(pyFloat); - ListElement = PyValue_To_Float(pyFloat); - - -#ifdef _DEBUG_VALUES - cerr << "value: " << ListElement << endl; -#endif - Output.push_back(ListElement); - } - return Output; -} - -// if numpy is not installed this will not be called, -// therefor we do not check again -#ifdef HAVE_NUMPY -std::vector<float> -PyTypeInterface::PyArray_To_FloatVector (PyObject *pyValue) const -{ - std::vector<float> Output; - -#ifdef _DEBUG - // This is a low level function, normally called from - // PyValue_To_FloatVector(). Checking the array here is not required. - if (!PyArray_Check(pyValue)) { - std::string msg = "Object has no array interface."; - setValueError(msg,true); - cerr << "PyTypeInterface::PyArray_To_FloatVector failed. " << msg << endl; - return Output; - } -#endif - - PyArrayObject* pyArray = (PyArrayObject*) pyValue; - PyArray_Descr* descr = PyArray_DESCR(pyArray); - - /// check raw data and descriptor pointers - if (PyArray_DATA(pyArray) == 0 || descr == 0) { - std::string msg = "NumPy array with NULL data or descriptor pointer encountered."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << endl; -#endif - return Output; - } - - /// check dimensions - if (PyArray_NDIM(pyArray) != 1) { - std::string msg = "NumPy array must be a one dimensional vector."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << " Dims: " << (int) PyArray_NDIM(pyArray) << endl; -#endif - return Output; - } - -#ifdef _DEBUG_VALUES - cerr << "PyTypeInterface::PyArray_To_FloatVector: Numpy array verified." << endl; -#endif - - /// check strides (useful if array is not continuous) - size_t strides = *((size_t*) PyArray_STRIDES(pyArray)); - - /// convert the array - switch (descr->type_num) - { - case NPY_FLOAT : // dtype='float32' - return PyArray_Convert<float,float>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); - case NPY_DOUBLE : // dtype='float64' - return PyArray_Convert<float,double>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); - case NPY_INT : // dtype='int' - return PyArray_Convert<float,int>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); - case NPY_LONG : // dtype='long' - return PyArray_Convert<float,long>(PyArray_DATA(pyArray),PyArray_DIMS(pyArray)[0],strides); - default : - std::string msg = "Unsupported value type in NumPy array object."; - setValueError(msg,m_strict); -#ifdef _DEBUG - cerr << "PyTypeInterface::PyArray_To_FloatVector failed. Error: " << msg << endl; -#endif - return Output; - } -} -#endif - - /// FeatureSet (an integer map of FeatureLists) Vamp::Plugin::FeatureSet PyTypeInterface::PyValue_To_FeatureSet(PyObject* pyValue) const @@ -836,8 +135,8 @@ } // assume integer sample count - long sampleCount = PyValue_To_Long(pyValue); - if (m_error) { + long sampleCount = m_conv.PyValue_To_Long(pyValue); + if (m_conv.error) { std::string msg = "Unexpected value passed as RealTime.\nMust be vampy.RealTime type or integer sample count."; setValueError(msg,m_strict); #ifdef _DEBUG @@ -874,8 +173,8 @@ /// convert string (backward compatible) if (PyString_CheckExact(pyValue)) { Vamp::Plugin::OutputDescriptor::SampleType st; - st = (Vamp::Plugin::OutputDescriptor::SampleType) sampleKeys[PyValue_To_String(pyValue)]; - if (m_error) { + st = (Vamp::Plugin::OutputDescriptor::SampleType) sampleKeys[m_conv.PyValue_To_String(pyValue)]; + if (m_conv.error) { std::string msg = "Unexpected value passed as SampleType. Must be one of { OneSamplePerStep,FixedSampleRate,VariableSampleRate }\n(an integer in the range of 0..2) or a string value naming the type."; setValueError(msg,m_strict); return Vamp::Plugin::OutputDescriptor::SampleType(); @@ -908,8 +207,8 @@ /// convert string (backward compatible) if (PyString_CheckExact(pyValue)) { Vamp::Plugin::InputDomain id; - id = (PyValue_To_String(pyValue) == "FrequencyDomain")?Vamp::Plugin::FrequencyDomain:Vamp::Plugin::TimeDomain; - if (m_error) + id = (m_conv.PyValue_To_String(pyValue) == "FrequencyDomain")?Vamp::Plugin::FrequencyDomain:Vamp::Plugin::TimeDomain; + if (m_conv.error) { std::string msg = "Unexpected value passed as SampleType. Must be one of { TimeDomain,FrequencyDomain }\n(an integer in the range of 0..1) or a string value naming the type."; setValueError(msg,m_strict); @@ -927,6 +226,159 @@ return Vamp::Plugin::InputDomain(); } +/* Convert Sample Buffers to Python */ + +/// passing the sample buffers as builtin python lists +/// Optimization: using fast sequence protocol +inline PyObject* +PyTypeInterface::InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) +{ + //create a list of lists (new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + + // Pack samples into a Python List Object + // pyFloat/pyComplex types will always be new references, + // they will be freed when the lists are deallocated. + + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + for (size_t i=0; i < channels; ++i) { + + size_t arraySize; + if (dtype==Vamp::Plugin::FrequencyDomain) + arraySize = (blockSize / 2) + 1; //blockSize + 2; if cplx list isn't used + else + arraySize = blockSize; + + PyObject *pySampleList = PyList_New((Py_ssize_t) arraySize); + PyObject **pySampleListArray = PySequence_Fast_ITEMS(pySampleList); + + // Note: passing a complex list crashes the C-style plugin + // when it tries to convert it to a numpy array directly. + // This plugin will be obsolete, but we have to find a way + // to prevent such crash: possibly a numpy bug, + // works fine above 1.0.4 + + switch (dtype) //(Vamp::Plugin::TimeDomain) + { + case Vamp::Plugin::TimeDomain : + + for (size_t j = 0; j < arraySize; ++j) { + PyObject *pyFloat=PyFloat_FromDouble( + (double) inputBuffers[i][j]); + pySampleListArray[j] = pyFloat; + } + break; + + case Vamp::Plugin::FrequencyDomain : + + size_t k = 0; + for (size_t j = 0; j < arraySize; ++j) { + PyObject *pyComplex=PyComplex_FromDoubles( + (double) inputBuffers[i][k], + (double) inputBuffers[i][k+1]); + pySampleListArray[j] = pyComplex; + k += 2; + } + break; + + } + pyChannelListArray[i] = pySampleList; + } + return pyChannelList; +} + +/// numpy buffer interface: passing the sample buffers as shared memory buffers +/// Optimization: using sequence protocol for creating the buffer list +inline PyObject* +PyTypeInterface::InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) +{ + //create a list of buffers (returns new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + + // Expose memory using the Buffer Interface. + // This will pass a pointer which can be recasted in Python code + // as complex or float array using Numpy's frombuffer() method + // (this will not copy values just keep the starting adresses + // for each channel in a list) + Py_ssize_t bufferSize; + + if (dtype==Vamp::Plugin::FrequencyDomain) + bufferSize = (Py_ssize_t) sizeof(float) * (blockSize+2); + else + bufferSize = (Py_ssize_t) sizeof(float) * blockSize; + + for (size_t i=0; i < channels; ++i) { + PyObject *pyBuffer = PyBuffer_FromMemory + ((void *) (float *) inputBuffers[i],bufferSize); + pyChannelListArray[i] = pyBuffer; + } + return pyChannelList; +} + + +/// numpy array interface: passing the sample buffers as 2D numpy array +/// Optimization: using array API (needs numpy headers) +#ifdef HAVE_NUMPY +inline PyObject* +PyTypeInterface::InputBuffers_As_NumpyArray(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) +{ +/* +NOTE: We create a list of 1D Numpy arrays for each channel instead +of a matrix, because the address space of inputBuffers doesn't seem +to be continuous. Although the array strides could be calculated for +2 channels (i.e. inputBuffers[1] - inputBuffers[0]) i'm not sure +if this can be trusted, especially for more than 2 channels. + + cerr << "First channel: " << inputBuffers[0][0] << " address: " << inputBuffers[0] << endl; + if (channels == 2) + cerr << "Second channel: " << inputBuffers[1][0] << " address: " << inputBuffers[1] << endl; + +*/ + + // create a list of arrays (returns new references) + PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); + PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); + + // Expose memory using the Numpy Array Interface. + // This will wrap an array objects around the data. + // (will not copy values just steal the starting adresses) + + int arraySize, typenum; + + switch (dtype) + { + case Vamp::Plugin::TimeDomain : + typenum = dtype_float32; //NPY_FLOAT; + arraySize = (int) blockSize; + break; + + case Vamp::Plugin::FrequencyDomain : + typenum = dtype_complex64; //NPY_CFLOAT; + arraySize = (int) (blockSize / 2) + 1; + break; + + default : + cerr << "PyTypeInterface::InputBuffers_As_NumpyArray: Error: Unsupported numpy array data type." << endl; + return pyChannelList; + } + + // size for each dimension + npy_intp ndims[1]={arraySize}; + + for (size_t i=0; i < channels; ++i) { + PyObject *pyChannelArray = + //args: (dimensions, size in each dim, type kind, pointer to continuous array) + PyArray_SimpleNewFromData(1, ndims, typenum, (void*) inputBuffers[i]); + // make it read-only: set all flags to false except NPY_C_CONTIGUOUS + //!!! what about NPY_ARRAY_OWNDATA? + PyArray_CLEARFLAGS((PyArrayObject *)pyChannelArray, 0xff); + PyArray_ENABLEFLAGS((PyArrayObject *)pyChannelArray, NPY_ARRAY_C_CONTIGUOUS); + pyChannelListArray[i] = pyChannelArray; + } + return pyChannelList; +} +#endif /// OutputDescriptor void @@ -1073,7 +525,7 @@ } -/* Error handling */ +/* Error handling */ void PyTypeInterface::setValueError (std::string message, bool strict) const @@ -1083,7 +535,7 @@ } /// return a reference to the last error or creates a new one. -PyTypeInterface::ValueError& +ValueError& PyTypeInterface::lastError() const { m_error = false; @@ -1096,56 +548,22 @@ /// helper function to iterate over the error message queue: /// pops the oldest item -PyTypeInterface::ValueError +ValueError PyTypeInterface::getError() const { if (!m_errorQueue.empty()) { - PyTypeInterface::ValueError e = m_errorQueue.front(); + ValueError e = m_errorQueue.front(); m_errorQueue.pop(); if (m_errorQueue.empty()) m_error = false; return e; } else { m_error = false; - return PyTypeInterface::ValueError(); + return ValueError(); } } -/* Utilities */ - -/// get the type name of an object -std::string -PyTypeInterface::PyValue_Get_TypeName(PyObject* pyValue) const -{ - PyObject *pyType = PyObject_Type(pyValue); - if (!pyType) - { - cerr << "Warning: Object type name could not be found." << endl; - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - return std::string ("< unknown type >"); - } - PyObject *pyString = PyObject_Str(pyType); - if (!pyString) - { - cerr << "Warning: Object type name could not be found." << endl; - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - Py_CLEAR(pyType); - return std::string ("< unknown type >"); - } - char *cstr = PyString_AS_STRING(pyString); - if (!cstr) - { - cerr << "Warning: Object type name could not be found." << endl; - if (PyErr_Occurred()) {PyErr_Print(); PyErr_Clear();} - Py_DECREF(pyType); - Py_CLEAR(pyString); - return std::string("< unknown type >"); - } - Py_DECREF(pyType); - Py_DECREF(pyString); - return std::string(cstr); - -} +/* Utilities */ bool PyTypeInterface::initMaps() const
--- a/PyTypeInterface.h Mon Nov 17 14:07:00 2014 +0000 +++ b/PyTypeInterface.h Thu Nov 20 13:02:50 2014 +0000 @@ -12,7 +12,7 @@ /* PyTypeInterface: Type safe conversion utilities between Python types -and basic C/C++ types and Vamp API types. +and Vamp API types. See PyTypeConversions for basic C/C++ types. */ #ifndef _PY_TYPE_INTERFACE_H_ @@ -25,6 +25,7 @@ #include "numpy/arrayobject.h" #endif #include "PyExtensionModule.h" +#include "PyTypeConversions.h" #include <vector> #include <queue> #include <string> @@ -34,13 +35,6 @@ using std::cerr; using std::endl; -#ifdef HAVE_NUMPY -enum eArrayDataType { - dtype_float32 = (int) NPY_FLOAT, - dtype_complex64 = (int) NPY_CFLOAT - }; -#endif - namespace o { enum eOutDescriptors { not_found, @@ -95,79 +89,31 @@ label }; -/* C++ mapping of PyNone Type */ -struct NoneType {}; - class PyTypeInterface { + PyTypeConversions m_conv; + public: PyTypeInterface(); ~PyTypeInterface(); - // Data - class ValueError - { - public: - ValueError() {} - ValueError(std::string m, bool s) : message(m),strict(s) {} - std::string location; - std::string message; - bool strict; - std::string str() const { - return (location.empty()) ? message : message + "\nLocation: " + location;} - void print() const { cerr << str() << endl; } - template<typename V> ValueError &operator<< (const V& v) - { - std::ostringstream ss; - ss << v; - location += ss.str(); - return *this; - } - }; - // Utilities - void setStrictTypingFlag(bool b) {m_strict = b;} - void setNumpyInstalled(bool b) {m_numpyInstalled = b;} + void setStrictTypingFlag(bool b) {m_strict = b; m_conv.setStrictTypingFlag(b);} + void setNumpyInstalled(bool b) {m_numpyInstalled = b; m_conv.setNumpyInstalled(b); } ValueError getError() const; std::string PyValue_Get_TypeName(PyObject*) const; bool initMaps() const; - // Basic type conversion: Python to C++ - float PyValue_To_Float(PyObject*) const; - size_t PyValue_To_Size_t(PyObject*) const; - bool PyValue_To_Bool(PyObject*) const; - std::string PyValue_To_String(PyObject*) const; - long PyValue_To_Long(PyObject*) const; - // int PyValue_To_Int(PyObject* pyValue) const; - - - // C++ to Python - PyObject *PyValue_From_CValue(const char*) const; - PyObject *PyValue_From_CValue(const std::string& x) const { return PyValue_From_CValue(x.c_str()); } - PyObject *PyValue_From_CValue(size_t) const; - PyObject *PyValue_From_CValue(double) const; - PyObject *PyValue_From_CValue(float x) const { return PyValue_From_CValue((double)x); } - PyObject *PyValue_From_CValue(bool) const; - - // Sequence types - std::vector<std::string> PyValue_To_StringVector (PyObject*) const; - std::vector<float> PyValue_To_FloatVector (PyObject*) const; - std::vector<float> PyList_To_FloatVector (PyObject*) const; - // Input buffers to Python PyObject* InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype); PyObject* InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype); // Numpy types #ifdef HAVE_NUMPY - std::vector<float> PyArray_To_FloatVector (PyObject *pyValue) const; PyObject* InputBuffers_As_NumpyArray(const float *const *inputBuffers, const size_t&, const size_t&, const Vamp::Plugin::InputDomain& dtype); #endif - - - -/* Template functions */ +/* Template functions */ /// Common wrappers to set values in Vamp API structs. (to be used in template functions) @@ -211,9 +157,9 @@ //Python Dictionary Iterator: while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyDictValue)) { - std::string key = PyValue_To_String(pyKey); + std::string key = m_conv.PyValue_To_String(pyKey); #ifdef _DEBUG_VALUES - cerr << "key: '" << key << "' value: '" << PyValue_To_String(pyDictValue) << "' " << endl; + cerr << "key: '" << key << "' value: '" << m_conv.PyValue_To_String(pyDictValue) << "' " << endl; #endif SetValue(rd,key,pyDictValue); if (m_error) { @@ -296,54 +242,6 @@ } - /// Convert DTYPE type 1D NumpyArray to std::vector<RET> - template<typename RET, typename DTYPE> - std::vector<RET> PyArray_Convert(void* raw_data_ptr, long length, size_t strides) const - { - std::vector<RET> rValue; - - /// check if the array is continuous, if not use strides info - if (sizeof(DTYPE)!=strides) { -#ifdef _DEBUG_VALUES - cerr << "Warning: discontinuous numpy array. Strides: " << strides << " bytes. sizeof(dtype): " << sizeof(DTYPE) << endl; -#endif - char* data = (char*) raw_data_ptr; - for (long i = 0; i<length; ++i){ - rValue.push_back((RET)(*((DTYPE*)data))); -#ifdef _DEBUG_VALUES - cerr << "value: " << (RET)(*((DTYPE*)data)) << endl; -#endif - data+=strides; - } - return rValue; - } - - DTYPE* data = (DTYPE*) raw_data_ptr; - for (long i = 0; i<length; ++i){ -#ifdef _DEBUG_VALUES - cerr << "value: " << (RET)data[i] << endl; -#endif - rValue.push_back((RET)data[i]); - } - return rValue; - } - - /// this is a special case. numpy.float64 has an array interface but no array descriptor - inline std::vector<float> PyArray0D_Convert(PyArrayInterface *ai) const - { - std::vector<float> rValue; - if ((ai->typekind) == *"f") - rValue.push_back((float)*(double*)(ai->data)); - else { - setValueError("Unsupported NumPy data type.",m_strict); - return rValue; - } -#ifdef _DEBUG_VALUES - cerr << "value: " << rValue[0] << endl; -#endif - return rValue; - } - //Vamp specific types Vamp::Plugin::FeatureSet PyValue_To_FeatureSet(PyObject*) const; inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureSet &r) const @@ -359,26 +257,25 @@ inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::InputDomain &r) const { r = this->PyValue_To_InputDomain(pyValue); } - /* Overloaded PyValue_To_rValue() to support generic functions */ inline void PyValue_To_rValue(PyObject *pyValue, float &defValue) const - { float tmp = this->PyValue_To_Float(pyValue); + { float tmp = m_conv.PyValue_To_Float(pyValue); if(!m_error) defValue = tmp; } inline void PyValue_To_rValue(PyObject *pyValue, size_t &defValue) const - { size_t tmp = this->PyValue_To_Size_t(pyValue); + { size_t tmp = m_conv.PyValue_To_Size_t(pyValue); if(!m_error) defValue = tmp; } inline void PyValue_To_rValue(PyObject *pyValue, bool &defValue) const - { bool tmp = this->PyValue_To_Bool(pyValue); + { bool tmp = m_conv.PyValue_To_Bool(pyValue); if(!m_error) defValue = tmp; } inline void PyValue_To_rValue(PyObject *pyValue, std::string &defValue) const - { std::string tmp = this->PyValue_To_String(pyValue); + { std::string tmp = m_conv.PyValue_To_String(pyValue); if(!m_error) defValue = tmp; } /*used by templates where we expect no return value, if there is one it will be ignored*/ inline void PyValue_To_rValue(PyObject *pyValue, NoneType &defValue) const { if (m_strict && pyValue != Py_None) setValueError("Strict conversion error: Expected 'None' type.",m_strict); } - + /* convert sequence types to Vamp List types */ inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::OutputList &r) const { r = this->PyValue_To_VampList<Vamp::Plugin::OutputList,Vamp::Plugin::OutputDescriptor>(pyValue); } @@ -403,23 +300,23 @@ /* Overloaded _convert(), bypasses error checking to avoid doing it twice in internals. */ inline void _convert(PyObject *pyValue,float &r) const - { r = PyValue_To_Float(pyValue); } + { r = m_conv.PyValue_To_Float(pyValue); } inline void _convert(PyObject *pyValue,size_t &r) const - { r = PyValue_To_Size_t(pyValue); } - inline void _convert(PyObject *pyValue,bool &r) const - { r = PyValue_To_Bool(pyValue); } + { r = m_conv.PyValue_To_Size_t(pyValue); } + inline void _convert(PyObject *pyValue,bool &r) const + { r = m_conv.PyValue_To_Bool(pyValue); } inline void _convert(PyObject *pyValue,std::string &r) const - { r = PyValue_To_String(pyValue); } + { r = m_conv.PyValue_To_String(pyValue); } inline void _convert(PyObject *pyValue,std::vector<std::string> &r) const - { r = PyValue_To_StringVector(pyValue); } + { r = m_conv.PyValue_To_StringVector(pyValue); } inline void _convert(PyObject *pyValue,std::vector<float> &r) const - { r = PyValue_To_FloatVector(pyValue); } - inline void _convert(PyObject *pyValue,Vamp::RealTime &r) const + { r = m_conv.PyValue_To_FloatVector(pyValue); } + inline void _convert(PyObject *pyValue,Vamp::RealTime &r) const { r = PyValue_To_RealTime(pyValue); } inline void _convert(PyObject *pyValue,Vamp::Plugin::OutputDescriptor::SampleType &r) const { r = PyValue_To_SampleType(pyValue); } // inline void _convert(PyObject *pyValue,Vamp::Plugin::InputDomain &r) const - // { r = PyValue_To_InputDomain(pyValue); } + // { r = m_conv.PyValue_To_InputDomain(pyValue); } /* Identify descriptors for error reporting */ @@ -435,246 +332,4 @@ }; -/* Convert Sample Buffers to Python */ - -/// passing the sample buffers as builtin python lists -/// Optimization: using fast sequence protocol -inline PyObject* -PyTypeInterface::InputBuffers_As_PythonLists(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) -{ - //create a list of lists (new references) - PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); - - // Pack samples into a Python List Object - // pyFloat/pyComplex types will always be new references, - // they will be freed when the lists are deallocated. - - PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); - for (size_t i=0; i < channels; ++i) { - - size_t arraySize; - if (dtype==Vamp::Plugin::FrequencyDomain) - arraySize = (blockSize / 2) + 1; //blockSize + 2; if cplx list isn't used - else - arraySize = blockSize; - - PyObject *pySampleList = PyList_New((Py_ssize_t) arraySize); - PyObject **pySampleListArray = PySequence_Fast_ITEMS(pySampleList); - - // Note: passing a complex list crashes the C-style plugin - // when it tries to convert it to a numpy array directly. - // This plugin will be obsolete, but we have to find a way - // to prevent such crash: possibly a numpy bug, - // works fine above 1.0.4 - - switch (dtype) //(Vamp::Plugin::TimeDomain) - { - case Vamp::Plugin::TimeDomain : - - for (size_t j = 0; j < arraySize; ++j) { - PyObject *pyFloat=PyFloat_FromDouble( - (double) inputBuffers[i][j]); - pySampleListArray[j] = pyFloat; - } - break; - - case Vamp::Plugin::FrequencyDomain : - - size_t k = 0; - for (size_t j = 0; j < arraySize; ++j) { - PyObject *pyComplex=PyComplex_FromDoubles( - (double) inputBuffers[i][k], - (double) inputBuffers[i][k+1]); - pySampleListArray[j] = pyComplex; - k += 2; - } - break; - - } - pyChannelListArray[i] = pySampleList; - } - return pyChannelList; -} - -/// numpy buffer interface: passing the sample buffers as shared memory buffers -/// Optimization: using sequence protocol for creating the buffer list -inline PyObject* -PyTypeInterface::InputBuffers_As_SharedMemoryList(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) -{ - //create a list of buffers (returns new references) - PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); - PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); - - // Expose memory using the Buffer Interface. - // This will pass a pointer which can be recasted in Python code - // as complex or float array using Numpy's frombuffer() method - // (this will not copy values just keep the starting adresses - // for each channel in a list) - Py_ssize_t bufferSize; - - if (dtype==Vamp::Plugin::FrequencyDomain) - bufferSize = (Py_ssize_t) sizeof(float) * (blockSize+2); - else - bufferSize = (Py_ssize_t) sizeof(float) * blockSize; - - for (size_t i=0; i < channels; ++i) { - PyObject *pyBuffer = PyBuffer_FromMemory - ((void *) (float *) inputBuffers[i],bufferSize); - pyChannelListArray[i] = pyBuffer; - } - return pyChannelList; -} - - -/// numpy array interface: passing the sample buffers as 2D numpy array -/// Optimization: using array API (needs numpy headers) -#ifdef HAVE_NUMPY -inline PyObject* -PyTypeInterface::InputBuffers_As_NumpyArray(const float *const *inputBuffers,const size_t& channels, const size_t& blockSize, const Vamp::Plugin::InputDomain& dtype) -{ -/* -NOTE: We create a list of 1D Numpy arrays for each channel instead -of a matrix, because the address space of inputBuffers doesn't seem -to be continuous. Although the array strides could be calculated for -2 channels (i.e. inputBuffers[1] - inputBuffers[0]) i'm not sure -if this can be trusted, especially for more than 2 channels. - - cerr << "First channel: " << inputBuffers[0][0] << " address: " << inputBuffers[0] << endl; - if (channels == 2) - cerr << "Second channel: " << inputBuffers[1][0] << " address: " << inputBuffers[1] << endl; - -*/ - - // create a list of arrays (returns new references) - PyObject *pyChannelList = PyList_New((Py_ssize_t) channels); - PyObject **pyChannelListArray = PySequence_Fast_ITEMS(pyChannelList); - - // Expose memory using the Numpy Array Interface. - // This will wrap an array objects around the data. - // (will not copy values just steal the starting adresses) - - int arraySize, typenum; - - switch (dtype) - { - case Vamp::Plugin::TimeDomain : - typenum = dtype_float32; //NPY_FLOAT; - arraySize = (int) blockSize; - break; - - case Vamp::Plugin::FrequencyDomain : - typenum = dtype_complex64; //NPY_CFLOAT; - arraySize = (int) (blockSize / 2) + 1; - break; - - default : - cerr << "PyTypeInterface::InputBuffers_As_NumpyArray: Error: Unsupported numpy array data type." << endl; - return pyChannelList; - } - - // size for each dimension - npy_intp ndims[1]={arraySize}; - - for (size_t i=0; i < channels; ++i) { - PyObject *pyChannelArray = - //args: (dimensions, size in each dim, type kind, pointer to continuous array) - PyArray_SimpleNewFromData(1, ndims, typenum, (void*) inputBuffers[i]); - // make it read-only: set all flags to false except NPY_C_CONTIGUOUS - //!!! what about NPY_ARRAY_OWNDATA? - PyArray_CLEARFLAGS((PyArrayObject *)pyChannelArray, 0xff); - PyArray_ENABLEFLAGS((PyArrayObject *)pyChannelArray, NPY_ARRAY_C_CONTIGUOUS); - pyChannelListArray[i] = pyChannelArray; - } - return pyChannelList; -} #endif - - - -#ifdef NUMPY_REFERENCE -/// This should be all we need to compile without direct dependency, -/// but we don't do that. (it may not work on some platforms) -typedef struct { - int two; /* contains the integer 2 -- simple sanity check */ - int nd; /* number of dimensions */ - char typekind; /* kind in array --- character code of typestr */ - int itemsize; /* size of each element */ - int flags; /* flags indicating how the data should be interpreted */ - /* must set ARR_HAS_DESCR bit to validate descr */ - Py_intptr_t *shape; /* A length-nd array of shape information */ - Py_intptr_t *strides; /* A length-nd array of stride information */ - void *data; /* A pointer to the first element of the array */ - PyObject *descr; /* NULL or data-description (same as descr key */ - /* of __array_interface__) -- must set ARR_HAS_DESCR */ - /* flag or this will be ignored. */ -} PyArrayInterface; - -typedef struct PyArrayObject { - PyObject_HEAD - char *data; /* pointer to raw data buffer */ - int nd; /* number of dimensions, also called ndim */ - npy_intp *dimensions; /* size in each dimension */ - npy_intp *strides; /* bytes to jump to get to the - next element in each dimension */ - PyObject *base; /* This object should be decref'd - upon deletion of array */ - /* For views it points to the original array */ - /* For creation from buffer object it points - to an object that shold be decref'd on - deletion */ - /* For UPDATEIFCOPY flag this is an array - to-be-updated upon deletion of this one */ - PyArray_Descr *descr; /* Pointer to type structure */ - int flags; /* Flags describing array -- see below*/ - PyObject *weakreflist; /* For weakreferences */ -} PyArrayObject; - -typedef struct _PyArray_Descr { - PyObject_HEAD - PyTypeObject *typeobj; /* the type object representing an - instance of this type -- should not - be two type_numbers with the same type - object. */ - char kind; /* kind for this type */ - char type; /* unique-character representing this type */ - char byteorder; /* '>' (big), '<' (little), '|' - (not-applicable), or '=' (native). */ - char hasobject; /* non-zero if it has object arrays - in fields */ - int type_num; /* number representing this type */ - int elsize; /* element size for this type */ - int alignment; /* alignment needed for this type */ - struct _arr_descr \ - *subarray; /* Non-NULL if this type is - is an array (C-contiguous) - of some other type - */ - PyObject *fields; /* The fields dictionary for this type */ - /* For statically defined descr this - is always Py_None */ - - PyObject *names; /* An ordered tuple of field names or NULL - if no fields are defined */ - - PyArray_ArrFuncs *f; /* a table of functions specific for each - basic data descriptor */ -} PyArray_Descr; - -enum NPY_TYPES { NPY_BOOL=0, - NPY_BYTE, NPY_UBYTE, - NPY_SHORT, NPY_USHORT, - NPY_INT, NPY_UINT, - NPY_LONG, NPY_ULONG, - NPY_LONGLONG, NPY_ULONGLONG, - NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE, - NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE, - NPY_OBJECT=17, - NPY_STRING, NPY_UNICODE, - NPY_VOID, - NPY_NTYPES, - NPY_NOTYPE, - NPY_CHAR, /* special flag */ - NPY_USERDEF=256 /* leave room for characters */ -}; -#endif /*NUMPY_REFERENCE*/ -#endif