Add Python bindings

Add libcamera Python bindings. pybind11 is used to generate the C++ <->
Python layer.

We use pybind11 'smart_holder' version to avoid issues with private
destructors and shared_ptr. There is also an alternative solution here:

https://github.com/pybind/pybind11/pull/2067

Only a subset of libcamera classes are exposed. Implementing and testing
the wrapper classes is challenging, and as such only classes that I have
needed have been added so far.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Tomi Valkeinen 2022-05-09 13:10:20 +03:00 committed by Kieran Bingham
parent 5cb17e0c13
commit 8aa02271fd
11 changed files with 845 additions and 1 deletions

View file

@ -177,6 +177,7 @@ summary({
'Tracing support': tracing_enabled, 'Tracing support': tracing_enabled,
'Android support': android_enabled, 'Android support': android_enabled,
'GStreamer support': gst_enabled, 'GStreamer support': gst_enabled,
'Python bindings': pycamera_enabled,
'V4L2 emulation support': v4l2_enabled, 'V4L2 emulation support': v4l2_enabled,
'cam application': cam_enabled, 'cam application': cam_enabled,
'qcam application': qcam_enabled, 'qcam application': qcam_enabled,

View file

@ -58,3 +58,8 @@ option('v4l2',
type : 'boolean', type : 'boolean',
value : false, value : false,
description : 'Compile the V4L2 compatibility layer') description : 'Compile the V4L2 compatibility layer')
option('pycamera',
type : 'feature',
value : 'auto',
description : 'Enable libcamera Python bindings (experimental)')

View file

@ -37,4 +37,5 @@ subdir('cam')
subdir('qcam') subdir('qcam')
subdir('gstreamer') subdir('gstreamer')
subdir('py')
subdir('v4l2') subdir('v4l2')

View file

@ -0,0 +1,84 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from ._libcamera import *
class MappedFrameBuffer:
def __init__(self, fb):
self.__fb = fb
def __enter__(self):
import os
import mmap
fb = self.__fb
# Collect information about the buffers
bufinfos = {}
for i in range(fb.num_planes):
fd = fb.fd(i)
if fd not in bufinfos:
buflen = os.lseek(fd, 0, os.SEEK_END)
bufinfos[fd] = {'maplen': 0, 'buflen': buflen}
else:
buflen = bufinfos[fd]['buflen']
if fb.offset(i) > buflen or fb.offset(i) + fb.length(i) > buflen:
raise RuntimeError(f'plane is out of buffer: buffer length={buflen}, ' +
f'plane offset={fb.offset(i)}, plane length={fb.length(i)}')
bufinfos[fd]['maplen'] = max(bufinfos[fd]['maplen'], fb.offset(i) + fb.length(i))
# mmap the buffers
maps = []
for fd, info in bufinfos.items():
map = mmap.mmap(fd, info['maplen'], mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
info['map'] = map
maps.append(map)
self.__maps = tuple(maps)
# Create memoryviews for the planes
planes = []
for i in range(fb.num_planes):
fd = fb.fd(i)
info = bufinfos[fd]
mv = memoryview(info['map'])
start = fb.offset(i)
end = fb.offset(i) + fb.length(i)
mv = mv[start:end]
planes.append(mv)
self.__planes = tuple(planes)
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
for p in self.__planes:
p.release()
for mm in self.__maps:
mm.close()
@property
def planes(self):
return self.__planes
def __FrameBuffer__mmap(self):
return MappedFrameBuffer(self)
FrameBuffer.mmap = __FrameBuffer__mmap

View file

@ -0,0 +1,52 @@
# SPDX-License-Identifier: CC0-1.0
py3_dep = dependency('python3', required : get_option('pycamera'))
if not py3_dep.found()
pycamera_enabled = false
subdir_done()
endif
pycamera_enabled = true
pybind11_proj = subproject('pybind11')
pybind11_dep = pybind11_proj.get_variable('pybind11_dep')
pycamera_sources = files([
'pyenums.cpp',
'pymain.cpp',
])
pycamera_deps = [
libcamera_public,
py3_dep,
pybind11_dep,
]
pycamera_args = [
'-fvisibility=hidden',
'-Wno-shadow',
'-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT',
'-DLIBCAMERA_BASE_PRIVATE',
]
destdir = get_option('libdir') / ('python' + py3_dep.version()) / 'site-packages' / 'libcamera'
pycamera = shared_module('_libcamera',
pycamera_sources,
install : true,
install_dir : destdir,
name_prefix : '',
dependencies : pycamera_deps,
cpp_args : pycamera_args)
run_command('ln', '-fsT', '../../../../src/py/libcamera/__init__.py',
meson.current_build_dir() / '__init__.py',
check: true)
install_data(['__init__.py'], install_dir : destdir)
# \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files
# Note: Depends on pybind11-stubgen. To generate pylibcamera stubs:
# $ PYTHONPATH=build/src/py pybind11-stubgen --no-setup-py -o build/src/py libcamera
# $ mv build/src/py/libcamera-stubs/* build/src/py/libcamera/

View file

@ -0,0 +1,34 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*
* Python bindings - Enumerations
*/
#include <libcamera/libcamera.h>
#include <pybind11/smart_holder.h>
namespace py = pybind11;
using namespace libcamera;
void init_pyenums(py::module &m)
{
py::enum_<StreamRole>(m, "StreamRole")
.value("StillCapture", StreamRole::StillCapture)
.value("Raw", StreamRole::Raw)
.value("VideoRecording", StreamRole::VideoRecording)
.value("Viewfinder", StreamRole::Viewfinder);
py::enum_<ControlType>(m, "ControlType")
.value("None", ControlType::ControlTypeNone)
.value("Bool", ControlType::ControlTypeBool)
.value("Byte", ControlType::ControlTypeByte)
.value("Integer32", ControlType::ControlTypeInteger32)
.value("Integer64", ControlType::ControlTypeInteger64)
.value("Float", ControlType::ControlTypeFloat)
.value("String", ControlType::ControlTypeString)
.value("Rectangle", ControlType::ControlTypeRectangle)
.value("Size", ControlType::ControlTypeSize);
}

649
src/py/libcamera/pymain.cpp Normal file
View file

@ -0,0 +1,649 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*
* Python bindings
*/
/*
* \todo Add geometry classes (Point, Rectangle...)
* \todo Add bindings for the ControlInfo class
* \todo Add bindings for the PixelFormat class
*/
#include <mutex>
#include <stdexcept>
#include <sys/eventfd.h>
#include <unistd.h>
#include <libcamera/base/log.h>
#include <libcamera/libcamera.h>
#include <pybind11/functional.h>
#include <pybind11/smart_holder.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
namespace py = pybind11;
using namespace libcamera;
template<typename T>
static py::object valueOrTuple(const ControlValue &cv)
{
if (cv.isArray()) {
const T *v = reinterpret_cast<const T *>(cv.data().data());
auto t = py::tuple(cv.numElements());
for (size_t i = 0; i < cv.numElements(); ++i)
t[i] = v[i];
return std::move(t);
}
return py::cast(cv.get<T>());
}
static py::object controlValueToPy(const ControlValue &cv)
{
switch (cv.type()) {
case ControlTypeBool:
return valueOrTuple<bool>(cv);
case ControlTypeByte:
return valueOrTuple<uint8_t>(cv);
case ControlTypeInteger32:
return valueOrTuple<int32_t>(cv);
case ControlTypeInteger64:
return valueOrTuple<int64_t>(cv);
case ControlTypeFloat:
return valueOrTuple<float>(cv);
case ControlTypeString:
return py::cast(cv.get<std::string>());
case ControlTypeRectangle: {
const Rectangle *v = reinterpret_cast<const Rectangle *>(cv.data().data());
return py::make_tuple(v->x, v->y, v->width, v->height);
}
case ControlTypeSize: {
const Size *v = reinterpret_cast<const Size *>(cv.data().data());
return py::make_tuple(v->width, v->height);
}
case ControlTypeNone:
default:
throw std::runtime_error("Unsupported ControlValue type");
}
}
template<typename T>
static ControlValue controlValueMaybeArray(const py::object &ob)
{
if (py::isinstance<py::list>(ob) || py::isinstance<py::tuple>(ob)) {
std::vector<T> vec = ob.cast<std::vector<T>>();
return ControlValue(Span<const T>(vec));
}
return ControlValue(ob.cast<T>());
}
static ControlValue pyToControlValue(const py::object &ob, ControlType type)
{
switch (type) {
case ControlTypeBool:
return ControlValue(ob.cast<bool>());
case ControlTypeByte:
return controlValueMaybeArray<uint8_t>(ob);
case ControlTypeInteger32:
return controlValueMaybeArray<int32_t>(ob);
case ControlTypeInteger64:
return controlValueMaybeArray<int64_t>(ob);
case ControlTypeFloat:
return controlValueMaybeArray<float>(ob);
case ControlTypeString:
return ControlValue(ob.cast<std::string>());
case ControlTypeRectangle: {
auto array = ob.cast<std::array<int32_t, 4>>();
return ControlValue(Rectangle(array[0], array[1], array[2], array[3]));
}
case ControlTypeSize: {
auto array = ob.cast<std::array<int32_t, 2>>();
return ControlValue(Size(array[0], array[1]));
}
case ControlTypeNone:
default:
throw std::runtime_error("Control type not implemented");
}
}
static std::weak_ptr<CameraManager> gCameraManager;
static int gEventfd;
static std::mutex gReqlistMutex;
static std::vector<Request *> gReqList;
static void handleRequestCompleted(Request *req)
{
{
std::lock_guard guard(gReqlistMutex);
gReqList.push_back(req);
}
uint64_t v = 1;
size_t s = write(gEventfd, &v, 8);
/*
* We should never fail, and have no simple means to manage the error,
* so let's use LOG(Fatal).
*/
if (s != 8)
LOG(Fatal) << "Unable to write to eventfd";
}
void init_pyenums(py::module &m);
PYBIND11_MODULE(_libcamera, m)
{
init_pyenums(m);
/* Forward declarations */
/*
* We need to declare all the classes here so that Python docstrings
* can be generated correctly.
* https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings
*/
auto pyCameraManager = py::class_<CameraManager>(m, "CameraManager");
auto pyCamera = py::class_<Camera>(m, "Camera");
auto pyCameraConfiguration = py::class_<CameraConfiguration>(m, "CameraConfiguration");
auto pyCameraConfigurationStatus = py::enum_<CameraConfiguration::Status>(pyCameraConfiguration, "Status");
auto pyStreamConfiguration = py::class_<StreamConfiguration>(m, "StreamConfiguration");
auto pyStreamFormats = py::class_<StreamFormats>(m, "StreamFormats");
auto pyFrameBufferAllocator = py::class_<FrameBufferAllocator>(m, "FrameBufferAllocator");
auto pyFrameBuffer = py::class_<FrameBuffer>(m, "FrameBuffer");
auto pyStream = py::class_<Stream>(m, "Stream");
auto pyControlId = py::class_<ControlId>(m, "ControlId");
auto pyRequest = py::class_<Request>(m, "Request");
auto pyRequestStatus = py::enum_<Request::Status>(pyRequest, "Status");
auto pyRequestReuse = py::enum_<Request::ReuseFlag>(pyRequest, "Reuse");
auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
auto pyFrameMetadataStatus = py::enum_<FrameMetadata::Status>(pyFrameMetadata, "Status");
auto pyTransform = py::class_<Transform>(m, "Transform");
auto pyColorSpace = py::class_<ColorSpace>(m, "ColorSpace");
auto pyColorSpacePrimaries = py::enum_<ColorSpace::Primaries>(pyColorSpace, "Primaries");
auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
/* Global functions */
m.def("log_set_level", &logSetLevel);
/* Classes */
pyCameraManager
.def_static("singleton", []() {
std::shared_ptr<CameraManager> cm = gCameraManager.lock();
if (cm)
return cm;
int fd = eventfd(0, 0);
if (fd == -1)
throw std::system_error(errno, std::generic_category(),
"Failed to create eventfd");
cm = std::shared_ptr<CameraManager>(new CameraManager, [](auto p) {
close(gEventfd);
gEventfd = -1;
delete p;
});
gEventfd = fd;
gCameraManager = cm;
int ret = cm->start();
if (ret)
throw std::system_error(-ret, std::generic_category(),
"Failed to start CameraManager");
return cm;
})
.def_property_readonly("version", &CameraManager::version)
.def_property_readonly("efd", [](CameraManager &) {
return gEventfd;
})
.def("get_ready_requests", [](CameraManager &) {
std::vector<Request *> v;
{
std::lock_guard guard(gReqlistMutex);
swap(v, gReqList);
}
std::vector<py::object> ret;
for (Request *req : v) {
py::object o = py::cast(req);
/* Decrease the ref increased in Camera.queue_request() */
o.dec_ref();
ret.push_back(o);
}
return ret;
})
.def("get", py::overload_cast<const std::string &>(&CameraManager::get), py::keep_alive<0, 1>())
/* Create a list of Cameras, where each camera has a keep-alive to CameraManager */
.def_property_readonly("cameras", [](CameraManager &self) {
py::list l;
for (auto &c : self.cameras()) {
py::object py_cm = py::cast(self);
py::object py_cam = py::cast(c);
py::detail::keep_alive_impl(py_cam, py_cm);
l.append(py_cam);
}
return l;
});
pyCamera
.def_property_readonly("id", &Camera::id)
.def("acquire", &Camera::acquire)
.def("release", &Camera::release)
.def("start", [](Camera &self, py::dict controls) {
/* \todo What happens if someone calls start() multiple times? */
self.requestCompleted.connect(handleRequestCompleted);
const ControlInfoMap &controlMap = self.controls();
ControlList controlList(controlMap);
for (const auto& [hkey, hval]: controls) {
auto key = hkey.cast<std::string>();
auto it = std::find_if(controlMap.begin(), controlMap.end(),
[&key](const auto &kvp) {
return kvp.first->name() == key;
});
if (it == controlMap.end())
throw std::runtime_error("Control " + key + " not found");
const auto &id = it->first;
auto obj = py::cast<py::object>(hval);
controlList.set(id->id(), pyToControlValue(obj, id->type()));
}
int ret = self.start(&controlList);
if (ret) {
self.requestCompleted.disconnect(handleRequestCompleted);
return ret;
}
return 0;
}, py::arg("controls") = py::dict())
.def("stop", [](Camera &self) {
int ret = self.stop();
if (ret)
return ret;
self.requestCompleted.disconnect(handleRequestCompleted);
return 0;
})
.def("__str__", [](Camera &self) {
return "<libcamera.Camera '" + self.id() + "'>";
})
/* Keep the camera alive, as StreamConfiguration contains a Stream* */
.def("generate_configuration", &Camera::generateConfiguration, py::keep_alive<0, 1>())
.def("configure", &Camera::configure)
.def("create_request", &Camera::createRequest, py::arg("cookie") = 0)
.def("queue_request", [](Camera &self, Request *req) {
py::object py_req = py::cast(req);
/*
* Increase the reference count, will be dropped in
* CameraManager.get_ready_requests().
*/
py_req.inc_ref();
int ret = self.queueRequest(req);
if (ret)
py_req.dec_ref();
return ret;
})
.def_property_readonly("streams", [](Camera &self) {
py::set set;
for (auto &s : self.streams()) {
py::object py_self = py::cast(self);
py::object py_s = py::cast(s);
py::detail::keep_alive_impl(py_s, py_self);
set.add(py_s);
}
return set;
})
.def("find_control", [](Camera &self, const std::string &name) {
const auto &controls = self.controls();
auto it = std::find_if(controls.begin(), controls.end(),
[&name](const auto &kvp) {
return kvp.first->name() == name;
});
if (it == controls.end())
throw std::runtime_error("Control '" + name + "' not found");
return it->first;
}, py::return_value_policy::reference_internal)
.def_property_readonly("controls", [](Camera &self) {
py::dict ret;
for (const auto &[id, ci] : self.controls()) {
ret[id->name().c_str()] = std::make_tuple<py::object>(controlValueToPy(ci.min()),
controlValueToPy(ci.max()),
controlValueToPy(ci.def()));
}
return ret;
})
.def_property_readonly("properties", [](Camera &self) {
py::dict ret;
for (const auto &[key, cv] : self.properties()) {
const ControlId *id = properties::properties.at(key);
py::object ob = controlValueToPy(cv);
ret[id->name().c_str()] = ob;
}
return ret;
});
pyCameraConfiguration
.def("__iter__", [](CameraConfiguration &self) {
return py::make_iterator<py::return_value_policy::reference_internal>(self);
}, py::keep_alive<0, 1>())
.def("__len__", [](CameraConfiguration &self) {
return self.size();
})
.def("validate", &CameraConfiguration::validate)
.def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at),
py::return_value_policy::reference_internal)
.def_property_readonly("size", &CameraConfiguration::size)
.def_property_readonly("empty", &CameraConfiguration::empty)
.def_readwrite("transform", &CameraConfiguration::transform);
pyCameraConfigurationStatus
.value("Valid", CameraConfiguration::Valid)
.value("Adjusted", CameraConfiguration::Adjusted)
.value("Invalid", CameraConfiguration::Invalid);
pyStreamConfiguration
.def("__str__", &StreamConfiguration::toString)
.def_property_readonly("stream", &StreamConfiguration::stream,
py::return_value_policy::reference_internal)
.def_property(
"size",
[](StreamConfiguration &self) {
return std::make_tuple(self.size.width, self.size.height);
},
[](StreamConfiguration &self, std::tuple<uint32_t, uint32_t> size) {
self.size.width = std::get<0>(size);
self.size.height = std::get<1>(size);
})
.def_property(
"pixel_format",
[](StreamConfiguration &self) {
return self.pixelFormat.toString();
},
[](StreamConfiguration &self, std::string fmt) {
self.pixelFormat = PixelFormat::fromString(fmt);
})
.def_readwrite("stride", &StreamConfiguration::stride)
.def_readwrite("frame_size", &StreamConfiguration::frameSize)
.def_readwrite("buffer_count", &StreamConfiguration::bufferCount)
.def_property_readonly("formats", &StreamConfiguration::formats,
py::return_value_policy::reference_internal)
.def_readwrite("color_space", &StreamConfiguration::colorSpace);
pyStreamFormats
.def_property_readonly("pixel_formats", [](StreamFormats &self) {
std::vector<std::string> fmts;
for (auto &fmt : self.pixelformats())
fmts.push_back(fmt.toString());
return fmts;
})
.def("sizes", [](StreamFormats &self, const std::string &pixelFormat) {
auto fmt = PixelFormat::fromString(pixelFormat);
std::vector<std::tuple<uint32_t, uint32_t>> fmts;
for (const auto &s : self.sizes(fmt))
fmts.push_back(std::make_tuple(s.width, s.height));
return fmts;
})
.def("range", [](StreamFormats &self, const std::string &pixelFormat) {
auto fmt = PixelFormat::fromString(pixelFormat);
const auto &range = self.range(fmt);
return make_tuple(std::make_tuple(range.hStep, range.vStep),
std::make_tuple(range.min.width, range.min.height),
std::make_tuple(range.max.width, range.max.height));
});
pyFrameBufferAllocator
.def(py::init<std::shared_ptr<Camera>>(), py::keep_alive<1, 2>())
.def("allocate", &FrameBufferAllocator::allocate)
.def_property_readonly("allocated", &FrameBufferAllocator::allocated)
/* Create a list of FrameBuffers, where each FrameBuffer has a keep-alive to FrameBufferAllocator */
.def("buffers", [](FrameBufferAllocator &self, Stream *stream) {
py::object py_self = py::cast(self);
py::list l;
for (auto &ub : self.buffers(stream)) {
py::object py_buf = py::cast(ub.get(), py::return_value_policy::reference_internal, py_self);
l.append(py_buf);
}
return l;
});
pyFrameBuffer
/* \todo implement FrameBuffer::Plane properly */
.def(py::init([](std::vector<std::tuple<int, unsigned int>> planes, unsigned int cookie) {
std::vector<FrameBuffer::Plane> v;
for (const auto &t : planes)
v.push_back({ SharedFD(std::get<0>(t)), FrameBuffer::Plane::kInvalidOffset, std::get<1>(t) });
return new FrameBuffer(v, cookie);
}))
.def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal)
.def_property_readonly("num_planes", [](const FrameBuffer &self) {
return self.planes().size();
})
.def("length", [](FrameBuffer &self, uint32_t idx) {
const FrameBuffer::Plane &plane = self.planes()[idx];
return plane.length;
})
.def("fd", [](FrameBuffer &self, uint32_t idx) {
const FrameBuffer::Plane &plane = self.planes()[idx];
return plane.fd.get();
})
.def("offset", [](FrameBuffer &self, uint32_t idx) {
const FrameBuffer::Plane &plane = self.planes()[idx];
return plane.offset;
})
.def_property("cookie", &FrameBuffer::cookie, &FrameBuffer::setCookie);
pyStream
.def_property_readonly("configuration", &Stream::configuration);
pyControlId
.def_property_readonly("id", &ControlId::id)
.def_property_readonly("name", &ControlId::name)
.def_property_readonly("type", &ControlId::type);
pyRequest
/* \todo Fence is not supported, so we cannot expose addBuffer() directly */
.def("add_buffer", [](Request &self, const Stream *stream, FrameBuffer *buffer) {
return self.addBuffer(stream, buffer);
}, py::keep_alive<1, 3>()) /* Request keeps Framebuffer alive */
.def_property_readonly("status", &Request::status)
.def_property_readonly("buffers", &Request::buffers)
.def_property_readonly("cookie", &Request::cookie)
.def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers)
.def("set_control", [](Request &self, ControlId &id, py::object value) {
self.controls().set(id.id(), pyToControlValue(value, id.type()));
})
.def_property_readonly("metadata", [](Request &self) {
py::dict ret;
for (const auto &[key, cv] : self.metadata()) {
const ControlId *id = controls::controls.at(key);
py::object ob = controlValueToPy(cv);
ret[id->name().c_str()] = ob;
}
return ret;
})
/*
* \todo As we add a keep_alive to the fb in addBuffers(), we
* can only allow reuse with ReuseBuffers.
*/
.def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); });
pyRequestStatus
.value("Pending", Request::RequestPending)
.value("Complete", Request::RequestComplete)
.value("Cancelled", Request::RequestCancelled);
pyRequestReuse
.value("Default", Request::ReuseFlag::Default)
.value("ReuseBuffers", Request::ReuseFlag::ReuseBuffers);
pyFrameMetadata
.def_readonly("status", &FrameMetadata::status)
.def_readonly("sequence", &FrameMetadata::sequence)
.def_readonly("timestamp", &FrameMetadata::timestamp)
/* \todo Implement FrameMetadata::Plane properly */
.def_property_readonly("bytesused", [](FrameMetadata &self) {
std::vector<unsigned int> v;
v.resize(self.planes().size());
transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
return v;
});
pyFrameMetadataStatus
.value("Success", FrameMetadata::FrameSuccess)
.value("Error", FrameMetadata::FrameError)
.value("Cancelled", FrameMetadata::FrameCancelled);
pyTransform
.def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
bool ok;
Transform t = transformFromRotation(rotation, &ok);
if (!ok)
throw std::invalid_argument("Invalid rotation");
if (hflip)
t ^= Transform::HFlip;
if (vflip)
t ^= Transform::VFlip;
if (transpose)
t ^= Transform::Transpose;
return t;
}), py::arg("rotation") = 0, py::arg("hflip") = false,
py::arg("vflip") = false, py::arg("transpose") = false)
.def(py::init([](Transform &other) { return other; }))
.def("__str__", [](Transform &self) {
return "<libcamera.Transform '" + std::string(transformToString(self)) + "'>";
})
.def_property("hflip",
[](Transform &self) {
return !!(self & Transform::HFlip);
},
[](Transform &self, bool hflip) {
if (hflip)
self |= Transform::HFlip;
else
self &= ~Transform::HFlip;
})
.def_property("vflip",
[](Transform &self) {
return !!(self & Transform::VFlip);
},
[](Transform &self, bool vflip) {
if (vflip)
self |= Transform::VFlip;
else
self &= ~Transform::VFlip;
})
.def_property("transpose",
[](Transform &self) {
return !!(self & Transform::Transpose);
},
[](Transform &self, bool transpose) {
if (transpose)
self |= Transform::Transpose;
else
self &= ~Transform::Transpose;
})
.def("inverse", [](Transform &self) { return -self; })
.def("invert", [](Transform &self) {
self = -self;
})
.def("compose", [](Transform &self, Transform &other) {
self = self * other;
});
pyColorSpace
.def(py::init([](ColorSpace::Primaries primaries,
ColorSpace::TransferFunction transferFunction,
ColorSpace::YcbcrEncoding ycbcrEncoding,
ColorSpace::Range range) {
return ColorSpace(primaries, transferFunction, ycbcrEncoding, range);
}), py::arg("primaries"), py::arg("transferFunction"),
py::arg("ycbcrEncoding"), py::arg("range"))
.def(py::init([](ColorSpace &other) { return other; }))
.def("__str__", [](ColorSpace &self) {
return "<libcamera.ColorSpace '" + self.toString() + "'>";
})
.def_readwrite("primaries", &ColorSpace::primaries)
.def_readwrite("transferFunction", &ColorSpace::transferFunction)
.def_readwrite("ycbcrEncoding", &ColorSpace::ycbcrEncoding)
.def_readwrite("range", &ColorSpace::range)
.def_static("Raw", []() { return ColorSpace::Raw; })
.def_static("Jpeg", []() { return ColorSpace::Jpeg; })
.def_static("Srgb", []() { return ColorSpace::Srgb; })
.def_static("Smpte170m", []() { return ColorSpace::Smpte170m; })
.def_static("Rec709", []() { return ColorSpace::Rec709; })
.def_static("Rec2020", []() { return ColorSpace::Rec2020; });
pyColorSpacePrimaries
.value("Raw", ColorSpace::Primaries::Raw)
.value("Smpte170m", ColorSpace::Primaries::Smpte170m)
.value("Rec709", ColorSpace::Primaries::Rec709)
.value("Rec2020", ColorSpace::Primaries::Rec2020);
pyColorSpaceTransferFunction
.value("Linear", ColorSpace::TransferFunction::Linear)
.value("Srgb", ColorSpace::TransferFunction::Srgb)
.value("Rec709", ColorSpace::TransferFunction::Rec709);
pyColorSpaceYcbcrEncoding
.value("Null", ColorSpace::YcbcrEncoding::None)
.value("Rec601", ColorSpace::YcbcrEncoding::Rec601)
.value("Rec709", ColorSpace::YcbcrEncoding::Rec709)
.value("Rec2020", ColorSpace::YcbcrEncoding::Rec2020);
pyColorSpaceRange
.value("Full", ColorSpace::Range::Full)
.value("Limited", ColorSpace::Range::Limited);
}

1
src/py/meson.build Normal file
View file

@ -0,0 +1 @@
subdir('libcamera')

View file

@ -1,3 +1,4 @@
/googletest-release* /googletest-release*
/libyuv /libyuv
/packagecache /packagecache
/pybind11

View file

@ -0,0 +1,7 @@
project('pybind11', 'cpp',
version : '2.9.1',
license : 'BSD-3-Clause')
pybind11_incdir = include_directories('include')
pybind11_dep = declare_dependency(include_directories : pybind11_incdir)

View file

@ -0,0 +1,9 @@
[wrap-git]
url = https://github.com/pybind/pybind11.git
# This is the head of 'smart_holder' branch
revision = aebdf00cd060b871c5a1e0c2cf4a333503dd0431
depth = 1
patch_directory = pybind11
[provide]
pybind11 = pybind11_dep