changeset 40:fa3f80d4e340

2D array conversion (incorrect, crashing)
author Chris Cannam
date Wed, 26 Nov 2014 15:58:46 +0000
parents 13dcfe8c7ed7
children 55fcd0e3e513
files PyPluginObject.cpp VectorConversion.cpp VectorConversion.h test_process.py
diffstat 4 files changed, 146 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/PyPluginObject.cpp	Wed Nov 26 15:23:56 2014 +0000
+++ b/PyPluginObject.cpp	Wed Nov 26 15:58:46 2014 +0000
@@ -376,6 +376,52 @@
     return pyFs;
 }
 
+static vector<vector<float> >
+convertPluginInput(PyObject *pyBuffer, int channels, int blockSize)
+{
+    vector<vector<float> > data;
+
+    VectorConversion conv;
+
+    if (PyArray_CheckExact(pyBuffer)) {
+
+        data = conv.Py2DArray_To_FloatVector(pyBuffer);
+
+        if (conv.error) {
+            PyErr_SetString(PyExc_TypeError, conv.getError().str().c_str());
+            return data;
+        }
+
+    } else {
+        
+        if (!PyList_Check(pyBuffer)) {
+            PyErr_SetString(PyExc_TypeError, "List of NumPy Array required for process input.");
+            return data;
+        }
+
+        if (PyList_GET_SIZE(pyBuffer) != channels) {
+            cerr << "Wrong number of channels: got " << PyList_GET_SIZE(pyBuffer) << ", expected " << channels << endl;
+            PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
+            return data;
+        }
+
+        for (int c = 0; c < channels; ++c) {
+            PyObject *cbuf = PyList_GET_ITEM(pyBuffer, c);
+            data.push_back(conv.PyValue_To_FloatVector(cbuf));
+        }
+    
+        for (int c = 0; c < channels; ++c) {
+            if ((int)data[c].size() != blockSize) {
+                cerr << "Wrong number of samples on channel " << c << ": expected " << blockSize << " (plugin's block size), got " << data[c].size() << endl;
+                PyErr_SetString(PyExc_TypeError, "Wrong number of samples");
+                return vector<vector<float> >();
+            }
+        }
+    }
+    
+    return data;
+}
+
 static PyObject *
 process(PyObject *self, PyObject *args)
 {
@@ -386,18 +432,13 @@
                           &pyBuffer,                    // Audio data
                           &pyRealTime)) {               // TimeStamp
         PyErr_SetString(PyExc_TypeError,
-                        "process() takes plugin handle (object), buffer (2D array of channels * samples floats) and timestamp (RealTime) arguments");
+                        "process() takes plugin handle (object), buffer (list of arrays of floats, one array per channel) and timestamp (RealTime) arguments");
         return 0; }
 
     if (!PyRealTime_Check(pyRealTime)) {
-        PyErr_SetString(PyExc_TypeError,"Valid timestamp required.");
+        PyErr_SetString(PyExc_TypeError, "Valid timestamp required.");
         return 0; }
 
-    if (!PyList_Check(pyBuffer)) {
-        PyErr_SetString(PyExc_TypeError, "List of NumPy Array required for process input.");
-        return 0;
-    }
-
     PyPluginObject *pd = getPluginObject(self);
     if (!pd) return 0;
 
@@ -407,37 +448,17 @@
         return 0;
     }
 
-    int channels =  pd->channels;
-
-    if (PyList_GET_SIZE(pyBuffer) != channels) {
-        cerr << "Wrong number of channels: got " << PyList_GET_SIZE(pyBuffer) << ", expected " << channels << endl;
-        PyErr_SetString(PyExc_TypeError, "Wrong number of channels");
-        return 0;
-    }
+    int channels = pd->channels;
+    vector<vector<float> > data =
+        convertPluginInput(pyBuffer, channels, pd->blockSize);
+    if (data.empty()) return 0;
 
     float **inbuf = new float *[channels];
