I would like expose C++ 20 based generator as an iterator to Python via pybind11. The goal is to read custom video format frame by frame, perform tasks like undistortion and expose the frame as numpy array to the Python for further prototyping. I get stuck at what should be easy task i.e. exposure of the frame prepared in c++ to python.
Now to the issue I have: When I try to call iter() the program falls down without any error code.
python
reader = module.FrameReader("video.dat", [1824, 942])
it = iter(reader)
module.h
template<std::movable T>
class Generator
{
public:
struct promise_type
{
Generator get_return_object()
{
return Generator {Handle::from_promise(*this)};
}
static std::suspend_always initial_suspend() noexcept
{
return {};
}
static std::suspend_always final_suspend() noexcept
{
return {};
}
std::suspend_always yield_value(T value) noexcept
{
m_value = std::move(value);
return {};
}
void await_transform() = delete;
[[noreturn]]
static void unhandled_exception()
{
throw;
}
static void return_void()
{}
std::optional<T> m_value;
};
using Handle = std::coroutine_handle<promise_type>;
explicit Generator(const Handle coroutine)
: m_coroutine{coroutine}
{}
~Generator()
{
if (m_coroutine)
m_coroutine.destroy();
}
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) noexcept
: m_coroutine{other.m_coroutine}
{
other.m_coroutine = {};
}
Generator& operator=(Generator&& other) noexcept
{
if (this != &other)
{
if (m_coroutine)
m_coroutine.destroy();
m_coroutine = other.m_coroutine;
other.m_coroutine = {};
}
return *this;
}
// Range-based for loop support.
class Iterator
{
public:
explicit Iterator(const Handle coroutine)
: m_coroutine{coroutine}
{}
Iterator& operator++()
{
m_coroutine.resume();
return *this;
}
Iterator operator++(int)
{
Iterator current {*this};
++(*this);
return current;
}
const T& operator*() const
{
return *m_coroutine.promise().m_value;
}
bool operator==(std::default_sentinel_t) const {
return !m_coroutine || m_coroutine.done();
}
private:
Handle m_coroutine;
};
Iterator begin()
{
if (m_coroutine)
m_coroutine.resume();
return Iterator{m_coroutine};
}
std::default_sentinel_t end()
{
return {};
}
private:
Handle m_coroutine;
};
struct stFrame
{
unsigned int frameIndex;
std::vector<unsigned char> data;
};
module.cpp
Generator<stFrame> frame(std::string filename, std::array<size_t, 2> resolution)
{
// some code
for (unsigned int i {0}; i < numImages; ++i)
{
co_yield stFrame {frameIndex, std::move(imageBuffer)};
}
}
PYBIND11_MAKE_OPAQUE(std::vector<unsigned char>);
PYBIND11_MODULE (test, m)
{
m.doc () = "Camera utils";
pybind11::class_<Generator<stFrame>>(m, "FrameReader")
.def(pybind11::init([](std::string filename, std::array<size_t, 2> resolution)
{
return frame(filename, resolution);
}))
.def("__iter__", [](Generator<stFrame>& f) -> pybind11::iterator
{
return pybind11::make_iterator(f.begin(), f.end());
}, pybind11::keep_alive<0, 1>());
pybind11::class_<stFrame>(m, "Frame")
.def(pybind11::init())
.def_readonly("frameIndex", &stFrame::frameIndex)
.def_readonly("data", &stFrame::data);
}