summaryrefslogtreecommitdiff
path: root/src/py
diff options
context:
space:
mode:
Diffstat (limited to 'src/py')
-rwxr-xr-xsrc/py/cam/cam.py52
-rw-r--r--src/py/cam/helpers.py6
-rwxr-xr-xsrc/py/examples/simple-cam.py20
-rwxr-xr-xsrc/py/examples/simple-capture.py33
-rwxr-xr-xsrc/py/examples/simple-continuous-capture.py26
-rwxr-xr-xsrc/py/libcamera/gen-py-controls.py90
-rw-r--r--src/py/libcamera/meson.build52
-rw-r--r--src/py/libcamera/py_camera_manager.cpp131
-rw-r--r--src/py/libcamera/py_camera_manager.h45
-rw-r--r--src/py/libcamera/py_color_space.cpp70
-rw-r--r--src/py/libcamera/py_controls_generated.cpp.in8
-rw-r--r--src/py/libcamera/py_enums.cpp12
-rw-r--r--src/py/libcamera/py_formats_generated.cpp.in2
-rw-r--r--src/py/libcamera/py_geometry.cpp2
-rw-r--r--src/py/libcamera/py_helpers.cpp97
-rw-r--r--src/py/libcamera/py_helpers.h13
-rw-r--r--src/py/libcamera/py_main.cpp455
-rw-r--r--src/py/libcamera/py_main.h14
-rw-r--r--src/py/libcamera/py_properties_generated.cpp.in8
-rw-r--r--src/py/libcamera/py_transform.cpp81
-rw-r--r--src/py/meson.build2
21 files changed, 784 insertions, 435 deletions
diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
index 2ae89fa8..ff4b7f66 100755
--- a/src/py/cam/cam.py
+++ b/src/py/cam/cam.py
@@ -3,9 +3,6 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
-# \todo Convert ctx and state dicts to proper classes, and move relevant
-# functions to those classes.
-
from typing import Any
import argparse
import binascii
@@ -26,6 +23,7 @@ class CameraContext:
opt_metadata: bool
opt_save_frames: bool
opt_capture: int
+ opt_orientation: str
stream_names: dict[libcam.Stream, str]
streams: list[libcam.Stream]
@@ -149,6 +147,21 @@ class CameraContext:
if 'pixelformat' in stream_opts:
stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])
+ if self.opt_orientation is not None:
+ orientation_map = {
+ 'rot0': libcam.Orientation.Rotate0,
+ 'rot180': libcam.Orientation.Rotate180,
+ 'mirror': libcam.Orientation.Rotate0Mirror,
+ 'flip': libcam.Orientation.Rotate180Mirror,
+ }
+
+ orient = orientation_map.get(self.opt_orientation, None)
+ if orient is None:
+ print('Bad orientation: ', self.opt_orientation)
+ sys.exit(-1)
+
+ camconfig.orientation = orient
+
stat = camconfig.validate()
if stat == libcam.CameraConfiguration.Status.Invalid:
@@ -161,9 +174,7 @@ class CameraContext:
print('Camera configuration adjusted')
- r = self.camera.configure(camconfig)
- if r != 0:
- raise Exception('Configure failed')
+ self.camera.configure(camconfig)
self.stream_names = {}
self.streams = []
@@ -178,12 +189,7 @@ class CameraContext:
allocator = libcam.FrameBufferAllocator(self.camera)
for stream in self.streams:
- ret = allocator.allocate(stream)
- if ret < 0:
- print('Cannot allocate buffers')
- exit(-1)
-
- allocated = len(allocator.buffers(stream))
+ allocated = allocator.allocate(stream)
print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated))
@@ -208,10 +214,7 @@ class CameraContext:
buffers = self.allocator.buffers(stream)
buffer = buffers[buf_num]
- ret = request.add_buffer(stream, buffer)
- if ret < 0:
- print('Can not set buffer for request')
- exit(-1)
+ request.add_buffer(stream, buffer)
requests.append(request)
@@ -269,6 +272,11 @@ class CaptureState:
ctx.last = ts
ctx.fps = fps
+ if ctx.opt_metadata:
+ reqmeta = req.metadata
+ for ctrl, val in reqmeta.items():
+ print(f'\t{ctrl} = {val}')
+
for stream, fb in buffers.items():
stream_name = ctx.stream_names[stream]
@@ -287,11 +295,6 @@ class CaptureState:
'/'.join([str(p.bytes_used) for p in meta.planes]),
crcs))
- if ctx.opt_metadata:
- reqmeta = req.metadata
- for ctrl, val in reqmeta.items():
- print(f'\t{ctrl} = {val}')
-
if ctx.opt_save_frames:
with libcamera.utils.MappedFrameBuffer(fb) as mfb:
filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed)
@@ -398,6 +401,7 @@ def main():
parser.add_argument('--metadata', nargs=0, type=bool, action=CustomAction, help='Print the metadata for completed requests')
parser.add_argument('--strict-formats', type=bool, nargs=0, action=CustomAction, help='Do not allow requested stream format(s) to be adjusted')
parser.add_argument('-s', '--stream', nargs='+', action=CustomAction)
+ parser.add_argument('-o', '--orientation', help='Desired image orientation (rot0, rot180, mirror, flip)')
args = parser.parse_args()
cm = libcam.CameraManager.singleton()
@@ -421,6 +425,7 @@ def main():
ctx.opt_metadata = args.metadata.get(cam_idx, False)
ctx.opt_strict_formats = args.strict_formats.get(cam_idx, False)
ctx.opt_stream = args.stream.get(cam_idx, ['role=viewfinder'])
+ ctx.opt_orientation = args.orientation
contexts.append(ctx)
for ctx in contexts:
@@ -434,7 +439,10 @@ def main():
if args.info:
ctx.do_cmd_info()
- if args.capture:
+ # Filter out capture contexts which are not marked for capture
+ contexts = [ctx for ctx in contexts if ctx.opt_capture > 0]
+
+ if contexts:
state = CaptureState(cm, contexts)
if args.renderer == 'null':
diff --git a/src/py/cam/helpers.py b/src/py/cam/helpers.py
index 6b32a134..2d906667 100644
--- a/src/py/cam/helpers.py
+++ b/src/py/cam/helpers.py
@@ -117,14 +117,12 @@ def to_rgb(fmt, size, data):
bayer_pattern = fmt[1:5]
bitspp = int(fmt[5:])
- # \todo shifting leaves the lowest bits 0
if bitspp == 8:
data = data.reshape((h, w))
- data = data.astype(np.uint16) << 8
+ data = data.astype(np.uint16)
elif bitspp in [10, 12]:
data = data.view(np.uint16)
data = data.reshape((h, w))
- data = data << (16 - bitspp)
else:
raise Exception('Bad bitspp:' + str(bitspp))
@@ -145,7 +143,7 @@ def to_rgb(fmt, size, data):
b0 = (idx % 2, idx // 2)
rgb = demosaic(data, r0, g0, g1, b0)
- rgb = (rgb >> 8).astype(np.uint8)
+ rgb = (rgb >> (bitspp - 8)).astype(np.uint8)
else:
rgb = None
diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py
index 2b81bb65..1cd1019d 100755
--- a/src/py/examples/simple-cam.py
+++ b/src/py/examples/simple-cam.py
@@ -19,8 +19,9 @@ TIMEOUT_SEC = 3
def handle_camera_event(cm):
- # cm.get_ready_requests() will not block here, as we know there is an event
- # to read.
+ # cm.get_ready_requests() returns the ready requests, which in our case
+ # should almost always return a single Request, but in some cases there
+ # could be multiple or none.
reqs = cm.get_ready_requests()
@@ -258,12 +259,7 @@ def main():
allocator = libcam.FrameBufferAllocator(camera)
for cfg in config:
- ret = allocator.allocate(cfg.stream)
- if ret < 0:
- print('Can\'t allocate buffers')
- return -1
-
- allocated = len(allocator.buffers(cfg.stream))
+ allocated = allocator.allocate(cfg.stream)
print(f'Allocated {allocated} buffers for stream')
# --------------------------------------------------------------------
@@ -288,15 +284,9 @@ def main():
requests = []
for i in range(len(buffers)):
request = camera.create_request()
- if not request:
- print('Can\'t create request')
- return -1
buffer = buffers[i]
- ret = request.add_buffer(stream, buffer)
- if ret < 0:
- print('Can\'t set buffer for request')
- return -1
+ request.add_buffer(stream, buffer)
# Controls can be added to a request on a per frame basis.
request.set_control(libcam.controls.Brightness, 0.5)
diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py
index a6a9b33e..4b85408f 100755
--- a/src/py/examples/simple-capture.py
+++ b/src/py/examples/simple-capture.py
@@ -14,6 +14,7 @@
import argparse
import libcamera as libcam
+import selectors
import sys
# Number of frames to capture
@@ -42,8 +43,7 @@ def main():
# Acquire the camera for our use
- ret = cam.acquire()
- assert ret == 0
+ cam.acquire()
# Configure the camera
@@ -59,8 +59,7 @@ def main():
w, h = [int(v) for v in args.size.split('x')]
stream_config.size = libcam.Size(w, h)
- ret = cam.configure(cam_config)
- assert ret == 0
+ cam.configure(cam_config)
print(f'Capturing {TOTAL_FRAMES} frames with {stream_config}')
@@ -82,15 +81,13 @@ def main():
req = cam.create_request(i)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- assert ret == 0
+ req.add_buffer(stream, buffer)
reqs.append(req)
# Start the camera
- ret = cam.start()
- assert ret == 0
+ cam.start()
# frames_queued and frames_done track the number of frames queued and done
@@ -100,18 +97,24 @@ def main():
# Queue the requests to the camera
for req in reqs:
- ret = cam.queue_request(req)
- assert ret == 0
+ cam.queue_request(req)
frames_queued += 1
# The main loop. Wait for the queued Requests to complete, process them,
# and re-queue them again.
+ sel = selectors.DefaultSelector()
+ sel.register(cm.event_fd, selectors.EVENT_READ)
+
while frames_done < TOTAL_FRAMES:
- # cm.get_ready_requests() blocks until there is an event and returns
- # all the ready requests. Here we should almost always get a single
+ # cm.get_ready_requests() does not block, so we use a Selector to wait
+ # for a camera event. Here we should almost always get a single
# Request, but in some cases there could be multiple or none.
+ events = sel.select()
+ if not events:
+ continue
+
reqs = cm.get_ready_requests()
for req in reqs:
@@ -147,13 +150,11 @@ def main():
# Stop the camera
- ret = cam.stop()
- assert ret == 0
+ cam.stop()
# Release the camera
- ret = cam.release()
- assert ret == 0
+ cam.release()
return 0
diff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py
index fe78a2dd..e1cb931e 100755
--- a/src/py/examples/simple-continuous-capture.py
+++ b/src/py/examples/simple-continuous-capture.py
@@ -28,8 +28,7 @@ class CameraCaptureContext:
# Acquire the camera for our use
- ret = cam.acquire()
- assert ret == 0
+ cam.acquire()
# Configure the camera
@@ -37,8 +36,7 @@ class CameraCaptureContext:
stream_config = cam_config.at(0)
- ret = cam.configure(cam_config)
- assert ret == 0
+ cam.configure(cam_config)
stream = stream_config.stream
@@ -62,8 +60,7 @@ class CameraCaptureContext:
req = cam.create_request(idx)
buffer = allocator.buffers(stream)[i]
- ret = req.add_buffer(stream, buffer)
- assert ret == 0
+ req.add_buffer(stream, buffer)
self.reqs.append(req)
@@ -73,13 +70,11 @@ class CameraCaptureContext:
def uninit_camera(self):
# Stop the camera
- ret = self.cam.stop()
- assert ret == 0
+ self.cam.stop()
# Release the camera
- ret = self.cam.release()
- assert ret == 0
+ self.cam.release()
# A container class for our state
@@ -88,8 +83,9 @@ class CaptureContext:
camera_contexts: list[CameraCaptureContext] = []
def handle_camera_event(self):
- # cm.get_ready_requests() will not block here, as we know there is an event
- # to read.
+ # cm.get_ready_requests() returns the ready requests, which in our case
+ # should almost always return a single Request, but in some cases there
+ # could be multiple or none.
reqs = self.cm.get_ready_requests()
@@ -144,8 +140,7 @@ class CaptureContext:
for cam_ctx in self.camera_contexts:
for req in cam_ctx.reqs:
- ret = cam_ctx.cam.queue_request(req)
- assert ret == 0
+ cam_ctx.cam.queue_request(req)
# Use Selector to wait for events from the camera and from the keyboard
@@ -176,8 +171,7 @@ def main():
# Start the cameras
for cam_ctx in ctx.camera_contexts:
- ret = cam_ctx.cam.start()
- assert ret == 0
+ cam_ctx.cam.start()
ctx.capture()
diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py
index 99f3bbcf..8efbf95b 100755
--- a/src/py/libcamera/gen-py-controls.py
+++ b/src/py/libcamera/gen-py-controls.py
@@ -24,48 +24,57 @@ def find_common_prefix(strings):
def generate_py(controls, mode):
out = ''
- for ctrl in controls:
- name, ctrl = ctrl.popitem()
-
- if ctrl.get('draft'):
- ns = 'libcamera::{}::draft::'.format(mode)
- container = 'draft'
- else:
- ns = 'libcamera::{}::'.format(mode)
- container = 'controls'
+ vendors_class_def = []
+ vendor_defs = []
+ vendors = []
+ for vendor, ctrl_list in controls.items():
+ for ctrls in ctrl_list:
+ name, ctrl = ctrls.popitem()
+
+ if vendor not in vendors and vendor != 'libcamera':
+ vendor_mode_str = f'{vendor.capitalize()}{mode.capitalize()}'
+ vendors_class_def.append('class Py{}\n{{\n}};\n'.format(vendor_mode_str))
+ vendor_defs.append('\tauto {} = py::class_<Py{}>(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor))
+ vendors.append(vendor)
+
+ if vendor != 'libcamera':
+ ns = 'libcamera::{}::{}::'.format(mode, vendor)
+ container = vendor
+ else:
+ ns = 'libcamera::{}::'.format(mode)
+ container = 'controls'
- out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
+ out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
- enum = ctrl.get('enum')
- if not enum:
- continue
+ enum = ctrl.get('enum')
+ if not enum:
+ continue
- cpp_enum = name + 'Enum'
+ cpp_enum = name + 'Enum'
- out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
+ out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
- if mode == 'controls':
- # Adjustments for controls
- if name == 'LensShadingMapMode':
- prefix = 'LensShadingMapMode'
- elif name == 'SceneFlicker':
- # If we strip the prefix, we would get '50Hz', which is illegal name
- prefix = ''
+ if mode == 'controls':
+ # Adjustments for controls
+ if name == 'LensShadingMapMode':
+ prefix = 'LensShadingMapMode'
+ else:
+ prefix = find_common_prefix([e['name'] for e in enum])
else:
+ # Adjustments for properties
prefix = find_common_prefix([e['name'] for e in enum])
- else:
- # Adjustments for properties
- prefix = find_common_prefix([e['name'] for e in enum])
- for entry in enum:
- cpp_enum = entry['name']
- py_enum = entry['name'][len(prefix):]
+ for entry in enum:
+ cpp_enum = entry['name']
+ py_enum = entry['name'][len(prefix):]
- out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
+ out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
- out += '\t;\n\n'
+ out += '\t;\n\n'
- return {'controls': out}
+ return {'controls': out,
+ 'vendors_class_def': '\n'.join(vendors_class_def),
+ 'vendors_defs': '\n'.join(vendor_defs)}
def fill_template(template, data):
@@ -78,22 +87,25 @@ def fill_template(template, data):
def main(argv):
# Parse command line arguments
parser = argparse.ArgumentParser()
- parser.add_argument('-o', dest='output', metavar='file', type=str,
+ parser.add_argument('--mode', '-m', type=str, required=True,
+ help='Mode is either "controls" or "properties"')
+ parser.add_argument('--output', '-o', metavar='file', type=str,
help='Output file name. Defaults to standard output if not specified.')
- parser.add_argument('input', type=str,
- help='Input file name.')
- parser.add_argument('template', type=str,
+ parser.add_argument('--template', '-t', type=str, required=True,
help='Template file name.')
- parser.add_argument('--mode', type=str, required=True,
- help='Mode is either "controls" or "properties"')
+ parser.add_argument('input', type=str, nargs='+',
+ help='Input file name.')
args = parser.parse_args(argv[1:])
if args.mode not in ['controls', 'properties']:
print(f'Invalid mode option "{args.mode}"', file=sys.stderr)
return -1
- data = open(args.input, 'rb').read()
- controls = yaml.safe_load(data)['controls']
+ controls = {}
+ for input in args.input:
+ data = open(input, 'rb').read()
+ vendor = yaml.safe_load(data)['vendor']
+ controls[vendor] = yaml.safe_load(data)['controls']
data = generate_py(controls, args.mode)
diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build
index eb884538..4807ca7d 100644
--- a/src/py/libcamera/meson.build
+++ b/src/py/libcamera/meson.build
@@ -7,42 +7,56 @@ if not py3_dep.found()
subdir_done()
endif
-pycamera_enabled = true
+pybind11_dep = dependency('pybind11', required : get_option('pycamera'))
+
+if not pybind11_dep.found()
+ pycamera_enabled = false
+ subdir_done()
+endif
-pybind11_proj = subproject('pybind11')
-pybind11_dep = pybind11_proj.get_variable('pybind11_dep')
+pycamera_enabled = true
pycamera_sources = files([
+ 'py_camera_manager.cpp',
+ 'py_color_space.cpp',
'py_enums.cpp',
'py_geometry.cpp',
+ 'py_helpers.cpp',
'py_main.cpp',
+ 'py_transform.cpp',
])
# Generate controls
-gen_py_controls_input_files = files([
- '../../libcamera/control_ids.yaml',
- 'py_controls_generated.cpp.in',
-])
+gen_py_controls_input_files = []
+gen_py_controls_template = files('py_controls_generated.cpp.in')
gen_py_controls = files('gen-py-controls.py')
+foreach file : controls_files
+ gen_py_controls_input_files += files('../../libcamera/' + file)
+endforeach
+
pycamera_sources += custom_target('py_gen_controls',
input : gen_py_controls_input_files,
output : ['py_controls_generated.cpp'],
- command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', '@INPUT@'])
+ command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@',
+ '-t', gen_py_controls_template, '@INPUT@'])
# Generate properties
-gen_py_property_enums_input_files = files([
- '../../libcamera/property_ids.yaml',
- 'py_properties_generated.cpp.in',
-])
+gen_py_property_enums_input_files = []
+gen_py_properties_template = files('py_properties_generated.cpp.in')
+
+foreach file : properties_files
+ gen_py_property_enums_input_files += files('../../libcamera/' + file)
+endforeach
pycamera_sources += custom_target('py_gen_properties',
input : gen_py_property_enums_input_files,
output : ['py_properties_generated.cpp'],
- command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@'])
+ command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@',
+ '-t', gen_py_properties_template, '@INPUT@'])
# Generate formats
@@ -59,7 +73,7 @@ pycamera_sources += custom_target('py_gen_formats',
command : [gen_py_formats, '-o', '@OUTPUT@', '@INPUT@'])
pycamera_deps = [
- libcamera_public,
+ libcamera_private,
py3_dep,
pybind11_dep,
]
@@ -68,7 +82,6 @@ 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'
@@ -77,6 +90,7 @@ pycamera = shared_module('_libcamera',
pycamera_sources,
install : true,
install_dir : destdir,
+ install_tag : 'python-runtime',
name_prefix : '',
dependencies : pycamera_deps,
cpp_args : pycamera_args)
@@ -86,13 +100,15 @@ pycamera = shared_module('_libcamera',
run_command('ln', '-fsrT', files('__init__.py'),
meson.current_build_dir() / '__init__.py',
- check: true)
+ check : true)
run_command('ln', '-fsrT', meson.current_source_dir() / 'utils',
meson.current_build_dir() / 'utils',
- check: true)
+ check : true)
-install_data(['__init__.py'], install_dir : destdir)
+install_data(['__init__.py'],
+ install_dir : destdir,
+ install_tag : 'python-runtime')
# \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files
# Note: Depends on pybind11-stubgen. To generate pylibcamera stubs:
diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp
new file mode 100644
index 00000000..9ccb7aad
--- /dev/null
+++ b/src/py/libcamera/py_camera_manager.cpp
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include "py_camera_manager.h"
+
+#include <errno.h>
+#include <memory>
+#include <sys/eventfd.h>
+#include <system_error>
+#include <unistd.h>
+#include <vector>
+
+#include "py_main.h"
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+PyCameraManager::PyCameraManager()
+{
+ LOG(Python, Debug) << "PyCameraManager()";
+
+ cameraManager_ = std::make_unique<CameraManager>();
+
+ int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ if (fd == -1)
+ throw std::system_error(errno, std::generic_category(),
+ "Failed to create eventfd");
+
+ eventFd_ = UniqueFD(fd);
+
+ int ret = cameraManager_->start();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to start CameraManager");
+}
+
+PyCameraManager::~PyCameraManager()
+{
+ LOG(Python, Debug) << "~PyCameraManager()";
+}
+
+py::list PyCameraManager::cameras()
+{
+ /*
+ * Create a list of Cameras, where each camera has a keep-alive to
+ * CameraManager.
+ */
+ py::list l;
+
+ for (auto &camera : cameraManager_->cameras()) {
+ py::object py_cm = py::cast(this);
+ py::object py_cam = py::cast(camera);
+ py::detail::keep_alive_impl(py_cam, py_cm);
+ l.append(py_cam);
+ }
+
+ return l;
+}
+
+std::vector<py::object> PyCameraManager::getReadyRequests()
+{
+ int ret = readFd();
+
+ if (ret == -EAGAIN)
+ return std::vector<py::object>();
+
+ if (ret != 0)
+ throw std::system_error(-ret, std::generic_category());
+
+ std::vector<py::object> py_reqs;
+
+ for (Request *request : getCompletedRequests()) {
+ py::object o = py::cast(request);
+ /* Decrease the ref increased in Camera.queue_request() */
+ o.dec_ref();
+ py_reqs.push_back(o);
+ }
+
+ return py_reqs;
+}
+
+/* Note: Called from another thread */
+void PyCameraManager::handleRequestCompleted(Request *req)
+{
+ pushRequest(req);
+ writeFd();
+}
+
+void PyCameraManager::writeFd()
+{
+ uint64_t v = 1;
+
+ size_t s = write(eventFd_.get(), &v, 8);
+ /*
+ * We should never fail, and have no simple means to manage the error,
+ * so let's log a fatal error.
+ */
+ if (s != 8)
+ LOG(Python, Fatal) << "Unable to write to eventfd";
+}
+
+int PyCameraManager::readFd()
+{
+ uint8_t buf[8];
+
+ ssize_t ret = read(eventFd_.get(), buf, 8);
+
+ if (ret == 8)
+ return 0;
+ else if (ret < 0)
+ return -errno;
+ else
+ return -EIO;
+}
+
+void PyCameraManager::pushRequest(Request *req)
+{
+ MutexLocker guard(completedRequestsMutex_);
+ completedRequests_.push_back(req);
+}
+
+std::vector<Request *> PyCameraManager::getCompletedRequests()
+{
+ std::vector<Request *> v;
+ MutexLocker guard(completedRequestsMutex_);
+ swap(v, completedRequests_);
+ return v;
+}
diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h
new file mode 100644
index 00000000..3574db23
--- /dev/null
+++ b/src/py/libcamera/py_camera_manager.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/base/mutex.h>
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/pybind11.h>
+
+using namespace libcamera;
+
+class PyCameraManager
+{
+public:
+ PyCameraManager();
+ ~PyCameraManager();
+
+ pybind11::list cameras();
+ std::shared_ptr<Camera> get(const std::string &name) { return cameraManager_->get(name); }
+
+ static const std::string &version() { return CameraManager::version(); }
+
+ int eventFd() const { return eventFd_.get(); }
+
+ std::vector<pybind11::object> getReadyRequests();
+
+ void handleRequestCompleted(Request *req);
+
+private:
+ std::unique_ptr<CameraManager> cameraManager_;
+
+ UniqueFD eventFd_;
+ libcamera::Mutex completedRequestsMutex_;
+ std::vector<Request *> completedRequests_
+ LIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);
+
+ void writeFd();
+ int readFd();
+ void pushRequest(Request *req);
+ std::vector<Request *> getCompletedRequests();
+};
diff --git a/src/py/libcamera/py_color_space.cpp b/src/py/libcamera/py_color_space.cpp
new file mode 100644
index 00000000..5201121a
--- /dev/null
+++ b/src/py/libcamera/py_color_space.cpp
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ *
+ * Python bindings - Color Space classes
+ */
+
+#include <libcamera/color_space.h>
+#include <libcamera/libcamera.h>
+
+#include <pybind11/operators.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+void init_py_color_space(py::module &m)
+{
+ 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");
+
+ 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("Srgb", []() { return ColorSpace::Srgb; })
+ .def_static("Sycc", []() { return ColorSpace::Sycc; })
+ .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);
+}
diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in
index cb8442ba..8d282ce5 100644
--- a/src/py/libcamera/py_controls_generated.cpp.in
+++ b/src/py/libcamera/py_controls_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/control_ids.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -17,14 +17,12 @@ class PyControls
{
};
-class PyDraftControls
-{
-};
+${vendors_class_def}
void init_py_controls_generated(py::module& m)
{
auto controls = py::class_<PyControls>(m, "controls");
- auto draft = py::class_<PyDraftControls>(controls, "draft");
+${vendors_defs}
${controls}
}
diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp
index 96d4beef..e25689c6 100644
--- a/src/py/libcamera/py_enums.cpp
+++ b/src/py/libcamera/py_enums.cpp
@@ -7,7 +7,7 @@
#include <libcamera/libcamera.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -31,4 +31,14 @@ void init_py_enums(py::module &m)
.value("String", ControlType::ControlTypeString)
.value("Rectangle", ControlType::ControlTypeRectangle)
.value("Size", ControlType::ControlTypeSize);
+
+ py::enum_<Orientation>(m, "Orientation")
+ .value("Rotate0", Orientation::Rotate0)
+ .value("Rotate0Mirror", Orientation::Rotate0Mirror)
+ .value("Rotate180", Orientation::Rotate180)
+ .value("Rotate180Mirror", Orientation::Rotate180Mirror)
+ .value("Rotate90Mirror", Orientation::Rotate90Mirror)
+ .value("Rotate270", Orientation::Rotate270)
+ .value("Rotate270Mirror", Orientation::Rotate270Mirror)
+ .value("Rotate90", Orientation::Rotate90);
}
diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/libcamera/py_formats_generated.cpp.in
index b88807f3..a3f7f94d 100644
--- a/src/py/libcamera/py_formats_generated.cpp.in
+++ b/src/py/libcamera/py_formats_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/formats.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp
index 84b0cb08..5c2aeac4 100644
--- a/src/py/libcamera/py_geometry.cpp
+++ b/src/py/libcamera/py_geometry.cpp
@@ -11,7 +11,7 @@
#include <libcamera/libcamera.h>
#include <pybind11/operators.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp
new file mode 100644
index 00000000..79891ab6
--- /dev/null
+++ b/src/py/libcamera/py_helpers.cpp
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include "py_helpers.h"
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/functional.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>());
+}
+
+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:
+ return valueOrTuple<Rectangle>(cv);
+ case ControlTypeSize: {
+ const Size *v = reinterpret_cast<const Size *>(cv.data().data());
+ return py::cast(v);
+ }
+ case ControlTypeNone:
+ return py::none();
+ 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>());
+}
+
+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:
+ return controlValueMaybeArray<Rectangle>(ob);
+ case ControlTypeSize:
+ return ControlValue(ob.cast<Size>());
+ case ControlTypeNone:
+ return ControlValue();
+ default:
+ throw std::runtime_error("Control type not implemented");
+ }
+}
diff --git a/src/py/libcamera/py_helpers.h b/src/py/libcamera/py_helpers.h
new file mode 100644
index 00000000..983969df
--- /dev/null
+++ b/src/py/libcamera/py_helpers.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/libcamera.h>
+
+#include <pybind11/pybind11.h>
+
+pybind11::object controlValueToPy(const libcamera::ControlValue &cv);
+libcamera::ControlValue pyToControlValue(const pybind11::object &ob, libcamera::ControlType type);
diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp
index 505cc3dc..bce08218 100644
--- a/src/py/libcamera/py_main.cpp
+++ b/src/py/libcamera/py_main.cpp
@@ -5,132 +5,93 @@
* Python bindings
*/
-#include <mutex>
+#include "py_main.h"
+
+#include <memory>
#include <stdexcept>
-#include <sys/eventfd.h>
-#include <unistd.h>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/libcamera.h>
#include <pybind11/functional.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
+#include "py_camera_manager.h"
+#include "py_helpers.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());
+namespace libcamera {
- for (size_t i = 0; i < cv.numElements(); ++i)
- t[i] = v[i];
+LOG_DEFINE_CATEGORY(Python)
- return std::move(t);
- }
-
- return py::cast(cv.get<T>());
}
-static py::object controlValueToPy(const ControlValue &cv)
+/*
+ * This is a holder class used only for the Camera class, for the sole purpose
+ * of avoiding the compilation issue with Camera's private destructor.
+ *
+ * pybind11 requires a public destructor for classes held with shared_ptrs, even
+ * in cases where the public destructor is not strictly needed. The current
+ * understanding is that there are the following options to solve the problem:
+ *
+ * - Use pybind11 'smart_holder' branch. The downside is that 'smart_holder'
+ * is not the mainline branch, and not available in distributions.
+ * - https://github.com/pybind/pybind11/pull/2067
+ * - Make the Camera destructor public
+ * - Something like the PyCameraSmartPtr here, which adds a layer, hiding the
+ * issue.
+ */
+template<typename T>
+class PyCameraSmartPtr
{
- 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::cast(v);
- }
- case ControlTypeSize: {
- const Size *v = reinterpret_cast<const Size *>(cv.data().data());
- return py::cast(v);
+public:
+ using element_type = T;
+
+ PyCameraSmartPtr()
+ {
}
- case ControlTypeNone:
- default:
- throw std::runtime_error("Unsupported ControlValue type");
+
+ explicit PyCameraSmartPtr(T *)
+ {
+ throw std::runtime_error("invalid SmartPtr constructor call");
}
-}
-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));
+ explicit PyCameraSmartPtr(std::shared_ptr<T> p)
+ : ptr_(p)
+ {
}
- return ControlValue(ob.cast<T>());
-}
+ T *get() const { return ptr_.get(); }
-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:
- return ControlValue(ob.cast<Rectangle>());
- case ControlTypeSize:
- return ControlValue(ob.cast<Size>());
- case ControlTypeNone:
- default:
- throw std::runtime_error("Control type not implemented");
- }
-}
+ operator std::shared_ptr<T>() const { return ptr_; }
-static std::weak_ptr<CameraManager> gCameraManager;
-static int gEventfd;
-static std::mutex gReqlistMutex;
-static std::vector<Request *> gReqList;
+private:
+ std::shared_ptr<T> ptr_;
+};
-static void handleRequestCompleted(Request *req)
-{
- {
- std::lock_guard guard(gReqlistMutex);
- gReqList.push_back(req);
- }
+PYBIND11_DECLARE_HOLDER_TYPE(T, PyCameraSmartPtr<T>)
- 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";
-}
+/*
+ * Note: global C++ destructors can be ran on this before the py module is
+ * destructed.
+ */
+static std::weak_ptr<PyCameraManager> gCameraManager;
-void init_py_enums(py::module &m);
+void init_py_color_space(py::module &m);
void init_py_controls_generated(py::module &m);
+void init_py_enums(py::module &m);
void init_py_formats_generated(py::module &m);
void init_py_geometry(py::module &m);
void init_py_properties_generated(py::module &m);
+void init_py_transform(py::module &m);
PYBIND11_MODULE(_libcamera, m)
{
@@ -138,6 +99,8 @@ PYBIND11_MODULE(_libcamera, m)
init_py_controls_generated(m);
init_py_geometry(m);
init_py_properties_generated(m);
+ init_py_color_space(m);
+ init_py_transform(m);
/* Forward declarations */
@@ -147,8 +110,9 @@ PYBIND11_MODULE(_libcamera, m)
* 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 pyCameraManager = py::class_<PyCameraManager, std::shared_ptr<PyCameraManager>>(m, "CameraManager");
+ auto pyCamera = py::class_<Camera, PyCameraSmartPtr<Camera>>(m, "Camera");
+ auto pySensorConfiguration = py::class_<SensorConfiguration>(m, "SensorConfiguration");
auto pyCameraConfiguration = py::class_<CameraConfiguration>(m, "CameraConfiguration");
auto pyCameraConfigurationStatus = py::enum_<CameraConfiguration::Status>(pyCameraConfiguration, "Status");
auto pyStreamConfiguration = py::class_<StreamConfiguration>(m, "StreamConfiguration");
@@ -165,12 +129,6 @@ PYBIND11_MODULE(_libcamera, m)
auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
auto pyFrameMetadataStatus = py::enum_<FrameMetadata::Status>(pyFrameMetadata, "Status");
auto pyFrameMetadataPlane = py::class_<FrameMetadata::Plane>(pyFrameMetadata, "Plane");
- 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");
auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
init_py_formats_generated(m);
@@ -181,113 +139,69 @@ PYBIND11_MODULE(_libcamera, m)
/* 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("event_fd", [](CameraManager &) {
- return gEventfd;
- })
-
- .def("get_ready_requests", [](CameraManager &) {
- uint8_t buf[8];
-
- if (read(gEventfd, buf, 8) != 8)
- throw std::system_error(errno, std::generic_category());
-
- std::vector<Request *> v;
-
- {
- std::lock_guard guard(gReqlistMutex);
- swap(v, gReqList);
- }
-
- std::vector<py::object> ret;
+ std::shared_ptr<PyCameraManager> cm = gCameraManager.lock();
- 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);
+ if (!cm) {
+ cm = std::make_shared<PyCameraManager>();
+ gCameraManager = cm;
}
- return ret;
+ return cm;
})
- .def("get", py::overload_cast<const std::string &>(&CameraManager::get), py::keep_alive<0, 1>())
+ .def_property_readonly_static("version", [](py::object /* self */) { return PyCameraManager::version(); })
+ .def("get", &PyCameraManager::get, py::keep_alive<0, 1>())
+ .def_property_readonly("cameras", &PyCameraManager::cameras)
- /* 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;
- });
+ .def_property_readonly("event_fd", &PyCameraManager::eventFd)
+ .def("get_ready_requests", &PyCameraManager::getReadyRequests);
pyCamera
.def_property_readonly("id", &Camera::id)
- .def("acquire", &Camera::acquire)
- .def("release", &Camera::release)
+ .def("acquire", [](Camera &self) {
+ int ret = self.acquire();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to acquire camera");
+ })
+ .def("release", [](Camera &self) {
+ int ret = self.release();
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to release camera");
+ })
.def("start", [](Camera &self,
const std::unordered_map<const ControlId *, py::object> &controls) {
/* \todo What happens if someone calls start() multiple times? */
- self.requestCompleted.connect(handleRequestCompleted);
+ auto cm = gCameraManager.lock();
+ ASSERT(cm);
+
+ self.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted);
ControlList controlList(self.controls());
- for (const auto& [id, obj]: controls) {
+ for (const auto &[id, obj] : controls) {
auto val = pyToControlValue(obj, id->type());
controlList.set(id->id(), val);
}
int ret = self.start(&controlList);
if (ret) {
- self.requestCompleted.disconnect(handleRequestCompleted);
- return ret;
+ self.requestCompleted.disconnect();
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to start camera");
}
-
- return 0;
}, py::arg("controls") = std::unordered_map<const ControlId *, py::object>())
.def("stop", [](Camera &self) {
int ret = self.stop();
- if (ret)
- return ret;
- self.requestCompleted.disconnect(handleRequestCompleted);
+ self.requestCompleted.disconnect();
- return 0;
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to stop camera");
})
.def("__str__", [](Camera &self) {
@@ -295,10 +209,24 @@ PYBIND11_MODULE(_libcamera, m)
})
/* Keep the camera alive, as StreamConfiguration contains a Stream* */
- .def("generate_configuration", &Camera::generateConfiguration, py::keep_alive<0, 1>())
- .def("configure", &Camera::configure)
+ .def("generate_configuration", [](Camera &self, const std::vector<StreamRole> &roles) {
+ return self.generateConfiguration(roles);
+ }, py::keep_alive<0, 1>())
+
+ .def("configure", [](Camera &self, CameraConfiguration *config) {
+ int ret = self.configure(config);
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to configure camera");
+ })
- .def("create_request", &Camera::createRequest, py::arg("cookie") = 0)
+ .def("create_request", [](Camera &self, uint64_t cookie) {
+ std::unique_ptr<Request> req = self.createRequest(cookie);
+ if (!req)
+ throw std::system_error(ENOMEM, std::generic_category(),
+ "Failed to create request");
+ return req;
+ }, py::arg("cookie") = 0)
.def("queue_request", [](Camera &self, Request *req) {
py::object py_req = py::cast(req);
@@ -311,10 +239,11 @@ PYBIND11_MODULE(_libcamera, m)
py_req.inc_ref();
int ret = self.queueRequest(req);
- if (ret)
+ if (ret) {
py_req.dec_ref();
-
- return ret;
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to queue request");
+ }
})
.def_property_readonly("streams", [](Camera &self) {
@@ -353,6 +282,40 @@ PYBIND11_MODULE(_libcamera, m)
return ret;
});
+ pySensorConfiguration
+ .def(py::init<>())
+ .def_readwrite("bit_depth", &SensorConfiguration::bitDepth)
+ .def_readwrite("analog_crop", &SensorConfiguration::analogCrop)
+ .def_property(
+ "binning",
+ [](SensorConfiguration &self) {
+ return py::make_tuple(self.binning.binX, self.binning.binY);
+ },
+ [](SensorConfiguration &self, py::object value) {
+ auto vec = value.cast<std::vector<unsigned int>>();
+ if (vec.size() != 2)
+ throw std::runtime_error("binning requires iterable of 2 values");
+ self.binning.binX = vec[0];
+ self.binning.binY = vec[1];
+ })
+ .def_property(
+ "skipping",
+ [](SensorConfiguration &self) {
+ return py::make_tuple(self.skipping.xOddInc, self.skipping.xEvenInc,
+ self.skipping.yOddInc, self.skipping.yEvenInc);
+ },
+ [](SensorConfiguration &self, py::object value) {
+ auto vec = value.cast<std::vector<unsigned int>>();
+ if (vec.size() != 4)
+ throw std::runtime_error("skipping requires iterable of 4 values");
+ self.skipping.xOddInc = vec[0];
+ self.skipping.xEvenInc = vec[1];
+ self.skipping.yOddInc = vec[2];
+ self.skipping.yEvenInc = vec[3];
+ })
+ .def_readwrite("output_size", &SensorConfiguration::outputSize)
+ .def("is_valid", &SensorConfiguration::isValid);
+
pyCameraConfiguration
.def("__iter__", [](CameraConfiguration &self) {
return py::make_iterator<py::return_value_policy::reference_internal>(self);
@@ -365,7 +328,8 @@ PYBIND11_MODULE(_libcamera, m)
py::return_value_policy::reference_internal)
.def_property_readonly("size", &CameraConfiguration::size)
.def_property_readonly("empty", &CameraConfiguration::empty)
- .def_readwrite("transform", &CameraConfiguration::transform);
+ .def_readwrite("sensor_config", &CameraConfiguration::sensorConfig)
+ .def_readwrite("orientation", &CameraConfiguration::orientation);
pyCameraConfigurationStatus
.value("Valid", CameraConfiguration::Valid)
@@ -391,8 +355,14 @@ PYBIND11_MODULE(_libcamera, m)
.def("range", &StreamFormats::range);
pyFrameBufferAllocator
- .def(py::init<std::shared_ptr<Camera>>(), py::keep_alive<1, 2>())
- .def("allocate", &FrameBufferAllocator::allocate)
+ .def(py::init<PyCameraSmartPtr<Camera>>(), py::keep_alive<1, 2>())
+ .def("allocate", [](FrameBufferAllocator &self, Stream *stream) {
+ int ret = self.allocate(stream);
+ if (ret < 0)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to allocate buffers");
+ return ret;
+ })
.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) {
@@ -469,11 +439,15 @@ PYBIND11_MODULE(_libcamera, m)
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);
+ int ret = self.addBuffer(stream, buffer);
+ if (ret)
+ throw std::system_error(-ret, std::generic_category(),
+ "Failed to add 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("sequence", &Request::sequence)
.def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers)
.def("set_control", [](Request &self, const ControlId &id, py::object value) {
self.controls().set(id.id(), pyToControlValue(value, id.type()));
@@ -526,109 +500,6 @@ PYBIND11_MODULE(_libcamera, m)
pyFrameMetadataPlane
.def_readwrite("bytes_used", &FrameMetadata::Plane::bytesused);
- 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);
-
pyPixelFormat
.def(py::init<>())
.def(py::init<uint32_t, uint64_t>())
diff --git a/src/py/libcamera/py_main.h b/src/py/libcamera/py_main.h
new file mode 100644
index 00000000..5bb5f2d1
--- /dev/null
+++ b/src/py/libcamera/py_main.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#pragma once
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Python)
+
+}
diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in
index 044b2b2a..e3802b81 100644
--- a/src/py/libcamera/py_properties_generated.cpp.in
+++ b/src/py/libcamera/py_properties_generated.cpp.in
@@ -9,7 +9,7 @@
#include <libcamera/property_ids.h>
-#include <pybind11/smart_holder.h>
+#include <pybind11/pybind11.h>
namespace py = pybind11;
@@ -17,14 +17,12 @@ class PyProperties
{
};
-class PyDraftProperties
-{
-};
+${vendors_class_def}
void init_py_properties_generated(py::module& m)
{
auto controls = py::class_<PyProperties>(m, "properties");
- auto draft = py::class_<PyDraftProperties>(controls, "draft");
+${vendors_defs}
${controls}
}
diff --git a/src/py/libcamera/py_transform.cpp b/src/py/libcamera/py_transform.cpp
new file mode 100644
index 00000000..f3a0bfaf
--- /dev/null
+++ b/src/py/libcamera/py_transform.cpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ *
+ * Python bindings - Transform class
+ */
+
+#include <libcamera/transform.h>
+#include <libcamera/libcamera.h>
+
+#include <pybind11/operators.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+namespace py = pybind11;
+
+using namespace libcamera;
+
+void init_py_transform(py::module &m)
+{
+ auto pyTransform = py::class_<Transform>(m, "Transform");
+
+ 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;
+ });
+}
diff --git a/src/py/meson.build b/src/py/meson.build
index 4ce9668c..a4586b4a 100644
--- a/src/py/meson.build
+++ b/src/py/meson.build
@@ -1 +1,3 @@
+# SPDX-License-Identifier: CC0-1.0
+
subdir('libcamera')