-
-    VectorConversion typeConv;
-
-    vector<vector<float> > data;
     for (int c = 0; c < channels; ++c) {
-        PyObject *cbuf = PyList_GET_ITEM(pyBuffer, c);
-        data.push_back(typeConv.PyValue_To_FloatVector(cbuf));
-    }
-    
-    for (int c = 0; c < channels; ++c) {
-        if (data[c].size() != pd->blockSize) {
-            cerr << "Wrong number of samples on channel " << c << ": expected " << pd->blockSize << " (plugin's block size), got " << data[c].size() << endl;
-            PyErr_SetString(PyExc_TypeError, "Wrong number of samples");
-            return 0;
-        }
         inbuf[c] = &data[c][0];
     }
-
     RealTime timeStamp = *PyRealTime_AsRealTime(pyRealTime);
-
     Plugin::FeatureSet fs = pd->plugin->process(inbuf, timeStamp);
-
     delete[] inbuf;
 
     return convertFeatureSet(fs);
@@ -590,7 +611,7 @@
     PyObject_GenericSetAttr,            /*tp_setattro*/
     0,                                  /*tp_as_buffer*/
     Py_TPFLAGS_DEFAULT,                 /*tp_flags*/
-    "Vamp plugin object.",                    /*tp_doc*/
+    "Plugin object, providing a low-level API for running a Vamp plugin.", /*tp_doc*/
     0,                                  /*tp_traverse*/
     0,                                  /*tp_clear*/
     0,                                  /*tp_richcompare*/
--- a/VectorConversion.cpp	Wed Nov 26 15:23:56 2014 +0000
+++ b/VectorConversion.cpp	Wed Nov 26 15:58:46 2014 +0000
@@ -136,7 +136,7 @@
     }
 
     if (PyArray_NDIM(pyArray) != 1) {
-        string msg = "NumPy array must be a one dimensional vector.";
+        string msg = "NumPy array must be a one-dimensional vector.";
         setValueError(msg);
         return v;
     }
@@ -165,6 +165,68 @@
     }
 }
 
