From ea24782fc205a6417b9b89ff91013f4e1da94e0b Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Mon, 11 Nov 2013 18:48:49 +0100
Subject: [PATCH] Implement VideoReader iterator

---
 xbob/io/file.cpp              |  76 ++++++++++++++++++++++-
 xbob/io/include/xbob.io/api.h |  39 ++++++++++--
 xbob/io/main.cpp              |  16 +++++
 xbob/io/test/test_file.py     |  11 ++++
 xbob/io/test/test_video.py    |  11 ++++
 xbob/io/videoreader.cpp       | 113 +++++++++++++++++++++++++++++++---
 6 files changed, 250 insertions(+), 16 deletions(-)

diff --git a/xbob/io/file.cpp b/xbob/io/file.cpp
index fc38194..9d4ec03 100644
--- a/xbob/io/file.cpp
+++ b/xbob/io/file.cpp
@@ -14,7 +14,7 @@
 #include <stdexcept>
 #include <bobskin.h>
 
-#define FILETYPE_NAME file
+#define FILETYPE_NAME File
 PyDoc_STRVAR(s_file_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(FILETYPE_NAME));
 
 /* How to create a new PyBobIoFileObject */
@@ -299,7 +299,7 @@ static PyObject* PyBobIoFile_GetItem (PyBobIoFileObject* self, PyObject* item) {
      return PyBobIoFile_GetSlice(self, start, stop, step, slicelength);
    }
    else {
-     PyErr_Format(PyExc_TypeError, "File indices must be integers, not %.200s",
+     PyErr_Format(PyExc_TypeError, "File indices must be integers, not %s",
          item->ob_type->tp_name);
      return 0;
    }
@@ -592,6 +592,76 @@ static PyMethodDef PyBobIoFile_Methods[] = {
     {0}  /* Sentinel */
 };
 
+/**********************************
+ * Definition of Iterator to File *
+ **********************************/
+
+#define FILEITERTYPE_NAME File.iter
+PyDoc_STRVAR(s_fileiterator_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(FILEITERTYPE_NAME));
+
+/* How to create a new PyBobIoFileIteratorObject */
+static PyObject* PyBobIoFileIterator_New(PyTypeObject* type, PyObject*, PyObject*) {
+
+  /* Allocates the python object itself */
+  PyBobIoFileIteratorObject* self = (PyBobIoFileIteratorObject*)type->tp_alloc(type, 0);
+
+  return reinterpret_cast<PyObject*>(self);
+}
+
+static PyObject* PyBobIoFileIterator_Iter (PyBobIoFileIteratorObject* self) {
+  Py_INCREF(self);
+  return reinterpret_cast<PyObject*>(self);
+}
+
+static PyObject* PyBobIoFileIterator_Next (PyBobIoFileIteratorObject* self) {
+  if (self->curpos >= self->pyfile->f->size()) {
+    Py_XDECREF((PyObject*)self->pyfile);
+    self->pyfile = 0;
+    return 0;
+  }
+  return PyBobIoFile_GetIndex(self->pyfile, self->curpos++);
+}
+
+PyTypeObject PyBobIoFileIterator_Type = {
+    PyObject_HEAD_INIT(0)
+    0,                                          /* ob_size */
+    s_fileiterator_str,                         /* tp_name */
+    sizeof(PyBobIoFileIteratorObject),          /* tp_basicsize */
+    0,                                          /* tp_itemsize */
+    0,                                          /* tp_dealloc */
+    0,                                          /* tp_print */
+    0,                                          /* tp_getattr */
+    0,                                          /* tp_setattr */
+    0,                                          /* tp_compare */
+    0,                                          /* tp_repr */
+    0,                                          /* tp_as_number */
+    0,                                          /* 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 | Py_TPFLAGS_HAVE_ITER,  /* tp_flags */
+    0,                                          /* tp_doc */
+    0,		                                      /* tp_traverse */
+    0,		                                      /* tp_clear */
+    0,                                          /* tp_richcompare */
+    0,		                                      /* tp_weaklistoffset */
+    (getiterfunc)PyBobIoFileIterator_Iter,      /* tp_iter */
+    (iternextfunc)PyBobIoFileIterator_Next      /* tp_iternext */
+};
+
+static PyObject* PyBobIoFile_Iter (PyBobIoFileObject* self) {
+  PyBobIoFileIteratorObject* retval = (PyBobIoFileIteratorObject*)PyBobIoFileIterator_New(&PyBobIoFileIterator_Type, 0, 0);
+  if (!retval) return 0;
+  Py_INCREF(self);
+  retval->pyfile = self;
+  retval->curpos = 0;
+  return reinterpret_cast<PyObject*>(retval);
+}
+
 PyTypeObject PyBobIoFile_Type = {
     PyObject_HEAD_INIT(0)
     0,                                          /*ob_size*/
@@ -619,7 +689,7 @@ PyTypeObject PyBobIoFile_Type = {
     0,		                                      /* tp_clear */
     0,                                          /* tp_richcompare */
     0,		                                      /* tp_weaklistoffset */
-    0,		                                      /* tp_iter */
+    (getiterfunc)PyBobIoFile_Iter,              /* tp_iter */
     0,		                                      /* tp_iternext */
     PyBobIoFile_Methods,                        /* tp_methods */
     0,                                          /* tp_members */
diff --git a/xbob/io/include/xbob.io/api.h b/xbob/io/include/xbob.io/api.h
index f29a090..6df961f 100644
--- a/xbob/io/include/xbob.io/api.h
+++ b/xbob/io/include/xbob.io/api.h
@@ -50,15 +50,27 @@ typedef struct {
 #define PyBobIoFile_Type_NUM 1
 #define PyBobIoFile_Type_TYPE PyTypeObject
 
+typedef struct {
+  PyObject_HEAD
+  
+  /* Type-specific fields go here. */
+  PyBobIoFileObject* pyfile;
+  Py_ssize_t curpos;
+
+} PyBobIoFileIteratorObject;
+
+#define PyBobIoFileIterator_Type_NUM 2
+#define PyBobIoFileIterator_Type_TYPE PyTypeObject
+
 /************************
  * I/O generic bindings *
  ************************/
 
-#define PyBobIo_AsTypenum_NUM 2
+#define PyBobIo_AsTypenum_NUM 3
 #define PyBobIo_AsTypenum_RET int
 #define PyBobIo_AsTypenum_PROTO (bob::core::array::ElementType et)
 
-#define PyBobIo_TypeInfoAsTuple_NUM 3
+#define PyBobIo_TypeInfoAsTuple_NUM 4
 #define PyBobIo_TypeInfoAsTuple_RET PyObject*
 #define PyBobIo_TypeInfoAsTuple_PROTO (const bob::core::array::typeinfo& ti)
 
@@ -76,16 +88,28 @@ typedef struct {
 
 } PyBobIoVideoReaderObject;
 
-#define PyBobIoVideoReader_Type_NUM 4
+#define PyBobIoVideoReader_Type_NUM 5
 #define PyBobIoVideoReader_Type_TYPE PyTypeObject
 
+typedef struct {
+  PyObject_HEAD
+
+  /* Type-specific fields go here. */
+  PyBobIoVideoReaderObject* pyreader;
+  boost::shared_ptr<bob::io::VideoReader::const_iterator> iter;
+
+} PyBobIoVideoReaderIteratorObject;
+
+#define PyBobIoVideoReaderIterator_Type_NUM 5
+#define PyBobIoVideoReaderIterator_Type_TYPE PyTypeObject
+
 #endif /* WITH_FFMPEG */
 
 /* Total number of C API pointers */
 #if WITH_FFMPEG
-#  define PyXbobIo_API_pointers 5
-#else
 #  define PyXbobIo_API_pointers 6
+#else
+#  define PyXbobIo_API_pointers 7
 #endif /* WITH_FFMPEG */
 
 #ifdef XBOB_IO_MODULE
@@ -103,6 +127,7 @@ typedef struct {
    *****************************/
 
   extern PyBobIoFile_Type_TYPE PyBobIoFile_Type;
+  extern PyBobIoFileIterator_Type_TYPE PyBobIoFileIterator_Type;
 
   /************************
    * I/O generic bindings *
@@ -118,6 +143,7 @@ typedef struct {
    ******************/
 
   extern PyBobIoVideoReader_Type_TYPE PyBobIoVideoReader_Type;
+  extern PyBobIoVideoReaderIterator_Type_TYPE PyBobIoVideoReaderIterator_Type;
 #endif /* WITH_FFMPEG */
 
 #else
@@ -159,6 +185,7 @@ typedef struct {
    *****************************/
 
 # define PyBobIoFile_Type (*(PyBobIoFile_Type_TYPE *)PyXbobIo_API[PyBobIoFile_Type_NUM])
+# define PyBobIoFileIterator_Type (*(PyBobIoFileIterator_Type_TYPE *)PyXbobIo_API[PyBobIoFileIterator_Type_NUM])
 
   /************************
    * I/O generic bindings *
@@ -174,6 +201,8 @@ typedef struct {
    ******************/
 
 # define PyBobIoVideoReader_Type (*(PyBobIoVideoReader_Type_TYPE *)PyXbobIo_API[PyBobIoVideoReader_Type_NUM])
+
+# define PyBobIoVideoReaderIterator_Type (*(PyBobIoVideoReaderIterator_Type_TYPE *)PyXbobIo_API[PyBobIoVideoReaderIterator_Type_NUM])
 #endif /* WITH_FFMPEG */
 
   /**
diff --git a/xbob/io/main.cpp b/xbob/io/main.cpp
index c24476a..33cbc81 100644
--- a/xbob/io/main.cpp
+++ b/xbob/io/main.cpp
@@ -29,9 +29,15 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) {
   PyBobIoFile_Type.tp_new = PyType_GenericNew;
   if (PyType_Ready(&PyBobIoFile_Type) < 0) return;
 
+  PyBobIoFileIterator_Type.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&PyBobIoFileIterator_Type) < 0) return;
+
 #if WITH_FFMPEG
   PyBobIoVideoReader_Type.tp_new = PyType_GenericNew;
   if (PyType_Ready(&PyBobIoVideoReader_Type) < 0) return;
+
+  PyBobIoVideoReaderIterator_Type.tp_new = PyType_GenericNew;
+  if (PyType_Ready(&PyBobIoVideoReaderIterator_Type) < 0) return;
 #endif /* WITH_FFMPEG */
 
   PyObject* m = Py_InitModule3(BOOST_PP_STRINGIZE(XBOB_IO_MODULE_NAME),
@@ -45,9 +51,15 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) {
   Py_INCREF(&PyBobIoFile_Type);
   PyModule_AddObject(m, "File", (PyObject *)&PyBobIoFile_Type);
 
+  Py_INCREF(&PyBobIoFileIterator_Type);
+  PyModule_AddObject(m, "File.iter", (PyObject *)&PyBobIoFileIterator_Type);
+
 #if WITH_FFMPEG
   Py_INCREF(&PyBobIoVideoReader_Type);
   PyModule_AddObject(m, "VideoReader", (PyObject *)&PyBobIoVideoReader_Type);
+
+  Py_INCREF(&PyBobIoVideoReaderIterator_Type);
+  PyModule_AddObject(m, "VideoReader.iter", (PyObject *)&PyBobIoVideoReaderIterator_Type);
 #endif /* WITH_FFMPEG */
 
   static void* PyXbobIo_API[PyXbobIo_API_pointers];
@@ -66,6 +78,8 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) {
 
   PyXbobIo_API[PyBobIoFile_Type_NUM] = (void *)&PyBobIoFile_Type;
 
+  PyXbobIo_API[PyBobIoFileIterator_Type_NUM] = (void *)&PyBobIoFileIterator_Type;
+
   /************************
    * I/O generic bindings *
    ************************/
@@ -80,6 +94,8 @@ PyMODINIT_FUNC ENTRY_FUNCTION(XBOB_IO_MODULE_NAME) (void) {
    ******************/
 
   PyXbobIo_API[PyBobIoVideoReader_Type_NUM] = (void *)&PyBobIoVideoReader_Type;
+
+  PyXbobIo_API[PyBobIoVideoReaderIterator_Type_NUM] = (void *)&PyBobIoVideoReaderIterator_Type;
 #endif /* WITH_FFMPEG */
 
   /* imports the NumPy C-API */
diff --git a/xbob/io/test/test_file.py b/xbob/io/test/test_file.py
index 7797192..018572a 100644
--- a/xbob/io/test/test_file.py
+++ b/xbob/io/test/test_file.py
@@ -23,6 +23,17 @@ def test_peek():
   assert peek(f) == (numpy.uint16, (3,), (1,))
   assert peek_all(f) == (numpy.uint16, (3,3), (3,1))
 
+def test_iteration():
+ 
+  fname = testutils.datafile('matlab_2d.hdf5', __name__)
+  f = File(fname, 'r')
+  nose.tools.eq_(len(f), 512)
+
+  objs = load(fname)
+
+  for l, i in zip(objs, f):
+    assert numpy.allclose(l, i)
+
 def test_indexing():
  
   fname = testutils.datafile('matlab_2d.hdf5', __name__)
diff --git a/xbob/io/test/test_video.py b/xbob/io/test/test_video.py
index f13a3cc..b53afc3 100644
--- a/xbob/io/test/test_video.py
+++ b/xbob/io/test/test_video.py
@@ -100,6 +100,17 @@ def test_video_reader_str():
   assert repr(iv)
   assert str(iv)
 
+@testutils.ffmpeg_found()
+def test_iteration():
+ 
+  from .. import load, VideoReader
+  f = VideoReader(INPUT_VIDEO)
+  objs = load(INPUT_VIDEO)
+
+  nose.tools.eq_(len(f), len(objs))
+  for l, i in zip(objs, f):
+    assert numpy.allclose(l, i)
+
 @testutils.ffmpeg_found()
 def test_indexing():
 
diff --git a/xbob/io/videoreader.cpp b/xbob/io/videoreader.cpp
index 8856aad..c20f5a7 100644
--- a/xbob/io/videoreader.cpp
+++ b/xbob/io/videoreader.cpp
@@ -535,6 +535,110 @@ static PyMappingMethods PyBobIoVideoReader_Mapping = {
     0 /* (objobjargproc)PyBobIoVideoReader_SetItem //mp_ass_subscript */
 };
 
+/*****************************************
+ * Definition of Iterator to VideoReader *
+ *****************************************/
+
+#define VIDEOITERTYPE_NAME VideoReader.iter
+PyDoc_STRVAR(s_videoreaderiterator_str, BOOST_PP_STRINGIZE(XBOB_IO_MODULE_PREFIX) "." BOOST_PP_STRINGIZE(VIDEOITERTYPE_NAME));
+
+static PyObject* PyBobIoVideoReaderIterator_New(PyTypeObject* type, PyObject*, PyObject*) {
+
+  /* Allocates the python object itself */
+  PyBobIoVideoReaderIteratorObject* self = (PyBobIoVideoReaderIteratorObject*)type->tp_alloc(type, 0);
+
+  self->iter.reset();
+
+  return reinterpret_cast<PyObject*>(self);
+}
+
+static PyObject* PyBobIoVideoReaderIterator_Iter (PyBobIoVideoReaderIteratorObject* self) {
+  Py_INCREF(self);
+  return reinterpret_cast<PyObject*>(self);
+}
+
+static PyObject* PyBobIoVideoReaderIterator_Next (PyBobIoVideoReaderIteratorObject* self) {
+
+  if (*self->iter == self->pyreader->v->end()) {
+    self->iter->reset();
+    self->iter.reset();
+    Py_XDECREF((PyObject*)self->pyreader);
+    return 0;
+  }
+
+  const bob::core::array::typeinfo& info = self->pyreader->v->frame_type();
+
+  npy_intp shape[NPY_MAXDIMS];
+  for (int k=0; k<info.nd; ++k) shape[k] = info.shape[k];
+
+  int type_num = PyBobIo_AsTypenum(info.dtype);
+  if (type_num == NPY_NOTYPE) return 0; ///< failure
+
+  PyObject* retval = PyArray_SimpleNew(info.nd, shape, type_num);
+  if (!retval) return 0;
+
+  try {
+    bobskin skin(retval, info.dtype);
+    self->iter->read(skin);
+  }
+  catch (std::exception& e) {
+    if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught std::exception while reading frame %" PY_FORMAT_SIZE_T "d from file `%s': %s", self->iter->cur(), self->pyreader->v->filename().c_str(), e.what());
+    Py_DECREF(retval);
+    return 0;
+  }
+  catch (...) {
+    if (!PyErr_Occurred()) PyErr_Format(PyExc_RuntimeError, "caught unknown exception while reading frame #%" PY_FORMAT_SIZE_T "d from file `%s'", self->iter->cur(), self->pyreader->v->filename().c_str());
+    Py_DECREF(retval);
+    return 0;
+  }
+
+  return retval;
+
+}
+
+PyTypeObject PyBobIoVideoReaderIterator_Type = {
+    PyObject_HEAD_INIT(0)
+    0,                                          /* ob_size */
+    s_videoreaderiterator_str,                  /* tp_name */
+    sizeof(PyBobIoVideoReaderIteratorObject),   /* tp_basicsize */
+    0,                                          /* tp_itemsize */
+    0,                                          /* tp_dealloc */
+    0,                                          /* tp_print */
+    0,                                          /* tp_getattr */
+    0,                                          /* tp_setattr */
+    0,                                          /* tp_compare */
+    0,                                          /* tp_repr */
+    0,                                          /* tp_as_number */
+    0,                                          /* 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 | Py_TPFLAGS_HAVE_ITER,  /* tp_flags */
+    0,                                          /* tp_doc */
+    0,		                                      /* tp_traverse */
+    0,		                                      /* tp_clear */
+    0,                                          /* tp_richcompare */
+    0,		                                      /* tp_weaklistoffset */
+    (getiterfunc)PyBobIoVideoReaderIterator_Iter,      /* tp_iter */
+    (iternextfunc)PyBobIoVideoReaderIterator_Next      /* tp_iternext */
+};
+
+static PyObject* PyBobIoVideoReader_Iter (PyBobIoVideoReaderObject* self) {
+
+  /* Allocates the python object itself */
+  PyBobIoVideoReaderIteratorObject* retval = (PyBobIoVideoReaderIteratorObject*)PyBobIoVideoReaderIterator_New(&PyBobIoVideoReaderIterator_Type, 0, 0);
+  if (!retval) return 0;
+
+  Py_INCREF(self);
+  retval->pyreader = self;
+  retval->iter.reset(new bob::io::VideoReader::const_iterator(self->v->begin()));
+  return reinterpret_cast<PyObject*>(retval);
+}
+
 PyTypeObject PyBobIoVideoReader_Type = {
     PyObject_HEAD_INIT(0)
     0,                                          /*ob_size*/
@@ -562,7 +666,7 @@ PyTypeObject PyBobIoVideoReader_Type = {
     0,		                                      /* tp_clear */
     0,                                          /* tp_richcompare */
     0,		                                      /* tp_weaklistoffset */
-    0,		                                      /* tp_iter */
+    (getiterfunc)PyBobIoVideoReader_Iter,       /* tp_iter */
     0,		                                      /* tp_iternext */
     PyBobIoVideoReader_Methods,                 /* tp_methods */
     0,                                          /* tp_members */
@@ -577,11 +681,4 @@ PyTypeObject PyBobIoVideoReader_Type = {
     PyBobIoVideoReader_New,                     /* tp_new */
 };
 
-/**
-    .def("__iter__", &bob::io::VideoReader::begin, with_custodian_and_ward_postcall<0,1>())
-
-    .def("__getitem__", &videoreader_getslice)
-
-**/
-
 #endif /* WITH_FFMPEG */
-- 
GitLab