Mercurial > hg > vampy
view PyTypeInterface.h @ 92:a6718f9fe942
If a module appears to redefine one of our own types, refuse to load it. Also clear out the class dict for all refused modules now, so that we don't get stale names on the next scan due to not having cleared the module on unload
author | Chris Cannam |
---|---|
date | Mon, 14 Jan 2019 16:19:44 +0000 |
parents | ef4989f33648 |
children |
line wrap: on
line source
/* -*- 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.) */ /* 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 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #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, valueNames }; } 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;} void setNumpyInstalled(bool b) {m_numpyInstalled = 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 */ /// 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; } /// 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; } #ifdef HAVE_NUMPY /// 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; } #endif //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; bool m_numpyInstalled; 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 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