+vector<vector<float> >
+VectorConversion::Py2DArray_To_FloatVector (PyObject *pyValue) const 
+{
+    vector<vector<float> > v;
+	
+    if (!PyArray_Check(pyValue)) {
+        setValueError("Value is not an array");
+        return v;
+    } 
+
+    PyArrayObject* pyArray = (PyArrayObject*) pyValue;
+    PyArray_Descr* descr = PyArray_DESCR(pyArray);
+	
+    if (PyArray_DATA(pyArray) == 0 || descr == 0) {
+        string msg = "NumPy array with NULL data or descriptor pointer encountered.";
+        setValueError(msg);
+        return v;
+    }
+
+    if (PyArray_NDIM(pyArray) != 2) {
+        string msg = "NumPy array must be a two-dimensional matrix.";
+        setValueError(msg);
+        return v;
+    }
+
+    /// check strides (useful if array is not continuous)
+    size_t strides =  *((size_t*) PyArray_STRIDES(pyArray));
+
+    cerr << "dims = " << PyArray_DIMS(pyArray)[0] << "x" << PyArray_DIMS(pyArray)[1] << ", strides = " << strides << endl;
+    
+    /// convert the array
+    for (int i = 0; i < PyArray_DIMS(pyArray)[0]; ++i) {
+
+        vector<float> vv;
+        
+        switch (descr->type_num) {
+        
+        case NPY_FLOAT : // dtype='float32'
+            vv = PyArray_Convert<float,float>(PyArray_GETPTR2(pyArray, i, 0),PyArray_DIMS(pyArray)[1],strides);
+            break;
+        case NPY_DOUBLE : // dtype='float64'
+            vv = PyArray_Convert<float,double>(PyArray_GETPTR2(pyArray, i, 0),PyArray_DIMS(pyArray)[1],strides);
+            break;
+        case NPY_INT : // dtype='int'
+            vv = PyArray_Convert<float,int>(PyArray_GETPTR2(pyArray, i, 0),PyArray_DIMS(pyArray)[1],strides);
+            break;
+        case NPY_LONG : // dtype='long'
+            vv = PyArray_Convert<float,long>(PyArray_GETPTR2(pyArray, i, 0),PyArray_DIMS(pyArray)[1],strides);
+            break;
+        default :
+            string msg = "Unsupported value type in NumPy array object.";
+            cerr << "VectorConversion::PyArray_To_FloatVector failed (value type = " << descr->type_num << "). Error: " << msg << endl;
+            setValueError(msg);
+            return v;
+        }
+
+        v.push_back(vv);
+    }
+
+    return v;
+}
+
 PyObject *
 VectorConversion::PyArray_From_FloatVector(const vector<float> &v) const
 {
--- a/VectorConversion.h	Wed Nov 26 15:23:56 2014 +0000
+++ b/VectorConversion.h	Wed Nov 26 15:58:46 2014 +0000
@@ -87,7 +87,8 @@
     std::vector<float> PyValue_To_FloatVector (PyObject*) const;
     std::vector<float> PyArray_To_FloatVector (PyObject *) const;
     std::vector<float> PyList_To_FloatVector (PyObject*) const;
-
+    std::vector<std::vector<float> > Py2DArray_To_FloatVector (PyObject *) const;
+    
     PyObject *PyValue_From_StringVector(const std::vector<std::string> &) const;
     PyObject *PyArray_From_FloatVector(const std::vector<float> &) const;
 
@@ -100,6 +101,8 @@
     std::vector<RET> PyArray_Convert(void* raw_data_ptr,
                                      int length,
                                      size_t strides) const {
+
+        std::cerr << "PyArray_Convert: raw pointer is " << (long long)(raw_data_ptr) << std::endl;
         
         std::vector<RET> v(length);
 		
--- a/test_process.py	Wed Nov 26 15:23:56 2014 +0000
+++ b/test_process.py	Wed Nov 26 15:58:46 2014 +0000
@@ -1,5 +1,6 @@
 
 import vampyhost as vh
+import numpy as np
 
 testPluginKey = "vamp-test-plugin:vamp-test-plugin"
 
@@ -26,12 +27,33 @@
     plug = vh.loadPlugin(testPluginKey, rate)
     try:
         plug.process([[1,2,3,4]], vh.RealTime(0, 0))
-        assert(False)
+        assert False
     except StandardError:
         pass
 
-def test_process():
+def test_process_input_format():
     plug = vh.loadPlugin(testPluginKey, rate)
-    plug.initialise(1, 4, 4) # channels, stepsize, blocksize
-    result = plug.process([[1,2,3,4]], vh.RealTime(0, 0))
+    plug.initialise(2, 4, 4) # channels, stepsize, blocksize
+    result = plug.process([[1,2,3,4],[5,6,7,8]], vh.RealTime(0, 0))
+    result = plug.process([np.array([1,2,3,4]),np.array([5,6,7,8])], vh.RealTime(0, 0))
+    result = plug.process(np.array([[1,2,3,4],[5,6,7,8]]), vh.RealTime(0, 0))
+    try:
+        # Wrong number of channels
+        result = plug.process(np.array([[1,2,3,4]]), vh.RealTime(0, 0))
+        assert False
+    except TypeError:
+        pass
+    try:
+        # Wrong number of samples per channel
+        result = plug.process(np.array([[1,2,3],[4,5,6]]), vh.RealTime(0, 0))
+        assert False
+    except TypeError:
+        pass
+    try:
+        # Differing numbers of samples per channel
+        result = plug.process(np.array([[1,2,3,4],[5,6,7]]), vh.RealTime(0, 0))
+        assert False
+    except TypeError:
+        pass
 
+