Mercurial > hg > vampy
diff PyTypeInterface.h @ 37:27bab3a16c9a vampy2final
new branch Vampy2final
author | fazekasgy |
---|---|
date | Mon, 05 Oct 2009 11:28:00 +0000 |
parents | |
children | 8b2eddf686da |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyTypeInterface.h Mon Oct 05 11:28:00 2009 +0000 @@ -0,0 +1,661 @@ +/* + + * 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.) + +*/ + +/* +PyTypeInterface: Type safe conversion utilities between Python types +and basic C/C++ types and Vamp API types. +*/ + +#ifndef _PY_TYPE_INTERFACE_H_ +#define _PY_TYPE_INTERFACE_H_ +#include <Python.h> +#ifdef HAVE_NUMPY +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API +#define NO_IMPORT_ARRAY +#include "numpy/arrayobject.h" +#endif +#include "PyExtensionModule.h" +#include <vector> +#include <queue> +#include <string> +#include <sstream> +#include "vamp-sdk/Plugin.h" + +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, + identifier, + name, + description, + unit, + hasFixedBinCount, + binCount, + binNames, + hasKnownExtents, + minValue, + maxValue, + isQuantized, + quantizeStep, + sampleType, + sampleRate, + hasDuration, + endNode + }; +} + +namespace p { +enum eParmDescriptors { + not_found, + identifier, + name, + description, + unit, + minValue, + maxValue, + defaultValue, + isQuantized, + quantizeStep + }; +} + +enum eSampleTypes { + OneSamplePerStep, + FixedSampleRate, + VariableSampleRate + }; + +enum eFeatureFields { + unknown, + hasTimestamp, + timestamp, + hasDuration, + duration, + values, + label + }; + +/* C++ mapping of PyNone Type */ +struct NoneType {}; + +class PyTypeInterface +{ +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;} + 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); + + // 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 */ + + + /// Common wrappers to set values in Vamp API structs. (to be used in template functions) + void SetValue(Vamp::Plugin::OutputDescriptor& od, std::string& key, PyObject* pyValue) const; + void SetValue(Vamp::Plugin::ParameterDescriptor& od, std::string& key, PyObject* pyValue) const; + bool SetValue(Vamp::Plugin::Feature& od, std::string& key, PyObject* pyValue) const; + PyObject* GetDescriptor_As_Dict(PyObject* pyValue) const + { + if PyFeature_CheckExact(pyValue) return PyFeature_AS_DICT(pyValue); + if PyOutputDescriptor_CheckExact(pyValue) return PyOutputDescriptor_AS_DICT(pyValue); + if PyParameterDescriptor_CheckExact(pyValue) return PyParameterDescriptor_AS_DICT(pyValue); + return NULL; + } + + //returns e.g. Vamp::Plugin::OutputDescriptor or Vamp::Plugin::Feature + template<typename RET> + RET PyValue_To_VampDescriptor(PyObject* pyValue) const + { + PyObject* pyDict; + + // Descriptors encoded as dicts + pyDict = GetDescriptor_As_Dict(pyValue); + if (!pyDict) pyDict = pyValue; + + // TODO: support full mapping protocol as fallback. + if (!PyDict_Check(pyDict)) { + setValueError("Error while converting descriptor or feature object.\nThe value is neither a dictionary nor a Vamp Feature or Descriptor type.",m_strict); +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampDescriptor failed. Error: Unexpected return type." << endl; +#endif + return RET(); + } + + Py_ssize_t pyPos = 0; + PyObject *pyKey, *pyDictValue; + initMaps(); + int errors = 0; + m_error = false; + RET rd; + + //Python Dictionary Iterator: + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyDictValue)) + { + std::string key = PyValue_To_String(pyKey); +#ifdef _DEBUG_VALUES + cerr << "key: '" << key << "' value: '" << PyValue_To_String(pyDictValue) << "' " << endl; +#endif + SetValue(rd,key,pyDictValue); + if (m_error) { + errors++; + lastError() << "attribute '" << key << "'";// << " of " << getDescriptorId(rd); + } + } + if (errors) { + lastError() << " of " << getDescriptorId(rd); + m_error = true; +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampDescriptor: Warning: Value error in descriptor." << endl; +#endif + } + return rd; + } + + /// Convert a sequence (tipically list) of PySomething to + /// OutputList,ParameterList or FeatureList + /// <OutputList> <OutputDescriptor> + template<typename RET,typename ELEM> + RET PyValue_To_VampList(PyObject* pyValue) const + { + RET list; // e.g. Vamp::Plugin::OutputList + ELEM element; // e.g. Vamp::Plugin::OutputDescriptor + + /// convert lists (ParameterList, OutputList, FeatureList) + if (PyList_Check(pyValue)) { + PyObject *pyDict; //This reference will be borrowed + m_error = false; int errors = 0; + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyValue); ++i) { + //Get i-th Vamp output descriptor (Borrowed Reference) + pyDict = PyList_GET_ITEM(pyValue,i); + element = PyValue_To_VampDescriptor<ELEM>(pyDict); + if (m_error) errors++; + // Check for empty Feature/Descriptor as before? + list.push_back(element); + } + if (errors) m_error=true; + return list; + } + + /// convert other types implementing the sequence protocol + if (PySequence_Check(pyValue)) { + PyObject *pySequence = PySequence_Fast(pyValue,"Returned value can not be converted to list or tuple."); + PyObject **pyElements = PySequence_Fast_ITEMS(pySequence); + m_error = false; int errors = 0; + for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(pySequence); ++i) + { + element = PyValue_To_VampDescriptor<ELEM>(pyElements[i]); + if (m_error) errors++; + list.push_back(element); + } + if (errors) m_error=true; + Py_XDECREF(pySequence); + return list; + } + + // accept None as an empty list + if (pyValue == Py_None) return list; + + // in strict mode, returning a single value is not allowed + if (m_strict) { + setValueError("Strict conversion error: object is not list or iterable sequence.",m_strict); + return list; + } + + /// try to insert single, non-iterable values. i.e. feature <- [feature] + element = PyValue_To_VampDescriptor<ELEM>(pyValue); + if (m_error) { + setValueError("Could not insert returned value to Vamp List.",m_strict); + return list; + } + list.push_back(element); + return list; + +#ifdef _DEBUG + cerr << "PyTypeInterface::PyValue_To_VampList failed. Expected iterable return type." << endl; +#endif + + } + + /// Convert DTYPE type 1D NumpyArray to std::vector<RET> + template<typename RET, typename DTYPE> + std::vector<RET> PyArray_Convert(char* 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 + { r = this->PyValue_To_FeatureSet(pyValue); } + + Vamp::RealTime PyValue_To_RealTime(PyObject*) const; + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::RealTime &r) const + { r = this->PyValue_To_RealTime(pyValue); } + + Vamp::Plugin::OutputDescriptor::SampleType PyValue_To_SampleType(PyObject*) const; + + Vamp::Plugin::InputDomain PyValue_To_InputDomain(PyObject*) const; + 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); + 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); + if(!m_error) defValue = tmp; } + inline void PyValue_To_rValue(PyObject *pyValue, bool &defValue) const + { bool tmp = this->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); + 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); } + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::ParameterList &r) const + { r = this->PyValue_To_VampList<Vamp::Plugin::ParameterList,Vamp::Plugin::ParameterDescriptor>(pyValue); } + inline void PyValue_To_rValue(PyObject *pyValue, Vamp::Plugin::FeatureList &r) const + { r = this->PyValue_To_VampList<Vamp::Plugin::FeatureList,Vamp::Plugin::Feature>(pyValue); } + + /// this is only needed for RealTime->Frame conversion + void setInputSampleRate(float inputSampleRate) + { m_inputSampleRate = (unsigned int) inputSampleRate; } + +private: + bool m_strict; + mutable bool m_error; + mutable std::queue<ValueError> m_errorQueue; + unsigned int m_inputSampleRate; + + void setValueError(std::string,bool) const; + ValueError& lastError() const; + + /* 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); } + 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); } + inline void _convert(PyObject *pyValue,std::string &r) const + { r = PyValue_To_String(pyValue); } + inline void _convert(PyObject *pyValue,std::vector<std::string> &r) const + { r = 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 = 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); } + + + /* Identify descriptors for error reporting */ + inline std::string getDescriptorId(Vamp::Plugin::OutputDescriptor d) const + {return std::string("Output Descriptor '") + d.identifier +"' ";} + inline std::string getDescriptorId(Vamp::Plugin::ParameterDescriptor d) const + {return std::string("Parameter Descriptor '") + d.identifier +"' ";} + inline std::string getDescriptorId(Vamp::Plugin::Feature f) const + {return std::string("Feature (") + f.label + ")"; } + +public: + const bool& error; + +}; + +/* Convert Sample Buffers to Python */ + +/// passing the sample buffers as buitin 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) { + + PyObject *pySampleList = PyList_New((Py_ssize_t) blockSize); + 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. + + switch (Vamp::Plugin::TimeDomain) //(dtype) + { + case Vamp::Plugin::TimeDomain : + + for (size_t j = 0; j < blockSize; ++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 < blockSize/2; ++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) +{ + //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 = (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; + 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 + ((PyArrayObject*)pyChannelArray)->flags = NPY_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