diff --git a/doc/c_cpp_api.rst b/doc/c_cpp_api.rst index 241d529d73f19296a80d8882355e55dd71c9d1b5..73a8583cf5e2350fd263cbb85baf5d7cb6677582 100644 --- a/doc/c_cpp_api.rst +++ b/doc/c_cpp_api.rst @@ -6,11 +6,11 @@ C++ API ========= -The C++ API of ``xbob.core`` allows users to leverage from automatic converters -for classes in :py:class:`xbob.core.random`. To use the C API, clients should -first, include the header file ``<xbob.core/random.h>`` on their compilation -units and then, make sure to call once ``import_xbob_core_random()`` at their -module instantiation, as explained at the `Python manual +The C++ API of ``xbob.io`` allows users to leverage from automatic converters +for classes in :py:class:`xbob.io`. To use the C API, clients should first, +include the header file ``<xbob.io/api.h>`` on their compilation units and +then, make sure to call once ``import_xbob_io()`` at their module +instantiation, as explained at the `Python manual <http://docs.python.org/2/extending/extending.html#using-capsules>`_. Here is a dummy C example showing how to include the header and where to call @@ -18,7 +18,7 @@ the import function: .. code-block:: c++ - #include <xbob.core/random.h> + #include <xbob.io/api.h> PyMODINIT_FUNC initclient(void) { @@ -33,363 +33,40 @@ the import function: import_blitz_array(); // imports xbob.core.random C-API - import_xbob_core_random(); + import_xbob_io(); } .. note:: The include directory can be discovered using - :py:func:`xbob.core.get_include`. + :py:func:`xbob.io.get_include`. -Mersenne Twister Random Number Generator (mt19937) --------------------------------------------------- +Generic Functions +----------------- -This package contains bindings to ``boost::random::mt19937``, which is a -powerful random number generator available within the Boost_ C++ library. +.. cpp:function:: int PyBobIo_AsTypenum(bob::core::array::ElementType et) -.. cpp:type:: PyBoostMt19937Object + Converts the input Bob element type into a ``NPY_<TYPE>`` enumeration value. + Returns ``NPY_NOTYPE`` in case of problems, and sets a + :py:class:`RuntimeError`. - The pythonic object representation for a ``boost::random::mt19937`` object. - - .. code-block:: c - - typedef struct { - PyObject_HEAD - boost::random::mt19937* rng; - } PyBoostMt19937Object; - - .. c:member:: boost::random::mt19937* rng - - A direct pointer to the boost random number generator. You can use this - pointer in your C/C++ code if required. - - -.. cpp:function:: int PyBoostMt19937_Check(PyObject* o) - - Checks if the input object ``o`` is a ``PyBoostMt19937Object``. Returns - ``1`` if it is, and ``0`` otherwise. - - -.. cpp:function:: int PyBoostMt19937_Converter(PyObject* o, PyBoostMt19937Object** a) - - This function is meant to be used with :c:func:`PyArg_ParseTupleAndKeywords` - family of functions in the Python C-API. It checks the input - object to be of type ``PyBoostMt19937Object`` and sets a **new - reference** to it (in ``*a``) if it is the case. Returns ``0`` in case of - failure, ``1`` in case of success. - -.. cpp:function:: PyObject* PyBoostMt19937_SimpleNew() - - Creates a new instance of :cpp:type:`PyBoostMt19937Object`, with the default - seed. Returns a **new reference**. - -.. cpp:function:: PyObject* PyBoostMt19937_NewWithSeed(Py_ssize_t seed) - - Creates a new instance of :cpp:type:`PyBoostMt19937Object`, with a user - given seed. Returns a **new reference**. - -Distribution API +Bob File Support ---------------- -Together with the boost random number generator ``mt19937``, this package -provides bindings to these ``boost::random`` distributions: - - * Uniform - * Normal (or Gaussian) - * Log-normal - * Gamma - * Binomial - -Distributions wrap the random number generator, skewing the distribution of -numbers according to their parametrization. Distributions are *templated* -according to the scalar data types they produce. Different distributions -support a different set of scalar types: - - ============== ================================================= - Distribution Scalars supported - ============== ================================================= - Uniform bool, int8/16/32/64, uint8/16/32/64, float32/64 - Normal float32/64 - Log-normal float32/64 - Gamma float32/64 - Binomial float32/64 (internally using int64) - ============== ================================================= +.. cpp:type:: PyBobIoFileObject -.. cpp:type:: PyBoostUniformObject + The pythonic object representation for a ``bob::io::File`` object. - The pythonic object representation for a ``boost::random::uniform_*`` - object. - - .. code-block:: c + .. code-block:: cpp typedef struct { PyObject_HEAD - int type_num; - boost::shared_ptr<void> distro; - } PyUniformObject; - - .. c:member:: int type_num; - - The NumPy type number of scalars produced by this distribution. Accepted - values match the scalar type produced: - - ============= ======================================== - Scalar type NumPy scalar type number (enumeration) - ============= ======================================== - bool ``NPY_BOOL`` - int8 ``NPY_INT8`` - int16 ``NPY_INT16`` - int32 ``NPY_INT32`` - int64 ``NPY_INT64`` - int8 ``NPY_INT8`` - int16 ``NPY_INT16`` - int32 ``NPY_INT32`` - int64 ``NPY_INT64`` - float32 ``NPY_FLOAT32`` - float64 ``NPY_FLOAT64`` - ============= ======================================== - - .. c:member:: boost::shared_ptr<void> distro - - A direct pointer to the boost distribution. The underlying allocated type - changes with the scalar that is produced by the distribution: - - ============= ============================================== - Scalar type C++ data type - ============= ============================================== - bool ``boost::random::uniform_smallint<uint8_t>`` - int8 ``boost::random::uniform_int<int8_t>`` - int16 ``boost::random::uniform_int<int16_t>`` - int32 ``boost::random::uniform_int<int32_t>`` - int64 ``boost::random::uniform_int<int64_t>`` - uint8 ``boost::random::uniform_int<uint8_t>`` - uint16 ``boost::random::uniform_int<uint16_t>`` - uint32 ``boost::random::uniform_int<uint32_t>`` - uint64 ``boost::random::uniform_int<uint64_t>`` - float32 ``boost::random::uniform_real<float>`` - float64 ``boost::random::uniform_real<double>`` - ============= ============================================== - - In order to use the distribution in your C/C++ code, you must first cast the - shared pointer using ``boost::static_pointer_cast<D>``, with ``D`` matching - one of the distributions listed above, depending on the value of - -.. cpp:type:: PyBoostNormalObject - - The pythonic object representation for a - ``boost::random::normal_distribution`` object. - - .. code-block:: c - - typedef struct { - PyObject_HEAD - int type_num; - boost::shared_ptr<void> distro; - } PyUniformObject; - - .. c:member:: int type_num; - - The NumPy type number of scalars produced by this distribution. Accepted - values match the scalar type produced: - - ============= ======================================== - Scalar type NumPy scalar type number (enumeration) - ============= ======================================== - float32 ``NPY_FLOAT32`` - float64 ``NPY_FLOAT64`` - ============= ======================================== - - .. c:member:: boost::shared_ptr<void> distro - - A direct pointer to the boost distribution. The underlying allocated type - changes with the scalar that is produced by the distribution: - - ============= ================================================ - Scalar type C++ data type - ============= ================================================ - float32 ``boost::random::normal_distribution<float>`` - float64 ``boost::random::normal_distribution<double>`` - ============= ================================================ - -.. cpp:type:: PyBoostLogNormalObject - - The pythonic object representation for a - ``boost::random::lognormal_distribution`` object. - - .. code-block:: c - - typedef struct { - PyObject_HEAD - int type_num; - boost::shared_ptr<void> distro; - } PyUniformObject; - - .. c:member:: int type_num; - - The NumPy type number of scalars produced by this distribution. Accepted - values match the scalar type produced: - - ============= ======================================== - Scalar type NumPy scalar type number (enumeration) - ============= ======================================== - float32 ``NPY_FLOAT32`` - float64 ``NPY_FLOAT64`` - ============= ======================================== - - .. c:member:: boost::shared_ptr<void> distro - - A direct pointer to the boost distribution. The underlying allocated type - changes with the scalar that is produced by the distribution: - - ============= =================================================== - Scalar type C++ data type - ============= =================================================== - float32 ``boost::random::lognormal_distribution<float>`` - float64 ``boost::random::lognormal_distribution<double>`` - ============= =================================================== - -.. cpp:type:: PyBoostGammaObject - - The pythonic object representation for a - ``boost::random::gamma_distribution`` object. - - .. code-block:: c - - typedef struct { - PyObject_HEAD - int type_num; - boost::shared_ptr<void> distro; - } PyUniformObject; - - .. c:member:: int type_num; - - The NumPy type number of scalars produced by this distribution. Accepted - values match the scalar type produced: - - ============= ======================================== - Scalar type NumPy scalar type number (enumeration) - ============= ======================================== - float32 ``NPY_FLOAT32`` - float64 ``NPY_FLOAT64`` - ============= ======================================== - - .. c:member:: boost::shared_ptr<void> distro - - A direct pointer to the boost distribution. The underlying allocated type - changes with the scalar that is produced by the distribution: - - ============= =============================================== - Scalar type C++ data type - ============= =============================================== - float32 ``boost::random::gamma_distribution<float>`` - float64 ``boost::random::gamma_distribution<double>`` - ============= =============================================== - -.. cpp:type:: PyBoostBinomialObject - - The pythonic object representation for a - ``boost::random::binomial_distribution`` object. - - .. code-block:: c - - typedef struct { - PyObject_HEAD - int type_num; - boost::shared_ptr<void> distro; - } PyUniformObject; - - .. c:member:: int type_num; - - The NumPy type number of scalars produced by this distribution. Accepted - values match the scalar type produced: - - ============= ======================================== - Scalar type NumPy scalar type number (enumeration) - ============= ======================================== - float32 ``NPY_FLOAT32`` - float64 ``NPY_FLOAT64`` - ============= ======================================== - - .. c:member:: boost::shared_ptr<void> distro - - A direct pointer to the boost distribution. The underlying allocated type - changes with the scalar that is produced by the distribution: - - ============= ========================================================== - Scalar type C++ data type - ============= ========================================================== - float32 ``boost::random::binomial_distribution<int64_t,float>`` - float64 ``boost::random::binomial_distribution<int64_t,double>`` - ============= ========================================================== - -.. cpp:function:: int PyBoostUniform_Check(PyObject* o) - -.. cpp:function:: int PyBoostNormal_Check(PyObject* o) - -.. cpp:function:: int PyBoostLogNormal_Check(PyObject* o) - -.. cpp:function:: int PyBoostGamma_Check(PyObject* o) - -.. cpp:function:: int PyBoostBinomial_Check(PyObject* o) - - Checks if the input object ``o`` is a ``PyBoost<Distribution>Object``. - Returns ``1`` if it is, and ``0`` otherwise. - -.. cpp:function:: int PyBoostUniform_Converter(PyObject* o, PyBoostUniformObject** a) - -.. cpp:function:: int PyBoostNormal_Converter(PyObject* o, PyBoostNormalObject** a) - -.. cpp:function:: int PyBoostLogNormal_Converter(PyObject* o, PyBoostLogNormalObject** a) - -.. cpp:function:: int PyBoostGamma_Converter(PyObject* o, PyBoostGammaObject** a) - -.. cpp:function:: int PyBoostBinomial_Converter(PyObject* o, PyBoostBinomialObject** a) - - This function is meant to be used with :c:func:`PyArg_ParseTupleAndKeywords` - family of functions in the Python C-API. It checks the input object to be of - type ``PyBoost<Distribution>Object`` and returns a **new reference** to it - (in ``*a``) if it is the case. Returns ``0`` in case of failure, ``1`` in - case of success. - -.. cpp:function:: PyObject* PyBoostUniform_SimpleNew(int type_num, PyObject* min, PyObject* max) - - Creates a new instance of :cpp:type:`PyBoostUniformObject`, with the input - scalar establishing the minimum and the maximum of the distribution. Note - that ``bool`` distributions will raise an exception if one tries to set the - minimum and the maximum, since that is non-sensical. - - The parameter ``type_num`` may be set to one of the supported ``NPY_`` - enumeration values (e.g. ``NPY_UINT16``). - - .. warning:: - - For integral uniform distributions the range of numbers produced is - defined as :math:`[min, max]`. For real-valued distributions, the range of - numbers produced lies on the interval :math:`[min, max[`. - -.. cpp:function:: PyObject* PyBoostNormal_SimpleNew(int type_num, PyObject* mean, PyObject* sigma) - -.. cpp:function:: PyObject* PyBoostLogNormal_SimpleNew(int type_num, PyObject* mean, PyObject* sigma) - -.. cpp:function:: PyObject* PyBoostGamma_SimpleNew(int type_num, PyObject* alpha, PyObject* beta) - -.. cpp:function:: PyObject* PyBoostBinomial_SimpleNew(int type_num, PyObject* t, PyObject* p) - - Depending on the distribution, which may be one of ``Normal``, - ``LogNormal``, ``Gamma`` or ``Binomial``, each of the parameters assume a - different function: + boost::shared_ptr<bob::io::File> f; + } PyBobIoFileObject; - ============== ============= ============================ - Distribution Parameter 1 Parameter 2 - ============== ============= ============================ - Normal mean sigma (standard deviation) - LogNormal mean sigma (standard deviation) - Gamma alpha beta - Binomial t p - ============== ============= ============================ + .. cpp:member:: boost::shared_ptr<bob::io::File> f - The parameter ``type_num`` may be set to one of the supported ``NPY_`` - enumeration values (e.g. ``NPY_FLOAT64``). + A pointer to a file being read or written. .. include:: links.rst diff --git a/doc/py_api.rst b/doc/py_api.rst index bbddcc6483bef3371661f54363f3ee7ce64b7e11..d375d9e6f74d342547e3996be9493e86aed1a9be 100644 --- a/doc/py_api.rst +++ b/doc/py_api.rst @@ -2,152 +2,23 @@ .. Andre Anjos <andre.dos.anjos@gmail.com> .. Tue 15 Oct 17:41:52 2013 -.. testsetup:: coretest +.. testsetup:: iotest import numpy - import xbob.core + import xbob.io ============ User Guide ============ -Array Conversion ----------------- - -The function :py:func:`xbob.core.convert` allows you to convert objects of type -:py:class:`numpy.ndarray` between different types, with range compression or -decompression. For example, here we demonstrate a conversion using default -ranges. In this type of conversion, our implementation will assume that the -source array contains values within the range of ``uint8_t`` numbers and will -expand it to the range of ``uint16_t`` numbers, as desired by the programmer: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> x = numpy.array([0,255,0,255,0,255], 'uint8').reshape(2,3) - >>> x - array([[ 0, 255, 0], - [255, 0, 255]], dtype=uint8) - >>> xbob.core.convert(x, 'uint16') - array([[ 0, 65535, 0], - [65535, 0, 65535]], dtype=uint16) - - -The user can optionally specify source, destination ranges or both. For -example: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> x = numpy.array([0, 10, 20, 30, 40], 'uint8') - >>> xbob.core.convert(x, 'float64', source_range=(0,40), dest_range=(0.,1.)) - array([ 0. , 0.25, 0.5 , 0.75, 1. ]) - -Any range not specified is assumed to default on the type range. - - -Random Number Generation ------------------------- - -You can build a new random number generator (RNG) of type -:py:class:`xbob.core.random.mt19937` using one of two possible ways: - -1. Use the default constructor, which initializes with the default seed: - - .. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> xbob.core.random.mt19937() - xbob.core.random.mt19937() - -2. Pass a seed while initializing: - - .. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> rng = xbob.core.random.mt19937(34) - -RNGs can be compared for equality. The ``==`` operator checks if both -generators are on the exact same state and would generate the same sequence of -numbers when exposed to the same distributions. For example: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> rng1 = xbob.core.random.mt19937(111) - >>> rng2 = xbob.core.random.mt19937(111) - >>> rng1 == rng2 - True - >>> rng3 = xbob.core.random.mt19937(12) - >>> rng1 == rng3 - False - -The seed can be re-initialized at any point in time, which can be used to sync -two RNGs: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> rng3.seed(111) - >>> rng1 == rng3 - True - -Distributions skew numbers produced by the RNG so they look like the -parameterized distribution. By calling a distribution with an RNG, one -effectively generates random numbers: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> rng = xbob.core.random.mt19937() - >>> # creates an uniform distribution of integers inside [0, 10] - >>> u = xbob.core.random.uniform(int, 0, 10) - >>> u(rng) # doctest: +SKIP - 8 - -At our reference guide (see below), you will find more implemented -distributions you can use on your programs. To simplify the task of generating -random numbers, we provide a class that mimics the behavior of -``boost::random::variate_generator``, in Python: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> ugen = xbob.core.random.variate_generator(rng, u) - >>> ugen() # doctest: +SKIP - 6 - -You can also pass an optional shape when you call the variate generator, in -which case it generates a :py:class:`numpy.ndarray` of the specified size: - -.. doctest:: coretest - :options: +NORMALIZE_WHITESPACE - - >>> ugen((3,3)) # doctest: +SKIP - array([[ 3, 1, 6], - [ 3, 2, 6], - [10, 10, 10]]) +Basic I/O +--------- Reference --------- -This section includes information for using the pure Python API of -``xbob.core``. - -.. autofunction:: xbob.core.get_include - -.. autofunction:: xbob.core.convert - -.. autoclass:: xbob.core.random.mt19937 - -.. autoclass:: xbob.core.random.uniform - -.. autoclass:: xbob.core.random.normal - -.. autoclass:: xbob.core.random.lognormal - -.. autoclass:: xbob.core.random.gamma +This section includes information for using the pure Python API of ``xbob.io``. -.. autoclass:: xbob.core.random.binomial +.. autofunction:: xbob.io.get_include -.. autoclass:: xbob.core.random.variate_generator +.. autoclass:: xbob.io.file diff --git a/setup.py b/setup.py index 281463879025be22db3cdbda88e0f403b6d3b689..29c175f336cac1b2ef732e75f63cdfe97a53ef37 100644 --- a/setup.py +++ b/setup.py @@ -62,8 +62,9 @@ else: # Local include directory import os package_dir = os.path.dirname(os.path.realpath(__file__)) +package_base = os.path.join(package_dir, 'xbob', 'io') package_dir = os.path.join(package_dir, 'xbob', 'io', 'include') -include_dirs = [package_dir] +include_dirs = [package_base, package_dir] # Define package version version = '2.0.0a0' @@ -98,6 +99,7 @@ setup( ext_modules = [ Extension("xbob.io._library", [ + "xbob/io/bobskin.cpp", "xbob/io/file.cpp", "xbob/io/main.cpp", ], diff --git a/xbob/io/__init__.py b/xbob/io/__init__.py index b7e77707a3cadeb6e5e49db7bec3bb7516ca42e8..8d04c8504d57a16981af06a5f9d767c43080f5d3 100644 --- a/xbob/io/__init__.py +++ b/xbob/io/__init__.py @@ -1,4 +1,4 @@ -from ._library import __version__, __api_version__ +from ._library import __version__, __api_version__, file def get_include(): """Returns the directory containing the C/C++ API include directives""" diff --git a/xbob/io/bobskin.cpp b/xbob/io/bobskin.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bcd91acc2b091ccdbe01218b573a0914a6bcbe65 --- /dev/null +++ b/xbob/io/bobskin.cpp @@ -0,0 +1,50 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Wed 6 Nov 07:57:57 2013 + * + * @brief Implementation of our bobskin class + */ + +#include <numpy/arrayobject.h> +#include <stdexcept> + +bobskin::bobskin(PyObject* array, bob::core::array::ElementType& eltype) { + + if (!PyArray_CheckExact(array)) { + PyErr_SetString(PyExc_TypeError, "input object to bobskin constructor is not a numpy.ndarray"); + throw std::runtime_error(); + } + + m_type.set(eltype, PyArray_NDIM(array), PyArray_DIMS(array), + PyArray_STRIDES(array)); + + m_ptr = PyArray_DATA(array); + +} + +bobskin::~bobskin() { } + +void bobskin::set(const interface&) { + PyErr_SetString(PyExc_NotImplementedError, "setting C++ bobskin with (const interface&) is not implemented - DEBUG ME!"); + throw std::runtime_error(); +} + +void bobskin::set(boost::shared_ptr<interface> other); + PyErr_SetString(PyExc_NotImplementedError, "setting C++ bobskin with (boost::shared_ptr<interface>) is not implemented - DEBUG ME!"); + throw std::runtime_error(); +} + +void bobskin::set (const bob::core::array::typeinfo& req) { + PyErr_SetString(PyExc_NotImplementedError, "setting C++ bobskin with (const typeinfo&) implemented - DEBUG ME!"); + throw std::runtime_error(); +} + +boost::shared_ptr<void> bobskin::owner() { + PyErr_SetString(PyExc_NotImplementedError, "acquiring non-const owner from C++ bobskin is not implemented - DEBUG ME!"); + throw std::runtime_error(); +} + +boost::shared_ptr<const void> bobskin::owner() const { + PyErr_SetString(PyExc_NotImplementedError, "acquiring const owner from C++ bobskin is not implemented - DEBUG ME!"); + throw std::runtime_error(); +} diff --git a/xbob/io/bobskin.h b/xbob/io/bobskin.h new file mode 100644 index 0000000000000000000000000000000000000000..4b38b4e18c824df1d4d6f7158d0223f657049777 --- /dev/null +++ b/xbob/io/bobskin.h @@ -0,0 +1,72 @@ +/** + * @author Andre Anjos <andre.anjos@idiap.ch> + * @date Tue 5 Nov 22:09:07 2013 + * + * @brief A pythonic version of bob::core::array::interface, with minimal + * functionality. + */ + +#include <Python.h> +#include <bob/core/array.h> + +/** + * Wraps a PyArrayObject such that we can access it from bob::io + */ +class bobskin: public bob::core::array::interface { + + public: //api + + /** + * @brief Builds a new array an array like object + */ + bobskin(PyObject* array, bob::core::array::ElementType eltype); + + /** + * @brief By default, the interface is never freed. You must override + * this method to do something special for your class type. + */ + virtual ~bobskin(); + + /** + * @brief Copies the data from another interface. + */ + virtual void set(const interface& other); + + /** + * @brief Refers to the data of another interface. + */ + virtual void set(boost::shared_ptr<interface> other); + + /** + * @brief Re-allocates this interface taking into consideration new + * requirements. The internal memory should be considered uninitialized. + */ + virtual void set (const bob::core::array::typeinfo& req); + + /** + * @brief Type information for this interface. + */ + virtual const bob::core::array::typeinfo& type() const { return m_type; } + + /** + * @brief Borrows a reference from the underlying memory. This means + * this object continues to be responsible for deleting the memory and + * you should make sure that it outlives the usage of the returned + * pointer. + */ + virtual void* ptr() { return m_ptr; } + virtual const void* ptr() const { return m_ptr; } + + /** + * @brief Returns a representation of the internal cache using shared + * pointers. + */ + virtual boost::shared_ptr<void> owner(); + virtual boost::shared_ptr<const void> owner() const; + + private: //representation + + bob::core::array::typeinfo m_type; ///< type information + void* m_ptr; ///< pointer to the data + +}; diff --git a/xbob/io/file.cpp b/xbob/io/file.cpp index a57458633f0f782723949393a9fe6be8cd99ef48..db89459714e9da96298201b21ff6cd134cb08fce 100644 --- a/xbob/io/file.cpp +++ b/xbob/io/file.cpp @@ -9,8 +9,11 @@ #include <xbob.io/api.h> #include <bob/io/CodecRegistry.h> #include <bob/io/utils.h> +#include <numpy/arrayobject.h> #include <stdexcept> +#include <bobskin.h> + #define FILETYPE_NAME file PyDoc_STRVAR(s_file_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(FILETYPE_NAME)); @@ -99,9 +102,161 @@ pretend_extension\n\ ); static PyObject* PyBobIoFile_Repr(PyBobIoFileObject* self) { - return PyUnicode_FromFormat("%s()", s_file_str); + return +# if PY_VERSION_HEX >= 0x03000000 + PyUnicode_FromFormat +# else + PyString_FromFormat +# endif + ("%s(filename='%s', codec='%s')", s_file_str, self->f->filename().c_str(), + self->f->name().c_str()); +} + +static PyObject* PyBobIoFile_Filename(PyBobIoFileObject* self) { + return +# if PY_VERSION_HEX >= 0x03000000 + PyUnicode_FromString +# else + PyString_FromString +# endif + (self->f->filename().c_str()); +} + +static PyObject* PyBobIoFile_CodecName(PyBobIoFileObject* self) { + return +# if PY_VERSION_HEX >= 0x03000000 + PyUnicode_FromString +# else + PyString_FromString +# endif + (self->f->name().c_str()); +} + +PyDoc_STRVAR(s_filename_str, "filename"); +PyDoc_STRVAR(s_filename_doc, +"The path to the file being read/written" +); + +PyDoc_STRVAR(s_codec_name_str, "codec_name"); +PyDoc_STRVAR(s_codec_name_doc, +"Name of the File class implementation -- available for\n\ +compatibility reasons with the previous versions of this\n\ +library." +); + +static PyGetSetDef PyBobIoFile_getseters[] = { + { + s_filename_str, + (getter)PyBobIoFile_Filename, + 0, + s_filename_doc, + 0, + }, + { + s_codec_name_str, + (getter)PyBobIoFile_CodecName, + 0, + s_codec_name_doc, + 0, + }, + {0} /* Sentinel */ +}; + +static Py_ssize_t PyBobIoFile_Len (PyBobIoFileObject* self) { + Py_ssize_t retval = self->f->size(); + return retval; +} + +int PyBobIo_AsTypenum (bob::core::array::ElementType type) { + + switch(type) { + case bob::core::array::t_bool: + return NPY_BOOL; + case bob::core::array::t_int8: + return NPY_INT8; + case bob::core::array::t_int16: + return NPY_INT16; + case bob::core::array::t_int32: + return NPY_INT32; + case bob::core::array::t_int64: + return NPY_INT64; + case bob::core::array::t_uint8: + return NPY_UINT8; + case bob::core::array::t_uint16: + return NPY_UINT16; + case bob::core::array::t_uint32: + return NPY_UINT32; + case bob::core::array::t_uint64: + return NPY_UINT64; + case bob::core::array::t_float32: + return NPY_FLOAT32; + case bob::core::array::t_float64: + return NPY_FLOAT64; +#ifdef NPY_FLOAT128 + case bob::core::array::t_float128: + return NPY_FLOAT128; +#endif + case bob::core::array::t_complex64: + return NPY_COMPLEX64; + case bob::core::array::t_complex128: + return NPY_COMPLEX128; +#ifdef NPY_COMPLEX256 + case bob::core::array::t_complex256: + return NPY_COMPLEX256; +#endif + default: + PyErr_Format(PyExc_TypeError, "unsupported Bob/C++ element type (%s)", bob::core::array::stringize(type)); + } + + return NPY_NOTYPE; + } +static PyObject* PyBobIoFile_GetItem (PyBobIoFileObject* self, Py_ssize_t i) { + + if (i < 0 || i >= self->f->size()) { + PyErr_SetString(PyExc_IndexError, "file index out of range"); + return 0; + } + + const bob::core::array::typeinfo& info = self->f->type(); + + npy_intp shape[NPY_MAXDIMS]; + for (i=0; i<info.nd; ++i) shape[i] = info.shape[i]; + + int type_num = PyBobIo_AsTypenum(info.dtype); + if (type_num == NPY_NOTYPE) return 0; ///< failure + + PyObject* retval = PyArray_SimpleNew(info.nd, shape, type_num); + bobskin skin(retval, info.dtype); + + try { + self->f->read(skin, i); + } + catch (std::runtime_error& e) { + return 0; + } + catch (std::exception& e) { + PyErr_Format(PyExc_RuntimeError, "caught std::exception while reading file `%s': %s", self->f->filename().c_str(), e.what()); + return 0; + } + catch (...) { + PyErr_Format(PyExc_RuntimeError, "caught unknown while reading file `%s'", self->f->filename().c_str()); + return 0; + } + + return retval; + +} + +static PySequenceMethods PyBobIoFile_Sequence = { + (lenfunc)PyBobIoFile_Len, + 0, /* concat */ + 0, /* repeat */ + (ssizeargfunc)PyBobIoFile_GetItem, + 0 /* slice */ +}; + PyTypeObject PyBobIoFile_Type = { PyObject_HEAD_INIT(0) 0, /*ob_size*/ @@ -115,7 +270,7 @@ PyTypeObject PyBobIoFile_Type = { 0, /*tp_compare*/ (reprfunc)PyBobIoFile_Repr, /*tp_repr*/ 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ + &PyBobIoFile_Sequence, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ @@ -133,7 +288,7 @@ PyTypeObject PyBobIoFile_Type = { 0, /* tp_iternext */ 0, //PyBobIoFile_methods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + PyBobIoFile_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -157,16 +312,6 @@ static object file_read(bob::io::File& f, size_t index) { return a.pyobject(); //shallow copy } -static boost::shared_ptr<bob::io::File> string_open1 (const std::string& filename, - const std::string& mode) { - return bob::io::open(filename, mode[0]); -} - -static boost::shared_ptr<bob::io::File> string_open2 (const std::string& filename, - const std::string& mode, const std::string& pretend_extension) { - return bob::io::open(filename, mode[0], pretend_extension); -} - static void file_write(bob::io::File& f, object array) { bob::python::py_array a(array, object()); f.write(a); @@ -189,18 +334,19 @@ static dict extensions() { void bind_io_file() { - class_<bob::io::File, boost::shared_ptr<bob::io::File>, boost::noncopyable>("File", "Abstract base class for all Array/Arrayset i/o operations", no_init) - .def("__init__", make_constructor(string_open1, default_call_policies(), (arg("filename"), arg("mode"))), "Opens a (supported) file for reading arrays. The mode is a **single** character which takes one of the following values: 'r' - opens the file for read-only operations; 'w' - truncates the file and open it for reading and writing; 'a' - opens the file for reading and writing w/o truncating it.") - .def("__init__", make_constructor(string_open2, default_call_policies(), (arg("filename"), arg("mode"), arg("pretend_extension"))), "Opens a (supported) file for reading arrays but pretends its extension is as given by the last parameter - this way you can, potentially, override the default encoder/decoder used to read and write on the file. The mode is a **single** character which takes one of the following values: 'r' - opens the file for read-only operations; 'w' - truncates the file and open it for reading and writing; 'a' - opens the file for reading and writing w/o truncating it.") - .add_property("filename", make_function(&bob::io::File::filename, return_value_policy<copy_const_reference>()), "The path to the file being read/written") .add_property("type_all", make_function(&bob::io::File::type_all, return_value_policy<copy_const_reference>()), "Typing information to load all of the file at once") + .add_property("type", make_function(&bob::io::File::type, return_value_policy<copy_const_reference>()), "Typing information to load the file as an Arrayset") - .add_property("codec_name", make_function(&bob::io::File::name, return_value_policy<copy_const_reference>()), "Name of the File class implementation -- for compatibility reasons with the previous versions of this library") + .def("read", &file_read_all, (arg("self")), "Reads the whole contents of the file into a NumPy ndarray") .def("write", &file_write, (arg("self"), arg("array")), "Writes an array into the file, truncating it first") + .def("__len__", &bob::io::File::size, (arg("self")), "Size of the file if it is supposed to be read as a set of arrays instead of performing a single read") + .def("read", &file_read, (arg("self"), arg("index")), "Reads a single array from the file considering it to be an arrayset list") + .def("__getitem__", &file_read, (arg("self"), arg("index")), "Reads a single array from the file considering it to be an arrayset list") + .def("append", &file_append, (arg("self"), arg("array")), "Appends an array to a file. Compatibility requirements may be enforced.") ; diff --git a/xbob/io/include/xbob.io/api.h b/xbob/io/include/xbob.io/api.h index 3d72af969cc448de5591ea47f56d6ab98a2f82b3..b20aca3fbdb5764013254ca7d20f3699c9e171a8 100644 --- a/xbob/io/include/xbob.io/api.h +++ b/xbob/io/include/xbob.io/api.h @@ -44,8 +44,16 @@ typedef struct { #define PyBobIoFile_Type_NUM 1 #define PyBobIoFile_Type_TYPE PyTypeObject +/************************ + * I/O generic bindings * + ************************/ + +#define PyBobIo_AsTypenum_NUM 2 +#define PyBobIo_AsTypenum_RET int +#define PyBobIo_AsTypenum_PROTO (bob::core::array::ElementType et) + /* Total number of C API pointers */ -#define PyXbobIo_API_pointers 2 +#define PyXbobIo_API_pointers 3 #ifdef XBOB_IO_MODULE @@ -63,6 +71,12 @@ typedef struct { extern PyBobIoFile_Type_TYPE PyBobIoFile_Type; + /************************ + * I/O generic bindings * + ************************/ + + PyBobIo_AsTypenum_RET PyBobIo_AsTypenum PyBobIo_AsTypenum_PROTO; + #else /* This section is used in modules that use `blitz.array's' C-API */ @@ -103,6 +117,12 @@ typedef struct { # define PyBobIoFile_Type (*(PyBobIoFile_Type_TYPE *)PyXbobIo_API[PyBobIoFile_Type_NUM]) + /************************ + * I/O generic bindings * + ************************/ + +# define PyBobIo_AsTypenum (*(PyBobIo_AsTypenum_RET (*)PyBobIo_AsTypenum_PROTO) PyBlitzArray_API[PyBobIo_AsTypenum_NUM]) + /** * Returns -1 on error, 0 on success. PyCapsule_Import will set an exception * if there's an error. diff --git a/xbob/io/main.cpp b/xbob/io/main.cpp index 3822dd9109cd859137631aa10af7dfff2c5b22a0..e420b670092eab3fa00b391d8f27b76ead297ef5 100644 --- a/xbob/io/main.cpp +++ b/xbob/io/main.cpp @@ -43,6 +43,11 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { static void* PyXbobIo_API[PyXbobIo_API_pointers]; /* exhaustive list of C APIs */ + + /************** + * Versioning * + **************/ + PyXbobIo_API[PyXbobIo_APIVersion_NUM] = (void *)&PyXbobIo_APIVersion; /***************************** @@ -51,6 +56,12 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) { PyXbobIo_API[PyBobIoFile_Type_NUM] = (void *)&PyBobIoFile_Type; + /************************ + * I/O generic bindings * + ************************/ + + PyBlitzArray_API[PyBobIo_AsTypenum_NUM] = (void *)PyBobIo_AsTypenum; + /* imports the NumPy C-API */ import_array();