Mercurial > hg > vampy
changeset 51:c1e4f706ca9a
Fix numpy version incompatibility issues and updated some example plugins.
author | fazekasgy |
---|---|
date | Thu, 08 Oct 2009 08:47:28 +0000 |
parents | 3868da185d73 |
children | d56f48aafb99 |
files | PyOutputDescriptor.cpp PyParameterDescriptor.cpp PyPlugin.cpp PyPlugin.h PyTypeInterface.cpp PyTypeInterface.h README vampy-main.cpp |
diffstat | 8 files changed, 222 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/PyOutputDescriptor.cpp Tue Oct 06 12:37:01 2009 +0000 +++ b/PyOutputDescriptor.cpp Thu Oct 08 08:47:28 2009 +0000 @@ -88,6 +88,7 @@ Py_INCREF(v); return v; } + PyErr_SetString(PyExc_AttributeError,"non-existing OutputDescriptor attribute"); } return NULL; }
--- a/PyParameterDescriptor.cpp Tue Oct 06 12:37:01 2009 +0000 +++ b/PyParameterDescriptor.cpp Thu Oct 08 08:47:28 2009 +0000 @@ -89,6 +89,7 @@ Py_INCREF(v); return v; } + PyErr_SetString(PyExc_AttributeError,"non-existing ParameterDescriptor attribute"); } return NULL; }
--- a/PyPlugin.cpp Tue Oct 06 12:37:01 2009 +0000 +++ b/PyPlugin.cpp Thu Oct 08 08:47:28 2009 +0000 @@ -30,7 +30,7 @@ Mutex PyPlugin::m_pythonInterpreterMutex; -PyPlugin::PyPlugin(std::string pluginKey, float inputSampleRate, PyObject *pyClass, int &instcount) : +PyPlugin::PyPlugin(std::string pluginKey, float inputSampleRate, PyObject *pyClass, int &instcount, bool &numpyInstalled) : Plugin(inputSampleRate), m_pyClass(pyClass), m_instcount(instcount), @@ -44,7 +44,9 @@ m_pyProcess(NULL), m_inputDomain(TimeDomain), m_quitOnErrorFlag(false), - m_debugFlag(false) + m_debugFlag(false), + m_numpyInstalled(numpyInstalled), + m_processFailure(false) { m_ti.setInputSampleRate(inputSampleRate); MutexLocker locker(&m_pythonInterpreterMutex); @@ -86,6 +88,7 @@ if (m_debugFlag && st_flag) cerr << "Strict type conversion ON for: " << m_class << endl; m_ti.setStrictTypingFlag(st_flag); + m_ti.setNumpyInstalled(m_numpyInstalled); } @@ -188,6 +191,7 @@ PyPlugin::reset() { MutexLocker locker(&m_pythonInterpreterMutex); + m_processFailure = false; genericMethodCall("reset"); } @@ -290,6 +294,8 @@ return FeatureSet(); } + if (m_processFailure) return FeatureSet(); + return processMethodCall(inputBuffers,timestamp); } @@ -298,6 +304,7 @@ PyPlugin::getRemainingFeatures() { MutexLocker locker(&m_pythonInterpreterMutex); + if (m_processFailure) return FeatureSet(); FeatureSet rValue; return genericMethodCall("getRemainingFeatures",rValue); } @@ -355,6 +362,7 @@ //quering process implementation type char legacyMethod[]="process"; char numpyMethod[]="processN"; + m_processFailure = false; if (PyObject_HasAttrString(m_pyInstance,legacyMethod) && m_processType == 0) @@ -384,33 +392,68 @@ if (m_vampyFlags & vf_ARRAY) { #ifdef HAVE_NUMPY - m_processType = numpy_arrayProcess; - if (m_debugFlag) cerr << "Process using numpy array interface." << endl; + if (m_numpyInstalled) { m_processType = numpy_arrayProcess; + if (m_debugFlag) + cerr << "Process using numpy array interface." << endl; + } + else { + m_processFailure = true; + char method[]="initialise::setProcessType"; + cerr << PLUGIN_ERROR + << "This plugin requests the Numpy array interface by setting " + << " the vf_ARRAY flag in its __init__() function." << endl + << "However, we could not found a version of Numpy compatible with this build of Vampy." << endl + << "If you have a numerical library installed that supports the buffer interface, " << endl + << "you can request this interface instead by setting the vf_BUFFER flag." << endl; + } #else - cerr << "Error: This version of vampy was compiled without numpy support, " - << "however the vf_ARRAY flag is set for plugin: " << m_class << endl - << "The default behaviour is: passing a python list of samples for each channel in process() " - << "or a list of memory buffers in processN(). " << endl - << "This can be used create numpy arrays using the numpy.frombuffer() command." << endl; + m_processFailure = true; + char method[]="initialise::setProcessType"; + cerr << PLUGIN_ERROR + << "Error: This version of vampy was compiled without numpy support, " + << "however the vf_ARRAY flag is set for plugin: " << m_class << endl + << "The default behaviour is: passing a python list of samples for each channel in process() " + << "or a list of memory buffers in processN(). " << endl + << "This can be used create numpy arrays using the numpy.frombuffer() command." << endl; #endif } - if (!m_processType) + if (!m_pyProcessCallable) { m_processType = not_implemented; m_pyProcess = NULL; - m_pyProcessCallable = NULL; char method[]="initialise::setProcessType"; cerr << PLUGIN_ERROR << " No process implementation found. Plugin will do nothing." << endl; + m_processFailure = true; } } void -PyPlugin::typeErrorHandler(char *method) const +PyPlugin::typeErrorHandler(char *method, bool process) const { bool strict = false; while (m_ti.error) { PyTypeInterface::ValueError e = m_ti.getError(); +#ifdef HAVE_NUMPY + // disable the process completely if numpy types are returned + // but a compatible version was not loaded. + // This is required because if an object is returned from + // the wrong build, malloc complains about its size + // (i.e. the interpreter doesn't free it properly) + // and the process may be leaking. + // Note: this only happens in the obscure situation when + // someone forces to return wrong numpy types from an + // incompatible version using the buffer interface. + // In this case the incampatible library is still usable, + // but manual conversion to python builtins is required. + // If the ARRAY interface is set but Numpy is not installed + // the process will be disabled already at initialisation. + if (process && !m_numpyInstalled && e.str().find("numpy")!=std::string::npos) + { + m_processFailure = true; + cerr << "Warning: incompatible numpy type encountered. Disabling process." << endl; + } +#endif cerr << PLUGIN_ERROR << e.str() << endl; if (e.strict) strict = true; // e.print(); @@ -422,5 +465,9 @@ /// It would be best if hosts could catch an exception instead /// and display something meaningful to the user. if (strict && m_quitOnErrorFlag) exit(EXIT_FAILURE); + + // this would disable all outputs even if some are valid + // if (process) m_processFailure = true; + }
--- a/PyPlugin.h Tue Oct 06 12:37:01 2009 +0000 +++ b/PyPlugin.h Thu Oct 08 08:47:28 2009 +0000 @@ -73,7 +73,7 @@ class PyPlugin : public Vamp::Plugin { public: - PyPlugin(std::string plugin,float inputSampleRate, PyObject *pyClass, int &instcount); + PyPlugin(std::string plugin,float inputSampleRate, PyObject *pyClass, int &instcount, bool &numpyInstalled); virtual ~PyPlugin(); bool initialise(size_t channels, size_t stepSize, size_t blockSize); @@ -122,6 +122,8 @@ bool m_quitOnErrorFlag; bool m_debugFlag; bool m_useRealTimeFlag; + bool m_numpyInstalled; + mutable bool m_processFailure; void setProcessType(); @@ -129,7 +131,7 @@ bool getBooleanFlag(char flagName[],bool) const; int getBinaryFlags(char flagName[], eVampyFlags) const; - void typeErrorHandler(char *method) const; + void typeErrorHandler(char *method, bool process = false) const; /// simple 'void return' call with no args void genericMethodCall(char *method) const @@ -397,7 +399,7 @@ inline PyPlugin::FeatureSet PyPlugin::processMethodCall(const float *const *inputBuffers,Vamp::RealTime timestamp) { - + /// Optimizations: 1) we avoid ...ObjArg functions since we know /// the number of arguments, and we don't like va_list parsing /// in the process. 2) Also: we're supposed to incref args, @@ -410,16 +412,19 @@ PyObject *pyChannelList = NULL; if (m_processType == numpy_bufferProcess) { - pyChannelList = m_ti.InputBuffers_As_SharedMemoryList(inputBuffers,m_channels,m_blockSize,m_inputDomain); + pyChannelList = m_ti.InputBuffers_As_SharedMemoryList( + inputBuffers,m_channels,m_blockSize,m_inputDomain); } if (m_processType == legacyProcess) { - pyChannelList = m_ti.InputBuffers_As_PythonLists(inputBuffers,m_channels,m_blockSize,m_inputDomain); + pyChannelList = m_ti.InputBuffers_As_PythonLists( + inputBuffers,m_channels,m_blockSize,m_inputDomain); } #ifdef HAVE_NUMPY if (m_processType == numpy_arrayProcess) { - pyChannelList = m_ti.InputBuffers_As_NumpyArray(inputBuffers,m_channels,m_blockSize,m_inputDomain); + pyChannelList = m_ti.InputBuffers_As_NumpyArray( + inputBuffers,m_channels,m_blockSize,m_inputDomain); } #endif @@ -481,7 +486,7 @@ Py_DECREF(pyValue); Py_DECREF(pyArgs); } else { - typeErrorHandler(PyString_AsString(m_pyProcess)); + typeErrorHandler(PyString_AsString(m_pyProcess),true); Py_CLEAR(pyValue); Py_CLEAR(pyArgs); }
--- a/PyTypeInterface.cpp Tue Oct 06 12:37:01 2009 +0000 +++ b/PyTypeInterface.cpp Thu Oct 08 08:47:28 2009 +0000 @@ -45,6 +45,7 @@ PyTypeInterface::PyTypeInterface() : m_strict(false), m_error(false), + m_numpyInstalled(false), error(m_error) // const public reference for easy access { } @@ -64,13 +65,13 @@ if (pyValue == NULL) { - setValueError("Error while converting float object.",m_strict); + 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 is not float.",m_strict); + setValueError("Strict conversion error: object" + PyValue_Get_TypeName(pyValue) +" is not float.",m_strict); return 0.0; } @@ -560,9 +561,10 @@ } return Output; } -#ifdef _DEBUG - cerr << "PyTypeInterface::PyValue_To_StringVector: Warning: Value is not list of strings." << endl; -#endif + +// #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'] @@ -583,6 +585,8 @@ { #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) @@ -604,7 +608,7 @@ /// numpy array if (PyArray_CheckExact(pyValue)) return PyArray_To_FloatVector(pyValue); - +} #endif /// python list of floats (backward compatible) @@ -621,7 +625,7 @@ 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; + cerr << "PyTypeInterface::PyValue_To_FloatVector failed. " << msg << endl; #endif } return Output; @@ -674,7 +678,9 @@ return Output; } -#ifdef HAVE_NUMPY +// 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 { @@ -744,7 +750,7 @@ #endif -/// FeatureSet (an integer map of OutputLists) +/// FeatureSet (an integer map of FeatureLists) Vamp::Plugin::FeatureSet PyTypeInterface::PyValue_To_FeatureSet(PyObject* pyValue) const { @@ -798,7 +804,7 @@ return rFeatureSet; } - /// accept no return values + /// accept None return values if (pyValue == Py_None) return rFeatureSet; /// give up
--- a/PyTypeInterface.h Tue Oct 06 12:37:01 2009 +0000 +++ b/PyTypeInterface.h Thu Oct 08 08:47:28 2009 +0000 @@ -124,6 +124,7 @@ // 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; @@ -392,6 +393,7 @@ 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; @@ -432,7 +434,7 @@ /* Convert Sample Buffers to Python */ -/// passing the sample buffers as buitin python lists +/// 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) @@ -446,22 +448,23 @@ 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); - size_t arraySize; - + + size_t arraySize; if (dtype==Vamp::Plugin::FrequencyDomain) - arraySize = blockSize + 2; + 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. + // to prevent such crash: possibly a numpy bug, + // works fine above 1.0.4 - switch (Vamp::Plugin::TimeDomain) //(dtype) + switch (dtype) //(Vamp::Plugin::TimeDomain) { case Vamp::Plugin::TimeDomain : @@ -475,7 +478,7 @@ case Vamp::Plugin::FrequencyDomain : size_t k = 0; - for (size_t j = 0; j < arraySize/2; ++j) { + for (size_t j = 0; j < arraySize; ++j) { PyObject *pyComplex=PyComplex_FromDoubles( (double) inputBuffers[i][k], (double) inputBuffers[i][k+1]);
--- a/README Tue Oct 06 12:37:01 2009 +0000 +++ b/README Thu Oct 08 08:47:28 2009 +0000 @@ -127,15 +127,15 @@ HISTORY: v1: - * added support for NumPy arrays in processN() + * added support for Numpy arrays in processN() * framecount is now passed also to legacy process() and fixed resulting bugs in the PyZeroCrossing plugin * added two examples which use Frequency Domain input in processN() v2.0: * complete rewrite (using generic functions implementing full error checking) * added extension module : support RealTime and other Vamp type wrappers - * added numpy Array interface - * added falgs + * added Numpy Array interface + * added flags * added environment variables * recognise byte compiled python scripts
--- a/vampy-main.cpp Tue Oct 06 12:37:01 2009 +0000 +++ b/vampy-main.cpp Thu Oct 08 08:47:28 2009 +0000 @@ -12,8 +12,25 @@ #include <Python.h> #ifdef HAVE_NUMPY -#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API + +// define a unique API pointer +#define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API #include "numpy/arrayobject.h" + +// prevent building with very old versions of numpy +#ifndef NPY_VERSION +#undef HAVE_NUMPY +#endif + +#endif + +// this is not part of the API, but we will require it for a bug workaround +// define this symbol if you use another version of numpy in the makefile +// Vampy will not attempt to load a lower version than specified +#ifdef HAVE_NUMPY +#ifndef NUMPY_SHORTVERSION +#define NUMPY_SHORTVERSION 1.1 +#endif #endif #include "vamp/vamp.h" @@ -41,6 +58,8 @@ static int adinstcount; static int totinstcount; +static bool numpyInstalled = false; +static bool arrayApiInitialised = false; class PyPluginAdapter : public Vamp::PluginAdapterBase { @@ -66,7 +85,7 @@ Vamp::Plugin *createPlugin(float inputSampleRate) { try { - PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount); + PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount, numpyInstalled); return plugin; } catch (...) { cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl; @@ -81,15 +100,106 @@ bool m_failed; }; + static void array_API_initialiser() { -/// numpy C-API requirement + if (arrayApiInitialised) return; + +/* Numpy 1.3 build note: there seems to be a bug +in this version (at least on OS/X) which will cause memory +access error in the array API import function if an earlier runtime +version of Numpy is used when loading the library. +(below is a horrible workaround) +*/ + #ifdef HAVE_NUMPY + + string ver; + float numpyVersion; + + /// attmept to test numpy version before importing the array API + cerr << "Numpy build information: ABI level: " << NPY_VERSION + << " Numpy version: " << NUMPY_SHORTVERSION << endl; + + PyObject *pyModule, *pyDict, *pyVer; + + pyModule = PyImport_ImportModule("numpy"); //numpy.core.multiarray + if (!pyModule) { + cerr << "Vampy was compiled with Numpy support but Numpy does not seem to be installed." << endl; +#ifdef __APPLE__ + cerr << "Hint: Check if Numpy is installed for the particular setup of Python used by Vampy (given by Python exec prefix)." << endl; +#endif + goto numpyFailure; + } + + pyDict = PyModule_GetDict(pyModule); // borrowed ref + if (!pyDict) { + cerr << "Can not access Numpy module dictionary." << endl; + goto numpyFailure; + } + + pyVer = PyDict_GetItemString(pyDict,"__version__"); //borrowed ref + if (!pyVer) { + cerr << "Can not access Numpy version information." << endl; + goto numpyFailure; + } + + ver = PyString_AsString(pyVer); + ver = ver.substr(0,ver.rfind(".")); + if(EOF == sscanf(ver.c_str(), "%f", &numpyVersion)) + { + cerr << "Could not parse Numpy version information." << endl; + goto numpyFailure; + } + + cerr << "Numpy runtime version: " << numpyVersion << endl; + if (numpyVersion < (float) NUMPY_SHORTVERSION) { + cerr << "Incompatible Numpy version found: " << numpyVersion << endl; + goto numpyFailure; + } + + Py_DECREF(pyModule); + + // At least we catch import errors, but if binary compatibility + // has changed without notice, this would still fail. + // However, we should never get to this point now anyway. import_array(); - if(NPY_VERSION != PyArray_GetNDArrayCVersion()) - cerr << "Warning: Numpy ABI version mismatch. (Build version: " - << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl; + if (PyErr_Occurred()) { + cerr << "Import error while loading the Numpy Array API." << endl; + PyErr_Print(); PyErr_Clear(); + goto numpyFailure; + } + else { + +#ifdef _DEBUG + if (NPY_VERSION != PyArray_GetNDArrayCVersion()) { + // the Import function does this check already. + cerr << "Warning: Numpy version mismatch. (Build version: " + << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl; + goto numpyFailure; + } #endif + + numpyInstalled = true; + arrayApiInitialised = true; + return; + } + + +numpyFailure: + cerr << "Please make sure you have Numpy " << NUMPY_SHORTVERSION << " or greater installed." << endl; + cerr << "Vampy: Numpy support disabled." << endl; + numpyInstalled = false; + arrayApiInitialised = true; + if (pyModule) Py_XDECREF(pyModule); + return; + +/*HAVE_NUMPY*/ +#endif + + numpyInstalled = false; + arrayApiInitialised = true; + return; } @@ -187,6 +297,7 @@ if (PyImport_AppendInittab("vampy",initvampy) != 0) cerr << "Warning: Extension module could not be added to module inittab." << endl; Py_Initialize(); + array_API_initialiser(); initvampy(); #ifdef _DEBUG cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl;