From ae6e15c80aa3700300828afe172fd5fad3b9cdc5 Mon Sep 17 00:00:00 2001 From: Paul Brossier Date: Wed, 30 Sep 2009 17:42:13 +0200 Subject: [PATCH] interfaces/python: improve draft wrapper alpha_norm example, add some unit tests --- interfaces/python/README | 5 + interfaces/python/aubio-types.h | 13 ++ interfaces/python/aubiomodule.c | 328 ++++++-------------------------- interfaces/python/py-fvec.c | 260 +++++++++++++++++++++++++ interfaces/python/setup.py | 2 +- interfaces/python/test_aubio.py | 72 +++++++ 6 files changed, 410 insertions(+), 270 deletions(-) create mode 100644 interfaces/python/README create mode 100644 interfaces/python/aubio-types.h create mode 100644 interfaces/python/py-fvec.c create mode 100644 interfaces/python/test_aubio.py diff --git a/interfaces/python/README b/interfaces/python/README new file mode 100644 index 00000000..adfb22c5 --- /dev/null +++ b/interfaces/python/README @@ -0,0 +1,5 @@ +This module wraps the aubio library for python using the numpy module. + +http://docs.python.org/c-api/index.html + +http://docs.scipy.org/doc/numpy/reference/c-api.html diff --git a/interfaces/python/aubio-types.h b/interfaces/python/aubio-types.h new file mode 100644 index 00000000..82fae6ac --- /dev/null +++ b/interfaces/python/aubio-types.h @@ -0,0 +1,13 @@ +#include +#include +#define NO_IMPORT_ARRAY +#include +#include + +typedef struct +{ + PyObject_HEAD fvec_t * o; + uint_t length; + uint_t channels; +} Py_fvec; +extern PyTypeObject Py_fvecType; diff --git a/interfaces/python/aubiomodule.c b/interfaces/python/aubiomodule.c index eb21dea4..ffa3294e 100644 --- a/interfaces/python/aubiomodule.c +++ b/interfaces/python/aubiomodule.c @@ -1,293 +1,81 @@ #include -#include +#define PY_ARRAY_UNIQUE_SYMBOL PyArray_API #include -#include -static char aubio_module_doc[] = "Python module for the aubio library"; - -/* fvec type definition - -class fvec(): - def __init__(self, length = 1024, channels = 1): - self.length = length - self.channels = channels - self.data = array(length, channels) - -*/ +#include "aubio-types.h" -#define Py_fvec_default_length 1024 -#define Py_fvec_default_channels 1 - -static char Py_fvec_doc[] = "fvec object"; - -typedef struct -{ - PyObject_HEAD fvec_t * o; - uint_t length; - uint_t channels; -} Py_fvec; +static char Py_alpha_norm_doc[] = "compute alpha normalisation factor"; static PyObject * -Py_fvec_new (PyTypeObject * type, PyObject * args, PyObject * kwds) +Py_alpha_norm (PyObject * self, PyObject * args) { - int length = 0, channels = 0; - Py_fvec *self; - static char *kwlist[] = { "length", "channels", NULL }; - - if (!PyArg_ParseTupleAndKeywords (args, kwds, "|II", kwlist, - &length, &channels)) { - return NULL; - } - - - self = (Py_fvec *) type->tp_alloc (type, 0); - - self->length = Py_fvec_default_length; - self->channels = Py_fvec_default_channels; - - if (self == NULL) { - return NULL; - } + PyObject *input; + Py_fvec *vec; + smpl_t alpha; + PyObject *result; + PyObject *array; + uint_t i; - if (length > 0) { - self->length = length; - } else if (length < 0) { - PyErr_SetString (PyExc_ValueError, - "can not use negative number of elements"); + if (!PyArg_ParseTuple (args, "Of:alpha_norm", &input, &alpha)) { return NULL; } - if (channels > 0) { - self->channels = channels; - } else if (channels < 0) { - PyErr_SetString (PyExc_ValueError, - "can not use negative number of channels"); + if (input == NULL) { return NULL; } + // parsing input object into a Py_fvec + if (PyObject_TypeCheck (input, &Py_fvecType)) { + // input is an fvec, nothing else to do + vec = (Py_fvec *) input; + } else if (PyArray_Check(input)) { - return (PyObject *) self; -} - -static int -Py_fvec_init (Py_fvec * self, PyObject * args, PyObject * kwds) -{ - self->o = new_fvec (self->length, self->channels); - if (self->o == NULL) { - return -1; - } - - return 0; -} - -static void -Py_fvec_del (Py_fvec * self) -{ - del_fvec (self->o); - self->ob_type->tp_free ((PyObject *) self); -} - -static PyObject * -Py_fvec_repr (Py_fvec * self, PyObject * unused) -{ - PyObject *format = NULL; - PyObject *args = NULL; - PyObject *result = NULL; - - format = PyString_FromString ("aubio fvec of %d elements with %d channels"); - if (format == NULL) { - goto fail; - } - - args = Py_BuildValue ("II", self->length, self->channels); - if (args == NULL) { - goto fail; - } - - result = PyString_Format (format, args); - -fail: - Py_XDECREF (format); - Py_XDECREF (args); - - return result; -} - -static PyObject * -Py_fvec_print (Py_fvec * self, PyObject * unused) -{ - fvec_print (self->o); - return Py_None; -} - -static PyObject * -Py_fvec_array (Py_fvec * self) -{ - PyObject *array = NULL; - if (self->channels == 1) { - npy_intp dims[] = { self->length, 1 }; - array = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[0]); - } else { - uint_t i; - npy_intp dims[] = { self->length, 1 }; - PyObject *concat = PyList_New (0), *tmp = NULL; - for (i = 0; i < self->channels; i++) { - tmp = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[i]); - PyList_Append (concat, tmp); - Py_DECREF (tmp); + // we got an array, convert it to an fvec + if (PyArray_NDIM (input) == 0) { + PyErr_SetString (PyExc_ValueError, "input array is a scalar"); + goto fail; + } else if (PyArray_NDIM (input) > 2) { + PyErr_SetString (PyExc_ValueError, "input array has more than two dimensions"); + goto fail; } - array = PyArray_FromObject (concat, NPY_FLOAT, 2, 2); - Py_DECREF (concat); - } - return array; -} -static Py_ssize_t -Py_fvec_getchannels (Py_fvec * self) -{ - return self->channels; -} - -static PyObject * -Py_fvec_getitem (Py_fvec * self, Py_ssize_t index) -{ - PyObject *array; - - if (index < 0 || index >= self->channels) { - PyErr_SetString (PyExc_IndexError, "no such channel"); - return NULL; - } - - npy_intp dims[] = { self->length, 1 }; - array = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[index]); - return array; -} - -static int -Py_fvec_setitem (Py_fvec * self, Py_ssize_t index, PyObject * o) -{ - PyObject *array; - - if (index < 0 || index >= self->channels) { - PyErr_SetString (PyExc_IndexError, "no such channel"); - return -1; - } - - array = PyArray_FROM_OT (o, NPY_FLOAT); - if (array == NULL) { - PyErr_SetString (PyExc_ValueError, "should be an array of float"); - goto fail; - } - - if (PyArray_NDIM (array) != 1) { - PyErr_SetString (PyExc_ValueError, "should be a one-dimensional array"); - goto fail; - } - - if (PyArray_SIZE (array) != self->length) { - PyErr_SetString (PyExc_ValueError, - "should be an array of same length as target fvec"); - goto fail; - } - - self->o->data[index] = (smpl_t *) PyArray_GETPTR1 (array, 0); - - return 0; - -fail: - return -1; -} - -static PyMemberDef Py_fvec_members[] = { - // TODO remove READONLY flag and define getter/setter - {"length", T_INT, offsetof (Py_fvec, length), READONLY, - "length attribute"}, - {"channels", T_INT, offsetof (Py_fvec, channels), READONLY, - "channels attribute"}, - {NULL} /* Sentinel */ -}; - -static PyMethodDef Py_fvec_methods[] = { - {"dump", (PyCFunction) Py_fvec_print, METH_NOARGS, - "Dumps the contents of the vector to stdout."}, - {"__array__", (PyCFunction) Py_fvec_array, METH_NOARGS, - "Returns the first channel as a numpy array."}, - {NULL} -}; - -static PySequenceMethods Py_fvec_tp_as_sequence = { - (lenfunc) Py_fvec_getchannels, /* sq_length */ - 0, /* sq_concat */ - 0, /* sq_repeat */ - (ssizeargfunc) Py_fvec_getitem, /* sq_item */ - 0, /* sq_slice */ - (ssizeobjargproc) Py_fvec_setitem, /* sq_ass_item */ - 0, /* sq_ass_slice */ - 0, /* sq_contains */ - 0, /* sq_inplace_concat */ - 0, /* sq_inplace_repeat */ -}; - - -static PyTypeObject Py_fvecType = { - PyObject_HEAD_INIT (NULL) - 0, /* ob_size */ - "fvec", /* tp_name */ - sizeof (Py_fvec), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor) Py_fvec_del, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc) Py_fvec_repr, /* tp_repr */ - 0, /* tp_as_number */ - &Py_fvec_tp_as_sequence, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - Py_fvec_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Py_fvec_methods, /* tp_methods */ - Py_fvec_members, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) Py_fvec_init, /* tp_init */ - 0, /* tp_alloc */ - Py_fvec_new, /* tp_new */ -}; - -/* end of fvec type definition */ + if (!PyArray_ISFLOAT (input)) { + PyErr_SetString (PyExc_ValueError, "input array should be float"); + goto fail; + } else if (PyArray_TYPE (input) != NPY_FLOAT) { + // input data type is not float32, casting + array = PyArray_Cast ( (PyArrayObject*) input, NPY_FLOAT); + if (array == NULL) { + PyErr_SetString (PyExc_IndexError, "failed converting to NPY_FLOAT"); + goto fail; + } + } else { + // input data type is float32, nothing else to do + array = input; + } -static PyObject * -Py_alpha_norm (PyObject * self, PyObject * args) -{ - Py_fvec *vec; - smpl_t alpha; - PyObject *result; + // create a new fvec object + vec = (Py_fvec*) PyObject_New (Py_fvec, &Py_fvecType); + if (PyArray_NDIM (array) == 1) { + vec->channels = 1; + vec->length = PyArray_SIZE (array); + } else { + vec->channels = PyArray_DIM (array, 0); + vec->length = PyArray_DIM (array, 1); + } - if (!PyArg_ParseTuple (args, "Of:alpha_norm", &vec, &alpha)) { - return NULL; - } + // FIXME should not need to allocate fvec + vec->o = new_fvec (vec->length, vec->channels); + for (i = 0; i < vec->channels; i++) { + vec->o->data[i] = (smpl_t *) PyArray_GETPTR1 (array, i); + } - if (vec == NULL) { + } else { + PyErr_SetString (PyExc_ValueError, "can only accept array or fvec as input"); return NULL; } + // compute the function result = Py_BuildValue ("f", vec_alpha_norm (vec->o, alpha)); if (result == NULL) { return NULL; @@ -295,15 +83,17 @@ Py_alpha_norm (PyObject * self, PyObject * args) return result; +fail: + return NULL; } -static char Py_alpha_norm_doc[] = "compute alpha normalisation factor"; - static PyMethodDef aubio_methods[] = { {"alpha_norm", Py_alpha_norm, METH_VARARGS, Py_alpha_norm_doc}, {NULL, NULL} /* Sentinel */ }; +static char aubio_module_doc[] = "Python module for the aubio library"; + PyMODINIT_FUNC init_aubio (void) { diff --git a/interfaces/python/py-fvec.c b/interfaces/python/py-fvec.c new file mode 100644 index 00000000..72d812ed --- /dev/null +++ b/interfaces/python/py-fvec.c @@ -0,0 +1,260 @@ +#include "aubio-types.h" + +/* fvec type definition + +class fvec(): + def __init__(self, length = 1024, channels = 1): + self.length = length + self.channels = channels + self.data = array(length, channels) + +*/ + +#define Py_fvec_default_length 1024 +#define Py_fvec_default_channels 1 + +static char Py_fvec_doc[] = "fvec object"; + +static PyObject * +Py_fvec_new (PyTypeObject * type, PyObject * args, PyObject * kwds) +{ + int length= 0, channels = 0; + Py_fvec *self; + static char *kwlist[] = { "length", "channels", NULL }; + + if (!PyArg_ParseTupleAndKeywords (args, kwds, "|II", kwlist, + &length, &channels)) { + return NULL; + } + + + self = (Py_fvec *) type->tp_alloc (type, 0); + + self->length = Py_fvec_default_length; + self->channels = Py_fvec_default_channels; + + if (self == NULL) { + return NULL; + } + + if (length > 0) { + self->length = length; + } else if (length < 0) { + PyErr_SetString (PyExc_ValueError, + "can not use negative number of elements"); + return NULL; + } + + if (channels > 0) { + self->channels = channels; + } else if (channels < 0) { + PyErr_SetString (PyExc_ValueError, + "can not use negative number of channels"); + return NULL; + } + + + return (PyObject *) self; +} + +static int +Py_fvec_init (Py_fvec * self, PyObject * args, PyObject * kwds) +{ + self->o = new_fvec (self->length, self->channels); + if (self->o == NULL) { + return -1; + } + + return 0; +} + +static void +Py_fvec_del (Py_fvec * self) +{ + del_fvec (self->o); + self->ob_type->tp_free ((PyObject *) self); +} + +static PyObject * +Py_fvec_repr (Py_fvec * self, PyObject * unused) +{ + PyObject *format = NULL; + PyObject *args = NULL; + PyObject *result = NULL; + + format = PyString_FromString ("aubio fvec of %d elements with %d channels"); + if (format == NULL) { + goto fail; + } + + args = Py_BuildValue ("II", self->length, self->channels); + if (args == NULL) { + goto fail; + } + + result = PyString_Format (format, args); + +fail: + Py_XDECREF (format); + Py_XDECREF (args); + + return result; +} + +static PyObject * +Py_fvec_print (Py_fvec * self, PyObject * unused) +{ + fvec_print (self->o); + return Py_None; +} + +static PyObject * +Py_fvec_array (Py_fvec * self) +{ + PyObject *array = NULL; + if (self->channels == 1) { + npy_intp dims[] = { self->length, 1 }; + array = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[0]); + } else { + uint_t i; + npy_intp dims[] = { self->length, 1 }; + PyObject *concat = PyList_New (0), *tmp = NULL; + for (i = 0; i < self->channels; i++) { + tmp = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[i]); + PyList_Append (concat, tmp); + Py_DECREF (tmp); + } + array = PyArray_FromObject (concat, NPY_FLOAT, 2, 2); + Py_DECREF (concat); + } + return array; +} + +static Py_ssize_t +Py_fvec_getchannels (Py_fvec * self) +{ + return self->channels; +} + +static PyObject * +Py_fvec_getitem (Py_fvec * self, Py_ssize_t index) +{ + PyObject *array; + + if (index < 0 || index >= self->channels) { + PyErr_SetString (PyExc_IndexError, "no such channel"); + return NULL; + } + + npy_intp dims[] = { self->length, 1 }; + array = PyArray_SimpleNewFromData (1, dims, NPY_FLOAT, self->o->data[index]); + return array; +} + +static int +Py_fvec_setitem (Py_fvec * self, Py_ssize_t index, PyObject * o) +{ + PyObject *array; + + if (index < 0 || index >= self->channels) { + PyErr_SetString (PyExc_IndexError, "no such channel"); + return -1; + } + + array = PyArray_FROM_OT (o, NPY_FLOAT); + if (array == NULL) { + PyErr_SetString (PyExc_ValueError, "should be an array of float"); + goto fail; + } + + if (PyArray_NDIM (array) != 1) { + PyErr_SetString (PyExc_ValueError, "should be a one-dimensional array"); + goto fail; + } + + if (PyArray_SIZE (array) != self->length) { + PyErr_SetString (PyExc_ValueError, + "should be an array of same length as target fvec"); + goto fail; + } + + self->o->data[index] = (smpl_t *) PyArray_GETPTR1 (array, 0); + + return 0; + +fail: + return -1; +} + +static PyMemberDef Py_fvec_members[] = { + // TODO remove READONLY flag and define getter/setter + {"length", T_INT, offsetof (Py_fvec, length), READONLY, + "length attribute"}, + {"channels", T_INT, offsetof (Py_fvec, channels), READONLY, + "channels attribute"}, + {NULL} /* Sentinel */ +}; + +static PyMethodDef Py_fvec_methods[] = { + {"dump", (PyCFunction) Py_fvec_print, METH_NOARGS, + "Dumps the contents of the vector to stdout."}, + {"__array__", (PyCFunction) Py_fvec_array, METH_NOARGS, + "Returns the first channel as a numpy array."}, + {NULL} +}; + +static PySequenceMethods Py_fvec_tp_as_sequence = { + (lenfunc) Py_fvec_getchannels, /* sq_length */ + 0, /* sq_concat */ + 0, /* sq_repeat */ + (ssizeargfunc) Py_fvec_getitem, /* sq_item */ + 0, /* sq_slice */ + (ssizeobjargproc) Py_fvec_setitem, /* sq_ass_item */ + 0, /* sq_ass_slice */ + 0, /* sq_contains */ + 0, /* sq_inplace_concat */ + 0, /* sq_inplace_repeat */ +}; + + +PyTypeObject Py_fvecType = { + PyObject_HEAD_INIT (NULL) + 0, /* ob_size */ + "fvec", /* tp_name */ + sizeof (Py_fvec), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) Py_fvec_del, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc) Py_fvec_repr, /* tp_repr */ + 0, /* tp_as_number */ + &Py_fvec_tp_as_sequence, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_fvec_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Py_fvec_methods, /* tp_methods */ + Py_fvec_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc) Py_fvec_init, /* tp_init */ + 0, /* tp_alloc */ + Py_fvec_new, /* tp_new */ +}; diff --git a/interfaces/python/setup.py b/interfaces/python/setup.py index bb2aa869..17aa301a 100644 --- a/interfaces/python/setup.py +++ b/interfaces/python/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup, Extension setup(name="_aubio", version="1.0", ext_modules = [ Extension("_aubio", - ["aubiomodule.c"], + ["aubiomodule.c", "py-fvec.c"], include_dirs=['../../build/default/src', '../../src' ], library_dirs=['../../build/default/src', '../../src/.libs' ], libraries=['aubio'])]) diff --git a/interfaces/python/test_aubio.py b/interfaces/python/test_aubio.py new file mode 100644 index 00000000..9532a54e --- /dev/null +++ b/interfaces/python/test_aubio.py @@ -0,0 +1,72 @@ +import unittest +from _aubio import * +from numpy import array + +class aubiomodule_test_case(unittest.TestCase): + + def setUp(self): + """ try importing aubio """ + + def test_vector(self): + a = fvec() + a.length, a.channels + a[0] + array(a) + a = fvec(10) + a = fvec(1, 2) + array(a).T + a[0] = range(a.length) + a[1][0] = 2 + + def test_wrong_values(self): + self.assertRaises (ValueError, fvec, -10) + self.assertRaises (ValueError, fvec, 1, -1) + + a = fvec(2, 3) + self.assertRaises (IndexError, a.__getitem__, 3) + self.assertRaises (IndexError, a[0].__getitem__, 2) + + def test_alpha_norm_of_fvec(self): + a = fvec(2, 2) + self.assertEquals (alpha_norm(a, 1), 0) + a[0] = [1, 2] + self.assertEquals (alpha_norm(a, 1), 1.5) + a[1] = [1, 2] + self.assertEquals (alpha_norm(a, 1), 3) + a[0] = [0, 1]; a[1] = [1, 0] + self.assertEquals (alpha_norm(a, 2), 1) + + def test_alpha_norm_of_array_of_float32(self): + a = array(1, dtype = 'float32') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array([[[1,2],[3,4]]], dtype = 'float32') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array(range(10), dtype = 'float32') + self.assertEquals (alpha_norm(a, 1), 4.5) + a = array([range(10), range(10)], dtype = 'float32') + self.assertEquals (alpha_norm(a, 1), 9) + + def test_alpha_norm_of_array_of_float64(self): + a = array(1, dtype = 'float64') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array([[[1,2],[3,4]]], dtype = 'float64') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array(range(10), dtype = 'float64') + self.assertEquals (alpha_norm(a, 1), 4.5) + a = array([range(10), range(10)], dtype = 'float64') + self.assertEquals (alpha_norm(a, 1), 9) + + def test_alpha_norm_of_array_of_int(self): + a = array(1, dtype = 'int') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array([[[1,2],[3,4]]], dtype = 'int') + self.assertRaises (ValueError, alpha_norm, a, 1) + a = array(range(10), dtype = 'int') + self.assertRaises (ValueError, alpha_norm, a, 1) + + def test_alpha_norm_of_array_of_string (self): + a = "hello" + self.assertRaises (ValueError, alpha_norm, a, 1) + +if __name__ == '__main__': + unittest.main() -- 2.26.2