From patchwork Tue May 24 11:45:52 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16007 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 207ACC3256 for ; Tue, 24 May 2022 11:46:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D7A6A6566E; Tue, 24 May 2022 13:46:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392792; bh=upYDWDJdApqmzorAWn3YLVaQA6aoQJaTCnP3PF5ggoU=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=HKaqJ4M8mBkdnoC9VU2kMCR8wreGPIGFUERcjv8StRKMuPlQ/CP/dTJ9WE0VtUcZ5 gNCEcjBQnmoMIlavj65aLih3yMj8gx8d9Dj/i+DMisgIzxhZz3afFVNpRjNDP1yf7r MJlwKMj9M9zN6qVuwFe5RLgxR7ju6lrlIkccoN72TGb3fOh0qOPY47OH/MVmE8gU65 lB0nj5tBV+aX8Lm3p9+hl4+4eX0FsJsEzyeiQ0LjLtMF3nN2w0K6byHxFI6cFdZj66 IJOXdDw12o6tMWOWZPRkFPXKW6Uh0r57PakbEXHPhDJMFlBfBLtgAz/YX/UJYOL96m CkIszGmzkmHnA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B275365660 for ; Tue, 24 May 2022 13:46:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Zzx7s9Ex"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E964659D; Tue, 24 May 2022 13:46:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392790; bh=upYDWDJdApqmzorAWn3YLVaQA6aoQJaTCnP3PF5ggoU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Zzx7s9ExECIPZ4HZYxHYeplKYwCC79APRtUOmTRFaCD+I1qp7ggkj/LEHaMwdqc3s xg5Q3pcwmKpfwbEXln2JBfisTLnOzcTzY1a3tukHjxIZVd0h1jn+C7oxKIhjJNw2oG 3rwQ4/fJBARJ6VcvLpWOdd8BYXXMTO1yEThRd3/0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:52 +0300 Message-Id: <20220524114610.41848-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 01/19] py: Generate pixel formats list X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Generate a list of pixel formats under 'libcamera.formats'. The 'formats' is a "dummy" container class, the only purpose of which is to contain the read-only pixel format properties. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/gen-py-formats.py | 56 ++++++++++++++++++++ src/py/libcamera/meson.build | 12 +++++ src/py/libcamera/py_formats_generated.cpp.in | 25 +++++++++ src/py/libcamera/py_main.cpp | 3 ++ 4 files changed, 96 insertions(+) create mode 100755 src/py/libcamera/gen-py-formats.py create mode 100644 src/py/libcamera/py_formats_generated.cpp.in diff --git a/src/py/libcamera/gen-py-formats.py b/src/py/libcamera/gen-py-formats.py new file mode 100755 index 00000000..0ff1d12a --- /dev/null +++ b/src/py/libcamera/gen-py-formats.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Generate Python format definitions from YAML + +import argparse +import string +import sys +import yaml + + +def generate(formats): + fmts = [] + + for format in formats: + name, format = format.popitem() + fmts.append(f'\t\t.def_readonly_static("{name}", &libcamera::formats::{name})') + + return {'formats': '\n'.join(fmts)} + + +def fill_template(template, data): + with open(template, encoding='utf-8') as f: + template = f.read() + + template = string.Template(template) + return template.substitute(data) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('-o', dest='output', 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, + help='Template file name.') + args = parser.parse_args(argv[1:]) + + with open(args.input, encoding='utf-8') as f: + formats = yaml.safe_load(f)['formats'] + + data = generate(formats) + data = fill_template(args.template, data) + + if args.output: + with open(args.output, 'w', encoding='utf-8') as f: + f.write(data) + else: + sys.stdout.write(data) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index a3388c63..0a7b65f3 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -30,6 +30,18 @@ pycamera_sources += custom_target('py_gen_controls', output : ['py_control_enums_generated.cpp'], command : [gen_py_control_enums, '-o', '@OUTPUT@', '@INPUT@']) +gen_py_formats_input_files = files([ + '../../libcamera/formats.yaml', + 'py_formats_generated.cpp.in', +]) + +gen_py_formats = files('gen-py-formats.py') + +pycamera_sources += custom_target('py_gen_formats', + input : gen_py_formats_input_files, + output : ['py_formats_generated.cpp'], + command : [gen_py_formats, '-o', '@OUTPUT@', '@INPUT@']) + pycamera_deps = [ libcamera_public, py3_dep, diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/libcamera/py_formats_generated.cpp.in new file mode 100644 index 00000000..b88807f3 --- /dev/null +++ b/src/py/libcamera/py_formats_generated.cpp.in @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings - Auto-generated formats + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include + +namespace py = pybind11; + +class PyFormats +{ +}; + +void init_py_formats_generated(py::module& m) +{ + py::class_(m, "formats") +${formats} + ; +} diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 1d941160..e7066841 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -132,6 +132,7 @@ static void handleRequestCompleted(Request *req) void init_py_enums(py::module &m); void init_py_control_enums_generated(py::module &m); +void init_py_formats_generated(py::module &m); void init_py_geometry(py::module &m); PYBIND11_MODULE(_libcamera, m) @@ -171,6 +172,8 @@ PYBIND11_MODULE(_libcamera, m) auto pyColorSpaceRange = py::enum_(pyColorSpace, "Range"); auto pyPixelFormat = py::class_(m, "PixelFormat"); + init_py_formats_generated(m); + /* Global functions */ m.def("log_set_level", &logSetLevel); From patchwork Tue May 24 11:45:53 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16008 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 19C2DBD161 for ; Tue, 24 May 2022 11:46:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0C2D265665; Tue, 24 May 2022 13:46:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392794; bh=u2tqjxRj2OwecQxi/1sS9LWYVbQZr9S9oBztjV3FNyo=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=qpPAsv78OSasKj7eHNRsn+gDYfk3HX5Dw3mnDFHzw+97FB158B/UwRRMuPNbvpaGh OaMtu067DmpwpWbfgrfCz4FWUyOllLpad0SWTAg71PdUrB1bsRXdsu38lXI+8J/8Qc sPMmpjXXwTOH38NUxfym8cMQXt9AHCs+LYMVoSMEti+3/UL+v6Zx/3u2uA+gXMF0qm m83WIyjn24NtaGW/fn/3X2coP0ZjcpfCbjHdBgcoqEULs3DtvnBXYvEHYk4sp6ujTX zGTmspJe0Ac+WY6vuqVS85IcKEezPtKAwYyKKB4nacqBHTwDS4B9pkDed4W2r27JGk EnrhSyiFq2mHQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0E26565662 for ; Tue, 24 May 2022 13:46:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BMPQYmhf"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 72D138DC; Tue, 24 May 2022 13:46:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392790; bh=u2tqjxRj2OwecQxi/1sS9LWYVbQZr9S9oBztjV3FNyo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BMPQYmhfh2NIASyrCInOR8tfz4Ig/u1X3zbYGp7uK70pWPLCePUa2mMIclUgdZ0em IIGhNZeq9U1T1PrbKC53oJDLKxPQHSbTcl9XPOLqhfcRQUR1b8eW5k3xEkYk8TGTjm DKeJoo8VSuB/EWRMBsO0C5LN8hNBLy1MykoCjpQw= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:53 +0300 Message-Id: <20220524114610.41848-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 02/19] py: cam_qt: Use libcamera.formats X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Use the new libcamera.formats in cam_qt.py. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham --- src/py/cam/cam_qt.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 91be2a08..0a25a823 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -8,6 +8,7 @@ from numpy.lib.stride_tricks import as_strided from PIL import Image from PIL.ImageQt import ImageQt from PyQt5 import QtCore, QtGui, QtWidgets +import libcamera as libcam import numpy as np import sys @@ -87,9 +88,7 @@ def to_rgb(fmt, size, data): w = size.width h = size.height - fmt = str(fmt) - - if fmt == 'YUYV': + if fmt == libcam.formats.YUYV: # YUV422 yuyv = data.reshape((h, w // 2 * 4)) @@ -111,20 +110,21 @@ def to_rgb(fmt, size, data): rgb[:, :, 2] -= 226.8183044444304 rgb = rgb.astype(np.uint8) - elif fmt == 'RGB888': + elif fmt == libcam.formats.RGB888: rgb = data.reshape((h, w, 3)) rgb[:, :, [0, 1, 2]] = rgb[:, :, [2, 1, 0]] - elif fmt == 'BGR888': + elif fmt == libcam.formats.BGR888: rgb = data.reshape((h, w, 3)) - elif fmt in ['ARGB8888', 'XRGB8888']: + elif fmt in [libcam.formats.ARGB8888, libcam.formats.XRGB8888]: rgb = data.reshape((h, w, 4)) rgb = np.flip(rgb, axis=2) # drop alpha component rgb = np.delete(rgb, np.s_[0::4], axis=2) - elif fmt.startswith('S'): + elif str(fmt).startswith('S'): + fmt = str(fmt) bayer_pattern = fmt[1:5] bitspp = int(fmt[5:]) @@ -296,7 +296,7 @@ class MainWindow(QtWidgets.QWidget): h = cfg.size.height pitch = cfg.stride - if str(cfg.pixel_format) == 'MJPEG': + if cfg.pixel_format == libcam.formats.MJPEG: img = Image.open(BytesIO(mfb.planes[0])) qim = ImageQt(img).copy() pix = QtGui.QPixmap.fromImage(qim) From patchwork Tue May 24 11:45:54 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16009 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 20C68C3256 for ; Tue, 24 May 2022 11:46:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 333D165673; Tue, 24 May 2022 13:46:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392795; bh=MvmqihW0QvEgtz1gL+sN5WamZWcNyohataGssaZYKvo=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=CRRpPGodBWmnZPfNxilTK0Krxq+hAUIvfLMTCU+9yq4Dw++QGIwLJ+aTKdJP2XK30 huPhzuG0G60i5qNoeItQoIZ4lvfEa03raa4ujl/6FlwKfY7toz0GSKpYAimoEyCpWp KvFuqFJ2YjJYegKakloAJh5gVbg8oTcrlevbVAHFNOxefmL9wjN2JAWXkRn8zJyh+j UlTzIYvEROxr7VT9xsGPrEGQ6lE4zauOFZviL95CbqfzThCyAiot/N5SeApWA/w343 t+BDKHD5RJvqlIXBmIcv6p6yvsvJJ62PQHm28UBTPHZakIkWUbIPkfmrYSNhBlAVKc SmnaEFErwpUQw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8A9E365662 for ; Tue, 24 May 2022 13:46:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kt0lfHjM"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F22E2A16; Tue, 24 May 2022 13:46:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392791; bh=MvmqihW0QvEgtz1gL+sN5WamZWcNyohataGssaZYKvo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kt0lfHjMx2qaeUag7GHQuG0LgpxyeXTOb24Zz0Uu36EYAYc0t5B+zEGUAiLz0ZGol gX0sObjqBfTdPeM7Y20Js6TKVv5dj5/AAdsKC2buHP5/8WWTBqWsYwYHFHuWCbeG6M HNtcxvxTIbOcoD7VWzF0/H6RmMJlkc+aWcEIXSHM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:54 +0300 Message-Id: <20220524114610.41848-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 03/19] py: cam: Cleanups X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" No functional changes. Drop unused variables, reduce typechecker warnings. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 2 +- src/py/cam/cam_qt.py | 12 ++---------- src/py/cam/cam_qtgl.py | 9 ++------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 2f0690b5..e2bc78da 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -304,7 +304,7 @@ def event_handler(state): running = any(ctx['reqs-completed'] < ctx['opt-capture'] for ctx in contexts) return running - except Exception as e: + except Exception: traceback.print_exc() return False diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 0a25a823..bff1175b 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -184,12 +184,7 @@ class QtRenderer: windows = [] for ctx in self.contexts: - camera = ctx['camera'] - for stream in ctx['streams']: - fmt = stream.configuration.pixel_format - size = stream.configuration.size - window = MainWindow(ctx, stream) window.setAttribute(QtCore.Qt.WA_ShowWithoutActivating) window.show() @@ -199,10 +194,10 @@ class QtRenderer: def run(self): camnotif = QtCore.QSocketNotifier(self.cm.efd, QtCore.QSocketNotifier.Read) - camnotif.activated.connect(lambda x: self.readcam()) + camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) - keynotif.activated.connect(lambda x: self.readkey()) + keynotif.activated.connect(lambda _: self.readkey()) print('Capturing...') @@ -292,9 +287,6 @@ class MainWindow(QtWidgets.QWidget): def buf_to_qpixmap(self, stream, fb): with fb.mmap() as mfb: cfg = stream.configuration - w = cfg.size.width - h = cfg.size.height - pitch = cfg.stride if cfg.pixel_format == libcam.formats.MJPEG: img = Image.open(BytesIO(mfb.planes[0])) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index 4bbcda6c..3fb7dde3 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -5,16 +5,11 @@ from PyQt5 import QtCore, QtWidgets from PyQt5.QtCore import Qt import math -import numpy as np import os import sys os.environ['PYOPENGL_PLATFORM'] = 'egl' -import OpenGL -# OpenGL.FULL_LOGGING = True - -from OpenGL import GL as gl from OpenGL.EGL.EXT.image_dma_buf_import import * from OpenGL.EGL.KHR.image import * from OpenGL.EGL.VERSION.EGL_1_0 import * @@ -149,10 +144,10 @@ class QtRenderer: def run(self): camnotif = QtCore.QSocketNotifier(self.state['cm'].efd, QtCore.QSocketNotifier.Read) - camnotif.activated.connect(lambda x: self.readcam()) + camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) - keynotif.activated.connect(lambda x: self.readkey()) + keynotif.activated.connect(lambda _: self.readkey()) print('Capturing...') From patchwork Tue May 24 11:45:55 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16010 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 6122CC326D for ; Tue, 24 May 2022 11:46:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 39E7E65678; Tue, 24 May 2022 13:46:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392797; bh=J82y7/1hXVMiwHUAH2A1Gwma14uw5Jc//aizDlp0V5U=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=XutE+9veGd7UuRxKNeVOHptHTjhY0DEUCsnLAMo1Q3bGljM7EKCgbaVo7Ojj9kpb6 sGsOGrPLANiciTEm6mrvQbJwjf+/u9wAksffcfJeZaiyfACYh31ET696/EAyPJuMi1 va5f47prpHUkDZIOP171ZXfwti5jhsmVNkBP8Q57eT8XnmHQgDLIHeNTSjFRBswsF7 VlgB+eTj49RpZSnrtMuD27dHxuX4l7nOeYYpvGGFPah8SVpXpAZAzGXJuDrt4bfZuw 8MRo5s7u9ugFkkIS4tGmL6Yk9VTNaTnVZohuDOWkNY/U1GKNPGu53YvQ4wsLICibaM qzdoCeVEvePnQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1473465666 for ; Tue, 24 May 2022 13:46:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="I0H1K9zq"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7CAA4AEC; Tue, 24 May 2022 13:46:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392791; bh=J82y7/1hXVMiwHUAH2A1Gwma14uw5Jc//aizDlp0V5U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=I0H1K9zqHtG5xJhQznh4wO4RH8WO8z+5rGLivtTK4A65Sk+/wgt2ACXo3qOCn3DIh /4a2V/b842oBg86chnaSNLZgz8/RTJhhmaKGdh/UUcpeLl1W1We1gTxNBmE2shOvwl QRAgSwwOXETdOjrWsXQBYJwRVrAiJohAHfjXDzHo= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:55 +0300 Message-Id: <20220524114610.41848-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 04/19] py: Fix SceneFlicker enum values X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Stripping 'SceneFlicker' prefix from the enum value names leads to '50Hz' and '60Hz', which are not valid names. So add another heuristics to keep the prefix for SceneFlicker. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/gen-py-control-enums.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/py/libcamera/gen-py-control-enums.py b/src/py/libcamera/gen-py-control-enums.py index dcc28b1a..6b2b5362 100755 --- a/src/py/libcamera/gen-py-control-enums.py +++ b/src/py/libcamera/gen-py-control-enums.py @@ -42,6 +42,9 @@ def generate_py(controls): if name == 'LensShadingMapMode': prefix = 'LensShadingMapMode' + elif name == 'SceneFlicker': + # If we strip the prefix, we would get '50Hz', which is illegal name + prefix = '' else: prefix = find_common_prefix([e['name'] for e in enum]) From patchwork Tue May 24 11:45:56 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16011 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 23B72BD161 for ; Tue, 24 May 2022 11:46:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AFD1A65660; Tue, 24 May 2022 13:46:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392798; bh=gB8wORxbh5RCxUzoZ/j9CClyLL96wF3CiB0KpQy77Fw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=V1AacIKbwAujd4/H/eUT3tshusoR3ClwPZMO8wr0kuU1lRV1EH93HP8gFjOHR7i6M u7uTPld6om+8fVc/xyBoWVAqNyTH2HaYBCGd/uTkoIVJtnAGNOUfrPMkjfwdpnQrzO /GNtXaJSnNoKdavD6fViGmkEHmaYHuWVd6zrDicvzRhSVpWN2Kbm3BMrbAmJWP3RFK Q9Z9BXVKycTN+O8+dg8mC1ldkBi0T5HHSv18qKf3TLd+PvN/kgE2TVD3XtAlS1H7Wm B6hYiZBUbOYfQhgPk81gTVIT8lA8SoU868cAKKN29yk0kUx/PMq5QpIem2t36CPepo /lUnUt2eDZT8A== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9B3766566C for ; Tue, 24 May 2022 13:46:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fNmyUsf2"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 07048BC1; Tue, 24 May 2022 13:46:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392792; bh=gB8wORxbh5RCxUzoZ/j9CClyLL96wF3CiB0KpQy77Fw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fNmyUsf2M/bJNsNcDq9IMwOoIKZNhS1ImkTsP6ZyzsPSoGyoJDJvbgmM6mN+CQ8nB DHHE7+mrZ4ut29N70SWjVloJ9cEw0z9RU2ZntvMovEI8vcWu2jdSTMem8sk1w2kIux dNUwTh2pL5rjDzzf4bEmsjiPLihrmNbITQaGj31I= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:56 +0300 Message-Id: <20220524114610.41848-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 05/19] py: Fix None value in ControlType enum X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" "None" is not a valid name for an enum value, so change it to "Null". Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/py_enums.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp index e55318f1..96d4beef 100644 --- a/src/py/libcamera/py_enums.cpp +++ b/src/py/libcamera/py_enums.cpp @@ -22,7 +22,7 @@ void init_py_enums(py::module &m) .value("Viewfinder", StreamRole::Viewfinder); py::enum_(m, "ControlType") - .value("None", ControlType::ControlTypeNone) + .value("Null", ControlType::ControlTypeNone) .value("Bool", ControlType::ControlTypeBool) .value("Byte", ControlType::ControlTypeByte) .value("Integer32", ControlType::ControlTypeInteger32) From patchwork Tue May 24 11:45:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16012 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id ED79EC326E for ; Tue, 24 May 2022 11:46:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 718EE65686; Tue, 24 May 2022 13:46:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392799; bh=GrOP/OlQSS6f85g7v8fdN0LyeTNIso0TXXdoVOivEro=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=LhFlN3c8dTHn/H/Dt3f4bb3XO+mi3B1VDaGwuz/g1RWVIWb4heMcvWO3loxVeOWoG RLovsOSAWa1+EFtG8wu+MvsuSVTs3naF0ltxC1vkK0Y/6rK6jTpsQY4VkkQX8QNyrR u8+a7tbwJjQ7kExtHIuxRJqhCNgXqVYzoT3U4hjDtZrrA/2IKXZ/hWVP55+HB7nSHH 5NCyokXTYweZzKcKNI5Crk0BwwIMt1JItgygdoV09jmAQB53DsJPFGBZgJkNvcFubq kaFg2fur1lzcyb4vzukdRmajykC+lfI/zvIwGJmgaCLwBoB4RH1mRNI80Rkm6wyYZu z/ZI4LaaUn/jw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1F3B46566F for ; Tue, 24 May 2022 13:46:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="J8iZSKo7"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8565E4A8; Tue, 24 May 2022 13:46:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392792; bh=GrOP/OlQSS6f85g7v8fdN0LyeTNIso0TXXdoVOivEro=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=J8iZSKo7Sek8eoT3eDnOYDSnZOYlVySbA4n7Fxf9nbvKwdeRGNkT9/f7r3Rq5Yi9D 4wi3GjMzQljl03i1LJH/+k1ALrWT6kYXZtMIDq0rnGz0kFZVQ88I3Vs+sEIdDW14j3 15RZE1wfqmSLWfrM9NL/oVCe7+w5kwnjZOEg0M4w= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:57 +0300 Message-Id: <20220524114610.41848-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 06/19] py: Add CameraManager.read_event() X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add CameraManager.read_event() so that the user does not need to call os.read(). We use eventfd, and we must always read 8 bytes. Hiding that inside read_event() makes sense. Signed-off-by: Tomi Valkeinen --- src/py/cam/cam.py | 3 +-- src/py/libcamera/py_main.cpp | 8 ++++++++ test/py/unittests.py | 3 +-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index e2bc78da..66df18bf 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -9,7 +9,6 @@ import argparse import binascii import libcamera as libcam -import os import sys import traceback @@ -294,7 +293,7 @@ def event_handler(state): cm = state['cm'] contexts = state['contexts'] - os.read(cm.efd, 8) + cm.read_event() reqs = cm.get_ready_requests() diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index e7066841..5d389942 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -212,6 +212,14 @@ PYBIND11_MODULE(_libcamera, m) return gEventfd; }) + .def("read_event", [](CameraManager &) { + uint8_t buf[8]; + + int ret = read(gEventfd, buf, 8); + if (ret != 8) + throw std::system_error(errno, std::generic_category()); + }) + .def("get_ready_requests", [](CameraManager &) { std::vector v; diff --git a/test/py/unittests.py b/test/py/unittests.py index 7dede33b..8c445bc9 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -7,7 +7,6 @@ from collections import defaultdict import errno import gc import libcamera as libcam -import os import selectors import time import typing @@ -278,7 +277,7 @@ class SimpleCaptureMethods(CameraTesterBase): while running: events = sel.select() for key, _ in events: - os.read(key.fd, 8) + cm.read_event() ready_reqs = cm.get_ready_requests() From patchwork Tue May 24 11:45:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16013 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id DFCD3C326F for ; Tue, 24 May 2022 11:46:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 93DDA65680; Tue, 24 May 2022 13:46:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392800; bh=3Y6MZT2PdQBH3vdPc7O0LKIaLRSQ+GWagBQ6i4gKJt4=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=n6U0Jn5bbBchUY/p1R+9XiPA3eBHC7QRd5GPHq+g4X/QL9fbz6rAjTE4uPvZhM1dU X7JFTSUWs6TbEawR5sZ4m68ln9JKT8onM5JBPdRBKDQf5yfjQaUJZTx7eHXFKM7NVL iCOn5g1QvGuf3xhaOeN7lhP8l9BdI5p1Ldxxf+4suX5v9/8kkTwvp8/j9IZbAN41sO CA+iJcRS0ppU3uWVOyJIdHjR4L9cgap4XThSQwCFvytpQcS7H7n9qYRo+0MyGJpXtF 4JFS72zQhBYdZwZKBq3cpnSLu/uk9ljCbgBo9nXLClAXiplLCaBZE86TR9+ARwNz5M QyANZmDIqMK5A== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A19F465671 for ; Tue, 24 May 2022 13:46:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uuduTYKs"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0E38859D; Tue, 24 May 2022 13:46:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392793; bh=3Y6MZT2PdQBH3vdPc7O0LKIaLRSQ+GWagBQ6i4gKJt4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uuduTYKsi1/+F50gyIbbniOmrxPEEfW0z07w7nGry2k2dx1hOMXL6B+tlGVUiECbl Gi7l2uUhhq0vjiAoAiJmAM/wbgzqHwRybRmoZXqJhZ4r+NlRSN2iixtnSXy0NP071N 6K2tv6iXBJT7B7inwrGq2xYBupjpOMSfV9d0vCGY= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:58 +0300 Message-Id: <20220524114610.41848-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 07/19] py: Move MappedFrameBuffer to libcamera.utils X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Move MappedFrameBuffer to libcamera.utils, instead of extending FrameBuffer class with a new mmap() method. This keeps us more aligned to the C++ API. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 5 +- src/py/cam/cam_qt.py | 3 +- src/py/libcamera/__init__.py | 80 ------------------- src/py/libcamera/meson.build | 4 + .../MappedFrameBuffer.py} | 10 --- src/py/libcamera/utils/__init__.py | 4 + 6 files changed, 13 insertions(+), 93 deletions(-) copy src/py/libcamera/{__init__.py => utils/MappedFrameBuffer.py} (93%) create mode 100644 src/py/libcamera/utils/__init__.py diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 66df18bf..64f67e86 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -9,6 +9,7 @@ import argparse import binascii import libcamera as libcam +import libcamera.utils import sys import traceback @@ -327,7 +328,7 @@ def request_handler(state, ctx, req): crcs = [] if ctx['opt-crc']: - with fb.mmap() as mfb: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: plane_crcs = [binascii.crc32(p) for p in mfb.planes] crcs.append(plane_crcs) @@ -345,7 +346,7 @@ def request_handler(state, ctx, req): print(f'\t{ctrl} = {val}') if ctx['opt-save-frames']: - with fb.mmap() as mfb: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: filename = 'frame-{}-{}-{}.data'.format(ctx['id'], stream_name, ctx['reqs-completed']) with open(filename, 'wb') as f: for p in mfb.planes: diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index bff1175b..70bdb7bb 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -9,6 +9,7 @@ from PIL import Image from PIL.ImageQt import ImageQt from PyQt5 import QtCore, QtGui, QtWidgets import libcamera as libcam +import libcamera.utils import numpy as np import sys @@ -285,7 +286,7 @@ class MainWindow(QtWidgets.QWidget): controlsLayout.addStretch() def buf_to_qpixmap(self, stream, fb): - with fb.mmap() as mfb: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: cfg = stream.configuration if cfg.pixel_format == libcam.formats.MJPEG: diff --git a/src/py/libcamera/__init__.py b/src/py/libcamera/__init__.py index 0d7da9e2..e234a5e4 100644 --- a/src/py/libcamera/__init__.py +++ b/src/py/libcamera/__init__.py @@ -2,83 +2,3 @@ # Copyright (C) 2022, Tomi Valkeinen 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 diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index 0a7b65f3..b705ac1f 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -72,6 +72,10 @@ run_command('ln', '-fsT', files('__init__.py'), meson.current_build_dir() / '__init__.py', check: true) +run_command('ln', '-fsT', meson.current_source_dir() / 'utils', + meson.current_build_dir() / 'utils', + check: true) + install_data(['__init__.py'], install_dir : destdir) # \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files diff --git a/src/py/libcamera/__init__.py b/src/py/libcamera/utils/MappedFrameBuffer.py similarity index 93% copy from src/py/libcamera/__init__.py copy to src/py/libcamera/utils/MappedFrameBuffer.py index 0d7da9e2..e7dd16ec 100644 --- a/src/py/libcamera/__init__.py +++ b/src/py/libcamera/utils/MappedFrameBuffer.py @@ -1,9 +1,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2022, Tomi Valkeinen -from ._libcamera import * - - class MappedFrameBuffer: def __init__(self, fb): self.__fb = fb @@ -75,10 +72,3 @@ class MappedFrameBuffer: @property def planes(self): return self.__planes - - -def __FrameBuffer__mmap(self): - return MappedFrameBuffer(self) - - -FrameBuffer.mmap = __FrameBuffer__mmap diff --git a/src/py/libcamera/utils/__init__.py b/src/py/libcamera/utils/__init__.py new file mode 100644 index 00000000..4a23ce36 --- /dev/null +++ b/src/py/libcamera/utils/__init__.py @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (C) 2022, Tomi Valkeinen + +from .MappedFrameBuffer import MappedFrameBuffer From patchwork Tue May 24 11:45:59 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16014 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id BC748C3256 for ; Tue, 24 May 2022 11:46:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3298B65674; Tue, 24 May 2022 13:46:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392803; bh=9bWxwlmHas1NFcPFPjm1Dcf+Y5YWWSGzxnClX4EPsDs=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=Z+hdzXlJZk2vYUcZirN31TZqHDOvhq3LAS65edi9Yf8RyZuQVXNlzXSuLEUg99EuW llDUjomKU1SyRsBGjzyWzb/5ipfg32ADJheZuY6O0Jh8wO25ulLmIz2/vs/jLCEHNx Q7g06ic3VRVFLJZOwi5ZqQIh/3OowoatXho5+NumHKxKNrA4+HhCBkDy7xlEM+3cIV BbY58W2kp9jdnUUmljNMkblsLRCk9qxAMmwFdu8068lpQzGqzfcMeMF5VjWShNMUXo cA/WrGrO+JJQ8vF6ROgOVTDLwcqCs7spEoScdNPIKeUbiYREJB2FNKK/Sj5s1cK8vc Db0ev2GVvBXYA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2FF4A65667 for ; Tue, 24 May 2022 13:46:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XmQtH2KR"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 97E4F8DC; Tue, 24 May 2022 13:46:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392793; bh=9bWxwlmHas1NFcPFPjm1Dcf+Y5YWWSGzxnClX4EPsDs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XmQtH2KR9MHRY5b0JoeL2TPtjwQoRZLt7s4wr/m02Y9C2DZ3XrBxSanlFgWAulBXq U9dmF7/Hc0t1J2FQB3bKi1YhSQqyN89/r9hajZRsIpx3Mqddj7Sz7xhN5rlGXdYMrR aNJTn49mqkhRzSKWlbSw7FXf+R3y6EXdX63if6qQ= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:45:59 +0300 Message-Id: <20220524114610.41848-9-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 08/19] py: MappedFrameBuffer: Add type hints & docs X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a few type hints and (minimal) docs to MappedFrameBuffer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/utils/MappedFrameBuffer.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/py/libcamera/utils/MappedFrameBuffer.py b/src/py/libcamera/utils/MappedFrameBuffer.py index e7dd16ec..fc2726b6 100644 --- a/src/py/libcamera/utils/MappedFrameBuffer.py +++ b/src/py/libcamera/utils/MappedFrameBuffer.py @@ -1,8 +1,14 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (C) 2022, Tomi Valkeinen +import libcamera +from typing import Tuple + class MappedFrameBuffer: - def __init__(self, fb): + """ + Provides memoryviews for the FrameBuffer's planes + """ + def __init__(self, fb: libcamera.FrameBuffer): self.__fb = fb def __enter__(self): @@ -70,5 +76,6 @@ class MappedFrameBuffer: mm.close() @property - def planes(self): + def planes(self) -> Tuple[memoryview, ...]: + """memoryviews for the planes""" return self.__planes From patchwork Tue May 24 11:46:00 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16016 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 63508C3271 for ; Tue, 24 May 2022 11:46:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 21F0C65671; Tue, 24 May 2022 13:46:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392805; bh=98Tl2l7nSb5jbMU+doXcvRwG06fg31CD540El0/whyg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=lIso8v+Ahc/dKVfshL90KvU5c4dPS7CRd7p8inFZbcGoPqace3Bz876vfB69+o6qI BK3cKf6zrrMXrvoe78ZquhmM7Y13hhBC1oEj4KvFa/SStQ5FNSBObAibDzTerlWtFK 6bjKA+ltWJMaknlhe/v59e0ay1oY9rhwxzApmKCpDtJ/nmxR7dlrwoigwm48t1foNW A6zh7f3sohCy+CNiqpZqYQnxXGIF+CdDhmA29HUQUXyknENP0Y9Bu3KSmOU7vJwWSq kGd874GH9A4M6a7day1/P+7JFYoqs8axsewac8j4BY7oyegKDyNyE36f3BTsyp0HRj DJtHoI+c7RUJw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C2BF965662 for ; Tue, 24 May 2022 13:46:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="W3nxvUBT"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 281E7A16; Tue, 24 May 2022 13:46:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392794; bh=98Tl2l7nSb5jbMU+doXcvRwG06fg31CD540El0/whyg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=W3nxvUBToiq2MzSr9nwhdofq3TckokrB7dQvv1rWbeRnvYqwpd8ePYU7721XYjOGM JvztgWbAq6OrIhKj+LOfcZ5nUBPTIs816Wp4cbwRCpY/GYmM8GCIfA/Zvrsq0bHGjl JQ2qDepBAR6f+zR2rmo2XZCuxbGBwlF8anipgyUY= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:00 +0300 Message-Id: <20220524114610.41848-10-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 09/19] py: cam: Drop WA_ShowWithoutActivating X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Drop WA_ShowWithoutActivating from the Qt renderers. I added the flag during development phase as I didn't want the window to take the focus, but it should be removed now. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_qt.py | 1 - src/py/cam/cam_qtgl.py | 1 - 2 files changed, 2 deletions(-) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 70bdb7bb..03096920 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -187,7 +187,6 @@ class QtRenderer: for ctx in self.contexts: for stream in ctx['streams']: window = MainWindow(ctx, stream) - window.setAttribute(QtCore.Qt.WA_ShowWithoutActivating) window.show() windows.append(window) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index 3fb7dde3..c9e367a2 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -137,7 +137,6 @@ class QtRenderer: self.app = QtWidgets.QApplication([]) window = MainWindow(self.state) - window.setAttribute(QtCore.Qt.WA_ShowWithoutActivating) window.show() self.window = window From patchwork Tue May 24 11:46:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16015 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7F4C0C3270 for ; Tue, 24 May 2022 11:46:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E5F96568A; Tue, 24 May 2022 13:46:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392804; bh=LymWA+q6FABhdVSTivdJLURAgL5L49nG23E9Mlc/HeI=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=xFQA5I+V7HbZq4n7eOZ5Py+F1MJ4n7TO3ceZOoplpErIUdrRQugsrgJGaW+8ilx7t /NSYZ0A1XJ8ePMOurEEiyWzdEKT1EQW0WdAXT4g8u6Fb+WmD7OeMCel+ldkV6DAB6u NL3yqnn8jJVwAZMziK6BE7paxv0WX2aNx6T+paeFQKFhelpLTz2vz6gdvuCMmfWNEN WB+G3IeNME2UW8dj9kLP6M8facal1i0rYN+auBVVTJFrjmEmD1eWodbAgdYqjjLdkI nzFSPfhYYAGH2xM2ZjzRmiDOR3alHZeoOIlzW5NTA2yjVZ7WTWLz3IrJMSw7vqKguo NH89SDOnXRMgQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4A8F865674 for ; Tue, 24 May 2022 13:46:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="iLZ9g1Xw"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A87F759D; Tue, 24 May 2022 13:46:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392795; bh=LymWA+q6FABhdVSTivdJLURAgL5L49nG23E9Mlc/HeI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iLZ9g1Xw6rumbNJfKV3CUwfPz3VYx2LJITEemYnohkowYc77zRlMF34XyhCWJVMBX 66BhQRASwdU1UKSaXCQi0kAbs8N8zsL9r/2eRKKvVFcvwqgBma0ym85asAHoavA+t9 gBQ6lQeNRLT6nGxXDVOsAhtj+D1mA9iXQb/aqbx4= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:01 +0300 Message-Id: <20220524114610.41848-11-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 10/19] py: examples: Add simple-capture.py X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add an example to showcase the more-or-less minimal capture case. Signed-off-by: Tomi Valkeinen --- src/py/examples/simple-capture.py | 153 ++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100755 src/py/examples/simple-capture.py diff --git a/src/py/examples/simple-capture.py b/src/py/examples/simple-capture.py new file mode 100755 index 00000000..45df9e7a --- /dev/null +++ b/src/py/examples/simple-capture.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2022, Tomi Valkeinen + +# A simple minimal capture example showing: +# - How to setup the camera +# - Capture frames in a blocking manner +# - Memory map the frames +# - How to stop the camera + +import argparse +import binascii +import libcamera as libcam +import libcamera.utils +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--camera', type=str, default='1', + help='Camera index number (starting from 1) or part of the name') + parser.add_argument('-f', '--format', type=str, help='Pixel format') + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') + args = parser.parse_args() + + cm = libcam.CameraManager.singleton() + + try: + if args.camera.isnumeric(): + cam_idx = int(args.camera) + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) + else: + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) + except Exception: + print(f'Failed to find camera "{args.camera}"') + return -1 + + # Acquire the camera for our use + + ret = cam.acquire() + assert ret == 0 + + # Configure the camera + + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) + + streamconfig = camconfig.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + streamconfig.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + streamconfig.size = libcam.Size(w, h) + + ret = cam.configure(camconfig) + assert ret == 0 + + stream = streamconfig.stream + + # Allocate the buffers for capture + + allocator = libcam.FrameBufferAllocator(cam) + ret = allocator.allocate(stream) + assert ret > 0 + + num_bufs = len(allocator.buffers(stream)) + + print(f'Capturing {num_bufs} frames with {streamconfig}') + + # Create the requests and assign a buffer for each request + + reqs = [] + for i in range(num_bufs): + # Use the buffer index as the cookie + req = cam.create_request(i) + + buffer = allocator.buffers(stream)[i] + ret = req.add_buffer(stream, buffer) + assert ret == 0 + + reqs.append(req) + + # Start the camera + + ret = cam.start() + assert ret == 0 + + # Queue the requests to the camera + + for req in reqs: + ret = cam.queue_request(req) + assert ret == 0 + + # Wait until the requests are finished + + reqs = [] + + while True: + # cm.read_event() blocks until there is an event + cm.read_event() + + # Get all the ready requests + ready_reqs = cm.get_ready_requests() + + reqs += ready_reqs + + if len(reqs) >= num_bufs: + break + + # Process the captured frames + + for i, req in enumerate(reqs): + assert i == req.cookie + + buffers = req.buffers + + # A ready Request could contain multiple buffers if multiple streams + # were being used. Here we know we only have a single stream, + # and we use next(iter()) to get the first and only buffer. + + assert len(buffers) == 1 + + stream, fb = next(iter(buffers.items())) + + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate + # the crc for each plane. + + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + crcs = [binascii.crc32(p) for p in mfb.planes] + + meta = fb.metadata + + print('seq {}, bytes {}, CRCs {}' + .format(meta.sequence, meta.bytesused, crcs)) + + # Stop the camera + + ret = cam.stop() + assert ret == 0 + + # Release the camera + + ret = cam.release() + assert ret == 0 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From patchwork Tue May 24 11:46:02 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16018 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id AE6E3C3273 for ; Tue, 24 May 2022 11:46:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1D60C6566F; Tue, 24 May 2022 13:46:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392811; bh=Zoo4eYDvJTk4uOcxTOPJMxZiJpWy8yZgBMPCKB9S/PE=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=DjhZg++gWR7OFQktRleQnVPQIHFqVawnc3/H5LunufM/U83zWmUp7NfW6mbqg7VOq EJekgtxKP7mBl+7gV5uJlVA20aoVw6w7ffRPmGlUhegyTi8+iW8wI69C3yccFANGyF 0dpSvlcHI2m75gbvqGyc1ZjgdsheXYM0YgyIN8bnlsJ3wYVWRjC/P8hsk+ot5P/7x7 CMCjcmC41IJ7cW8VGtc+IxqSWP8bOoem3OGbJS+E1vez3RWlvBsLVSVCEl2oV8+ohR ciY9Ytpfz7EfizvGqHyqsLSky3ywzPvVni9X8vHPb5/7mUxp8I8SbHmXocnrgCryfK HH7VsFjSWmbFw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C4E8760422 for ; Tue, 24 May 2022 13:46:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tJtEyB7m"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 33CF78DC; Tue, 24 May 2022 13:46:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392795; bh=Zoo4eYDvJTk4uOcxTOPJMxZiJpWy8yZgBMPCKB9S/PE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tJtEyB7mBoPwYSaxw8FURqf0v6Wuh5QhalDy48UXzYdXuqRIab78KsZSNwL0CdSKL RmRzLdjf7pnchU6DycptHT4PGmximM2iz9MHf9i6Q9+cH5Wf5YRUUYY2M1kmQnWAU7 sod2vTaxeCv1CVL7dkWNcsnkfavoYWaiJYMOdXA8= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:02 +0300 Message-Id: <20220524114610.41848-12-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 11/19] py: examples: Add simple-continuous-capture.py X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a slightly more complex, and I think a more realistic, example, where the script reacts to events and re-queues the buffers. Signed-off-by: Tomi Valkeinen --- src/py/examples/simple-continuous-capture.py | 179 +++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100755 src/py/examples/simple-continuous-capture.py diff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py new file mode 100755 index 00000000..05fa2ada --- /dev/null +++ b/src/py/examples/simple-continuous-capture.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2022, Tomi Valkeinen + +# A simple minimal capture example showing: +# - How to setup the camera +# - Capture frames using events +# - How to requeue requests +# - Memory map the frames +# - How to stop the camera + +import argparse +import binascii +import libcamera as libcam +import libcamera.utils +import selectors +import sys + + +def handle_camera_event(cm, cam): + # cm.read_event() will not block here, as we know there is an event to read. + # We have to read the event to clear it. + + cm.read_event() + + reqs = cm.get_ready_requests() + + # Process the captured frames + + for req in reqs: + buffers = req.buffers + + assert len(buffers) == 1 + + # A ready Request could contain multiple buffers if multiple streams + # were being used. Here we know we only have a single stream, + # and we use next(iter()) to get the first and only buffer. + + stream, fb = next(iter(buffers.items())) + + # Use MappedFrameBuffer to access the pixel data with CPU. We calculate + # the crc for each plane. + + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + crcs = [binascii.crc32(p) for p in mfb.planes] + + meta = fb.metadata + + print('buf {}, seq {}, bytes {}, CRCs {}' + .format(req.cookie, meta.sequence, meta.bytesused, crcs)) + + # We want to re-queue the buffer we just handled. Instead of creating + # a new Request, we re-use the old one. We need to call req.reuse() + # to re-initialize the Request before queuing. + + req.reuse() + cam.queue_request(req) + + return True + + +def handle_key_envet(fileob): + sys.stdin.readline() + print("Exiting...") + return False + + +def capture(cm, cam, reqs): + # Queue the requests to the camera + + for req in reqs: + ret = cam.queue_request(req) + assert ret == 0 + + # Use Selector to wait for events from the camera and from the keyboard + + sel = selectors.DefaultSelector() + sel.register(cm.efd, selectors.EVENT_READ, lambda fd: handle_camera_event(cm, cam)) + sel.register(sys.stdin, selectors.EVENT_READ, handle_key_envet) + + reqs = [] + running = True + + while running: + events = sel.select() + for key, mask in events: + running = key.data(key.fileobj) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--camera', type=str, default='1', + help='Camera index number (starting from 1) or part of the name') + parser.add_argument('-f', '--format', type=str, help='Pixel format') + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') + args = parser.parse_args() + + cm = libcam.CameraManager.singleton() + + try: + if args.camera.isnumeric(): + cam_idx = int(args.camera) + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) + else: + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) + except Exception: + print(f'Failed to find camera "{args.camera}"') + return -1 + + # Acquire the camera for our use + + ret = cam.acquire() + assert ret == 0 + + # Configure the camera + + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) + + streamconfig = camconfig.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + streamconfig.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + streamconfig.size = libcam.Size(w, h) + + ret = cam.configure(camconfig) + assert ret == 0 + + stream = streamconfig.stream + + # Allocate the buffers for capture + + allocator = libcam.FrameBufferAllocator(cam) + ret = allocator.allocate(stream) + assert ret > 0 + + num_bufs = len(allocator.buffers(stream)) + + print(f'Capturing {streamconfig} with {num_bufs} buffers from {cam.id}') + + # Create the requests and assign a buffer for each request + + reqs = [] + for i in range(num_bufs): + # Use the buffer index as the "cookie" + req = cam.create_request(i) + + buffer = allocator.buffers(stream)[i] + ret = req.add_buffer(stream, buffer) + assert ret == 0 + + reqs.append(req) + + # Start the camera + + ret = cam.start() + assert ret == 0 + + capture(cm, cam, reqs) + + # Stop the camera + + ret = cam.stop() + assert ret == 0 + + # Release the camera + + ret = cam.release() + assert ret == 0 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From patchwork Tue May 24 11:46:03 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16017 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5B1E7C3272 for ; Tue, 24 May 2022 11:46:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9F85665672; Tue, 24 May 2022 13:46:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392810; bh=Ois2QOxtdbz47T73qANKv+lKornBN8Dys4/BCUT8ft4=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=tIv3sbjTducq2J7WNTJrvX/d4lSMTdCEEp7Dvj5lzyx+/hmfNOPT6P0pYkTVX0OyO sjW7SvcelJrDYWa0O8NAUeTjwbTMWSHbGX5qV9ScxnEfRN9UnO829P+sP+273KD+K2 /Pryyza6FYSo4KbkELlGM6hkZxx8KOJHBvXB8920gmW/W6flEMkThoanNaqeFkYqTy G9YAQnjJ6FnoSC6VZGl63FFWaB3AdoTSavSOqhOJ7S6dpaw3kMxmCOw3fiPM8YIFkz WX57LIZnctEcUrW2IdeC2dHBWI6B01PaYhDjVH7NXQ53kMbg9bBGdWMpm67jm/mnq2 LmJWfNuNu78Pg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 490DD65668 for ; Tue, 24 May 2022 13:46:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="G8Q1EeBL"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B30194A8; Tue, 24 May 2022 13:46:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392796; bh=Ois2QOxtdbz47T73qANKv+lKornBN8Dys4/BCUT8ft4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G8Q1EeBL134x/LAEQq+6wnbmuoMAOl7zFcvZ8EIieyXHdmTBwQgQdrUekiNAdyYWy He8vRcH5AqLc0GjXzZsIudxes5kp19n3v7mdhx/252ZLY/tBuIaBxHma8K4icSgF0T 4iGVmC4wfDZ/tXamYS3LDHA6/yLUehnV27Dtqx2s= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:03 +0300 Message-Id: <20220524114610.41848-13-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 12/19] py: examples: Add itest.py X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a small tool which gives the user an IPython based interactive shell with libcamera capturing in the background. Can be used to study and test small things with libcamera. Signed-off-by: Tomi Valkeinen --- src/py/examples/itest.py | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100755 src/py/examples/itest.py diff --git a/src/py/examples/itest.py b/src/py/examples/itest.py new file mode 100755 index 00000000..01e020e2 --- /dev/null +++ b/src/py/examples/itest.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2022, Tomi Valkeinen + +# This sets up a single camera, starts a capture loop in a background thread, +# and starts an IPython shell which can be used to study and test libcamera. +# +# Note: This is not an example as such, but more of a developer tool to try +# out things. + +import argparse +import IPython +import libcamera as libcam +import selectors +import sys +import threading + + +def handle_camera_event(ctx): + cm = ctx['cm'] + cam = ctx['cam'] + + # cm.read_event() will not block here, as we know there is an event to read. + # We have to read the event to clear it. + + cm.read_event() + + reqs = cm.get_ready_requests() + + assert len(reqs) > 0 + + # Process the captured frames + + for req in reqs: + buffers = req.buffers + + assert len(buffers) == 1 + + # We want to re-queue the buffer we just handled. Instead of creating + # a new Request, we re-use the old one. We need to call req.reuse() + # to re-initialize the Request before queuing. + + req.reuse() + cam.queue_request(req) + + scope = ctx['scope'] + + if 'num_frames' not in scope: + scope['num_frames'] = 0 + + scope['num_frames'] += 1 + + +def capture(ctx): + cm = ctx['cm'] + cam = ctx['cam'] + reqs = ctx['reqs'] + + # Queue the requests to the camera + + for req in reqs: + ret = cam.queue_request(req) + assert ret == 0 + + # Use Selector to wait for events from the camera and from the keyboard + + sel = selectors.DefaultSelector() + sel.register(cm.efd, selectors.EVENT_READ, lambda fd: handle_camera_event(ctx)) + + reqs = [] + + while ctx['running']: + events = sel.select() + for key, mask in events: + key.data(key.fileobj) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--camera', type=str, default='1', + help='Camera index number (starting from 1) or part of the name') + parser.add_argument('-f', '--format', type=str, help='Pixel format') + parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') + args = parser.parse_args() + + cm = libcam.CameraManager.singleton() + + try: + if args.camera.isnumeric(): + cam_idx = int(args.camera) + cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) + else: + cam = next((cam for cam in cm.cameras if args.camera in cam.id)) + except Exception: + print(f'Failed to find camera "{args.camera}"') + return -1 + + # Acquire the camera for our use + + ret = cam.acquire() + assert ret == 0 + + # Configure the camera + + camconfig = cam.generate_configuration([libcam.StreamRole.StillCapture]) + + streamconfig = camconfig.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + streamconfig.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + streamconfig.size = libcam.Size(w, h) + + ret = cam.configure(camconfig) + assert ret == 0 + + stream = streamconfig.stream + + # Allocate the buffers for capture + + allocator = libcam.FrameBufferAllocator(cam) + ret = allocator.allocate(stream) + assert ret > 0 + + num_bufs = len(allocator.buffers(stream)) + + print(f'Capturing {streamconfig} with {num_bufs} buffers from {cam.id}') + + # Create the requests and assign a buffer for each request + + reqs = [] + for i in range(num_bufs): + # Use the buffer index as the "cookie" + req = cam.create_request(i) + + buffer = allocator.buffers(stream)[i] + ret = req.add_buffer(stream, buffer) + assert ret == 0 + + reqs.append(req) + + # Start the camera + + ret = cam.start() + assert ret == 0 + + # Create a scope for the IPython shell + scope = { + 'cm': cm, + 'cam': cam, + 'libcam': libcam, + } + + # Create a simple context shared between the background thread and the main + # thread. + ctx = { + 'running': True, + 'cm': cm, + 'cam': cam, + 'reqs': reqs, + 'scope': scope, + } + + # Note that "In CPython, due to the Global Interpreter Lock, only one thread + # can execute Python code at once". We rely on that here, which is not very + # nice. I am sure an fd-based polling loop could be integrated with IPython, + # somehow, which would allow us to drop the threading. + + t = threading.Thread(target=capture, args=(ctx,)) + t.start() + + IPython.embed(banner1="", exit_msg="", confirm_exit=False, user_ns=scope) + + print("Exiting...") + + ctx["running"] = False + t.join() + + # Stop the camera + + ret = cam.stop() + assert ret == 0 + + # Release the camera + + ret = cam.release() + assert ret == 0 + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From patchwork Tue May 24 11:46:04 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16020 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 75B82C3274 for ; Tue, 24 May 2022 11:46:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0FF1F6569F; Tue, 24 May 2022 13:46:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392812; bh=A0jcK9k2ooiMmYrbeoitDMJX2pKECCk+9tbqDLkqz6Q=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=uszj2TpLUQgBj1tKZpiC8wqsjbzg/0PYTfIM0cHassYt5MW2q3OSbXmtdubaRN2KV 8um90WKSQBFsys2tV6JoLkrjH8R79RBbwo6EVTR6Wy12eV5ZC9tJvVxyjN5UKtXEkG 7Ai6odHLoLxUxfAkor6sKY5BTs+S1+IVARsSypmISbEtteNR2soSRFidqke2avfenL 86M/bX/XrIuTDDV9oosylnzrtpt63vGz441QKgm3hEWKCQp5IktxnUIR6rVt+QjgTu OfPSe/UieOb2ppDWUkY59gWLJZed1iTcAKuQuphQUieNWGxkSL6hEWCspl6ACNYtCh 6DuB8MJh3WTIw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DE50E65676 for ; Tue, 24 May 2022 13:46:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vRgoqxob"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CA8E59D; Tue, 24 May 2022 13:46:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392796; bh=A0jcK9k2ooiMmYrbeoitDMJX2pKECCk+9tbqDLkqz6Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vRgoqxob49QDInd9um1hTasJXr2zHRp8W6mQQihWbtrbN13XhhLD+IKOh7GH5MKLx b1klp4cAEczX10rCWXn1Fhl40/3uMazy1Zx3eSQaNvSiKY/M3Hh7RcxZlDO/IEKB2D CMtolC+z72e+iOgLxOZXUTmAhqfN9GemQgWsz1V0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:04 +0300 Message-Id: <20220524114610.41848-14-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 13/19] py: cam: Convert ctx and state to classes X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Tomi Valkeinen Convert ctx and state dicts to classes. No functional changes. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 585 +++++++++++++++++++++-------------------- src/py/cam/cam_kms.py | 12 +- src/py/cam/cam_null.py | 8 +- src/py/cam/cam_qt.py | 16 +- src/py/cam/cam_qtgl.py | 22 +- 5 files changed, 327 insertions(+), 316 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 64f67e86..f6e8232c 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -6,6 +6,7 @@ # \todo Convert ctx and state dicts to proper classes, and move relevant # functions to those classes. +from typing import Any import argparse import binascii import libcamera as libcam @@ -14,379 +15,400 @@ import sys import traceback -class CustomAction(argparse.Action): - def __init__(self, option_strings, dest, **kwargs): - super().__init__(option_strings, dest, default={}, **kwargs) +class CameraContext: + camera: libcam.Camera + id: str + idx: int - def __call__(self, parser, namespace, values, option_string=None): - if len(namespace.camera) == 0: - print(f'Option {option_string} requires a --camera context') - sys.exit(-1) + opt_stream: str + opt_strict_formats: bool + opt_crc: bool + opt_metadata: bool + opt_save_frames: bool + opt_capture: int - if self.type == bool: - values = True + stream_names: dict[libcam.Stream, str] + streams: list[libcam.Stream] + allocator: libcam.FrameBufferAllocator + requests: list[libcam.Request] + reqs_queued: int + reqs_completed: int + last: int = 0 + fps: float - current = namespace.camera[-1] + def __init__(self, camera, idx): + self.camera = camera + self.idx = idx + self.id = 'cam' + str(idx) + self.reqs_queued = 0 + self.reqs_completed = 0 - data = getattr(namespace, self.dest) + def do_cmd_list_props(self): + camera = self.camera - if self.nargs == '+': - if current not in data: - data[current] = [] + print('Properties for', self.id) - data[current] += values - else: - data[current] = values + for name, prop in camera.properties.items(): + print('\t{}: {}'.format(name, prop)) + def do_cmd_list_controls(self): + camera = self.camera -def do_cmd_list(cm): - print('Available cameras:') - - for idx, c in enumerate(cm.cameras): - print(f'{idx + 1}: {c.id}') - - -def do_cmd_list_props(ctx): - camera = ctx['camera'] - - print('Properties for', ctx['id']) - - for name, prop in camera.properties.items(): - print('\t{}: {}'.format(name, prop)) - - -def do_cmd_list_controls(ctx): - camera = ctx['camera'] - - print('Controls for', ctx['id']) - - for name, prop in camera.controls.items(): - print('\t{}: {}'.format(name, prop)) + print('Controls for', self.id) + for name, prop in camera.controls.items(): + print('\t{}: {}'.format(name, prop)) -def do_cmd_info(ctx): - camera = ctx['camera'] + def do_cmd_info(self): + camera = self.camera - print('Stream info for', ctx['id']) + print('Stream info for', self.id) - roles = [libcam.StreamRole.Viewfinder] + roles = [libcam.StreamRole.Viewfinder] - camconfig = camera.generate_configuration(roles) - if camconfig is None: - raise Exception('Generating config failed') + camconfig = camera.generate_configuration(roles) + if camconfig is None: + raise Exception('Generating config failed') - for i, stream_config in enumerate(camconfig): - print('\t{}: {}'.format(i, stream_config)) + for i, stream_config in enumerate(camconfig): + print('\t{}: {}'.format(i, stream_config)) - formats = stream_config.formats - for fmt in formats.pixel_formats: - print('\t * Pixelformat:', fmt, formats.range(fmt)) + formats = stream_config.formats + for fmt in formats.pixel_formats: + print('\t * Pixelformat:', fmt, formats.range(fmt)) - for size in formats.sizes(fmt): - print('\t -', size) + for size in formats.sizes(fmt): + print('\t -', size) + def acquire(self): + camera = self.camera -def acquire(ctx): - camera = ctx['camera'] + camera.acquire() - camera.acquire() + def release(self): + camera = self.camera + camera.release() -def release(ctx): - camera = ctx['camera'] + def __parse_streams(self): + streams = [] - camera.release() + for stream_desc in self.opt_stream: + stream_opts: dict[str, Any] + stream_opts = {'role': libcam.StreamRole.Viewfinder} + for stream_opt in stream_desc.split(','): + if stream_opt == 0: + continue -def parse_streams(ctx): - streams = [] - - for stream_desc in ctx['opt-stream']: - stream_opts = {'role': libcam.StreamRole.Viewfinder} - - for stream_opt in stream_desc.split(','): - if stream_opt == 0: - continue - - arr = stream_opt.split('=') - if len(arr) != 2: - print('Bad stream option', stream_opt) - sys.exit(-1) - - key = arr[0] - value = arr[1] - - if key in ['width', 'height']: - value = int(value) - elif key == 'role': - rolemap = { - 'still': libcam.StreamRole.StillCapture, - 'raw': libcam.StreamRole.Raw, - 'video': libcam.StreamRole.VideoRecording, - 'viewfinder': libcam.StreamRole.Viewfinder, - } - - role = rolemap.get(value.lower(), None) + arr = stream_opt.split('=') + if len(arr) != 2: + print('Bad stream option', stream_opt) + sys.exit(-1) - if role is None: - print('Bad stream role', value) + key = arr[0] + value = arr[1] + + if key in ['width', 'height']: + value = int(value) + elif key == 'role': + rolemap = { + 'still': libcam.StreamRole.StillCapture, + 'raw': libcam.StreamRole.Raw, + 'video': libcam.StreamRole.VideoRecording, + 'viewfinder': libcam.StreamRole.Viewfinder, + } + + role = rolemap.get(value.lower(), None) + + if role is None: + print('Bad stream role', value) + sys.exit(-1) + + value = role + elif key == 'pixelformat': + pass + else: + print('Bad stream option key', key) sys.exit(-1) - value = role - elif key == 'pixelformat': - pass - else: - print('Bad stream option key', key) - sys.exit(-1) + stream_opts[key] = value - stream_opts[key] = value + streams.append(stream_opts) - streams.append(stream_opts) + return streams - return streams + def configure(self): + camera = self.camera + streams = self.__parse_streams() -def configure(ctx): - camera = ctx['camera'] + roles = [opts['role'] for opts in streams] - streams = parse_streams(ctx) + camconfig = camera.generate_configuration(roles) + if camconfig is None: + raise Exception('Generating config failed') - roles = [opts['role'] for opts in streams] + for idx, stream_opts in enumerate(streams): + stream_config = camconfig.at(idx) - camconfig = camera.generate_configuration(roles) - if camconfig is None: - raise Exception('Generating config failed') + if 'width' in stream_opts: + stream_config.size.width = stream_opts['width'] - for idx, stream_opts in enumerate(streams): - stream_config = camconfig.at(idx) + if 'height' in stream_opts: + stream_config.size.height = stream_opts['height'] - if 'width' in stream_opts: - stream_config.size.width = stream_opts['width'] + if 'pixelformat' in stream_opts: + stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) - if 'height' in stream_opts: - stream_config.size.height = stream_opts['height'] + stat = camconfig.validate() - if 'pixelformat' in stream_opts: - stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) + if stat == libcam.CameraConfiguration.Status.Invalid: + print('Camera configuration invalid') + exit(-1) + elif stat == libcam.CameraConfiguration.Status.Adjusted: + if self.opt_strict_formats: + print('Adjusting camera configuration disallowed by --strict-formats argument') + exit(-1) - stat = camconfig.validate() + print('Camera configuration adjusted') - if stat == libcam.CameraConfiguration.Status.Invalid: - print('Camera configuration invalid') - exit(-1) - elif stat == libcam.CameraConfiguration.Status.Adjusted: - if ctx['opt-strict-formats']: - print('Adjusting camera configuration disallowed by --strict-formats argument') - exit(-1) + r = camera.configure(camconfig) + if r != 0: + raise Exception('Configure failed') - print('Camera configuration adjusted') + self.stream_names = {} + self.streams = [] - r = camera.configure(camconfig) - if r != 0: - raise Exception('Configure failed') + for idx, stream_config in enumerate(camconfig): + stream = stream_config.stream + self.streams.append(stream) + self.stream_names[stream] = 'stream' + str(idx) + print('{}-{}: stream config {}'.format(self.id, self.stream_names[stream], stream.configuration)) - ctx['stream-names'] = {} - ctx['streams'] = [] + def alloc_buffers(self): + camera = self.camera - for idx, stream_config in enumerate(camconfig): - stream = stream_config.stream - ctx['streams'].append(stream) - ctx['stream-names'][stream] = 'stream' + str(idx) - print('{}-{}: stream config {}'.format(ctx['id'], ctx['stream-names'][stream], stream.configuration)) + allocator = libcam.FrameBufferAllocator(camera) + for stream in self.streams: + ret = allocator.allocate(stream) + if ret < 0: + print('Cannot allocate buffers') + exit(-1) -def alloc_buffers(ctx): - camera = ctx['camera'] + allocated = len(allocator.buffers(stream)) - allocator = libcam.FrameBufferAllocator(camera) + print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated)) - for idx, stream in enumerate(ctx['streams']): - ret = allocator.allocate(stream) - if ret < 0: - print('Cannot allocate buffers') - exit(-1) + self.allocator = allocator - allocated = len(allocator.buffers(stream)) + def create_requests(self): + camera = self.camera - print('{}-{}: Allocated {} buffers'.format(ctx['id'], ctx['stream-names'][stream], allocated)) + self.requests = [] - ctx['allocator'] = allocator + # Identify the stream with the least number of buffers + num_bufs = min([len(self.allocator.buffers(stream)) for stream in self.streams]) + requests = [] -def create_requests(ctx): - camera = ctx['camera'] + for buf_num in range(num_bufs): + request = camera.create_request(self.idx) - ctx['requests'] = [] + if request is None: + print('Can not create request') + exit(-1) - # Identify the stream with the least number of buffers - num_bufs = min([len(ctx['allocator'].buffers(stream)) for stream in ctx['streams']]) + for stream in self.streams: + buffers = self.allocator.buffers(stream) + buffer = buffers[buf_num] - requests = [] + ret = request.add_buffer(stream, buffer) + if ret < 0: + print('Can not set buffer for request') + exit(-1) - for buf_num in range(num_bufs): - request = camera.create_request(ctx['idx']) + requests.append(request) - if request is None: - print('Can not create request') - exit(-1) + self.requests = requests - for stream in ctx['streams']: - buffers = ctx['allocator'].buffers(stream) - buffer = buffers[buf_num] + def start(self): + camera = self.camera - ret = request.add_buffer(stream, buffer) - if ret < 0: - print('Can not set buffer for request') - exit(-1) + camera.start() - requests.append(request) + def stop(self): + camera = self.camera - ctx['requests'] = requests + camera.stop() + def queue_requests(self): + camera = self.camera -def start(ctx): - camera = ctx['camera'] + for request in self.requests: + camera.queue_request(request) + self.reqs_queued += 1 - camera.start() + del self.requests -def stop(ctx): - camera = ctx['camera'] +class CaptureState: + cm: libcam.CameraManager + contexts: list[CameraContext] + renderer: Any - camera.stop() + def __init__(self, cm, contexts): + self.cm = cm + self.contexts = contexts + # Called from renderer when there is a libcamera event + def event_handler(self): + try: + cm = self.cm + contexts = self.contexts -def queue_requests(ctx): - camera = ctx['camera'] + cm.read_event() - for request in ctx['requests']: - camera.queue_request(request) - ctx['reqs-queued'] += 1 + reqs = cm.get_ready_requests() - del ctx['requests'] + for req in reqs: + ctx = next(ctx for ctx in contexts if ctx.idx == req.cookie) + self.__request_handler(ctx, req) + running = any(ctx.reqs_completed < ctx.opt_capture for ctx in contexts) + return running + except Exception: + traceback.print_exc() + return False -def capture_init(contexts): - for ctx in contexts: - acquire(ctx) + def __request_handler(self, ctx, req): + if req.status != libcam.Request.Status.Complete: + raise Exception('{}: Request failed: {}'.format(ctx.id, req.status)) - for ctx in contexts: - configure(ctx) + buffers = req.buffers - for ctx in contexts: - alloc_buffers(ctx) + # Compute the frame rate. The timestamp is arbitrarily retrieved from + # the first buffer, as all buffers should have matching timestamps. + ts = buffers[next(iter(buffers))].metadata.timestamp + last = ctx.last + fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 + ctx.last = ts + ctx.fps = fps - for ctx in contexts: - create_requests(ctx) + for stream, fb in buffers.items(): + stream_name = ctx.stream_names[stream] + crcs = [] + if ctx.opt_crc: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + plane_crcs = [binascii.crc32(p) for p in mfb.planes] + crcs.append(plane_crcs) -def capture_start(contexts): - for ctx in contexts: - start(ctx) + meta = fb.metadata - for ctx in contexts: - queue_requests(ctx) + print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' + .format(ts / 1000000000, fps, + ctx.id, stream_name, + meta.sequence, meta.bytesused, + crcs)) + if ctx.opt_metadata: + reqmeta = req.metadata + for ctrl, val in reqmeta.items(): + print(f'\t{ctrl} = {val}') -# Called from renderer when there is a libcamera event -def event_handler(state): - try: - cm = state['cm'] - contexts = state['contexts'] + if ctx.opt_save_frames: + with libcamera.utils.MappedFrameBuffer(fb) as mfb: + filename = 'frame-{}-{}-{}.data'.format(ctx.id, stream_name, ctx.reqs_completed) + with open(filename, 'wb') as f: + for p in mfb.planes: + f.write(p) - cm.read_event() + self.renderer.request_handler(ctx, req) - reqs = cm.get_ready_requests() + ctx.reqs_completed += 1 - for req in reqs: - ctx = next(ctx for ctx in contexts if ctx['idx'] == req.cookie) - request_handler(state, ctx, req) + # Called from renderer when it has finished with a request + def request_processed(self, ctx, req): + camera = ctx.camera - running = any(ctx['reqs-completed'] < ctx['opt-capture'] for ctx in contexts) - return running - except Exception: - traceback.print_exc() - return False + if ctx.reqs_queued < ctx.opt_capture: + req.reuse() + camera.queue_request(req) + ctx.reqs_queued += 1 + def __capture_init(self): + for ctx in self.contexts: + ctx.acquire() -def request_handler(state, ctx, req): - if req.status != libcam.Request.Status.Complete: - raise Exception('{}: Request failed: {}'.format(ctx['id'], req.status)) + for ctx in self.contexts: + ctx.configure() - buffers = req.buffers + for ctx in self.contexts: + ctx.alloc_buffers() - # Compute the frame rate. The timestamp is arbitrarily retrieved from - # the first buffer, as all buffers should have matching timestamps. - ts = buffers[next(iter(buffers))].metadata.timestamp - last = ctx.get('last', 0) - fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 - ctx['last'] = ts - ctx['fps'] = fps + for ctx in self.contexts: + ctx.create_requests() - for stream, fb in buffers.items(): - stream_name = ctx['stream-names'][stream] + def __capture_start(self): + for ctx in self.contexts: + ctx.start() - crcs = [] - if ctx['opt-crc']: - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - plane_crcs = [binascii.crc32(p) for p in mfb.planes] - crcs.append(plane_crcs) + for ctx in self.contexts: + ctx.queue_requests() - meta = fb.metadata + def __capture_deinit(self): + for ctx in self.contexts: + ctx.stop() - print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' - .format(ts / 1000000000, fps, - ctx['id'], stream_name, - meta.sequence, meta.bytesused, - crcs)) + for ctx in self.contexts: + ctx.release() - if ctx['opt-metadata']: - reqmeta = req.metadata - for ctrl, val in reqmeta.items(): - print(f'\t{ctrl} = {val}') + def do_cmd_capture(self): + self.__capture_init() - if ctx['opt-save-frames']: - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - filename = 'frame-{}-{}-{}.data'.format(ctx['id'], stream_name, ctx['reqs-completed']) - with open(filename, 'wb') as f: - for p in mfb.planes: - f.write(p) + renderer = self.renderer - state['renderer'].request_handler(ctx, req) + renderer.setup() - ctx['reqs-completed'] += 1 + self.__capture_start() + renderer.run() -# Called from renderer when it has finished with a request -def request_prcessed(ctx, req): - camera = ctx['camera'] + self.__capture_deinit() - if ctx['reqs-queued'] < ctx['opt-capture']: - req.reuse() - camera.queue_request(req) - ctx['reqs-queued'] += 1 +class CustomAction(argparse.Action): + def __init__(self, option_strings, dest, **kwargs): + super().__init__(option_strings, dest, default={}, **kwargs) -def capture_deinit(contexts): - for ctx in contexts: - stop(ctx) + def __call__(self, parser, namespace, values, option_string=None): + if len(namespace.camera) == 0: + print(f'Option {option_string} requires a --camera context') + sys.exit(-1) - for ctx in contexts: - release(ctx) + if self.type == bool: + values = True + current = namespace.camera[-1] -def do_cmd_capture(state): - capture_init(state['contexts']) + data = getattr(namespace, self.dest) - renderer = state['renderer'] + if self.nargs == '+': + if current not in data: + data[current] = [] - renderer.setup() + data[current] += values + else: + data[current] = values - capture_start(state['contexts']) - renderer.run() +def do_cmd_list(cm): + print('Available cameras:') - capture_deinit(state['contexts']) + for idx, c in enumerate(cm.cameras): + print(f'{idx + 1}: {c.id}') def main(): @@ -422,39 +444,28 @@ def main(): print('Unable to find camera', cam_idx) return -1 - contexts.append({ - 'camera': camera, - 'idx': cam_idx, - 'id': 'cam' + str(cam_idx), - 'reqs-queued': 0, - 'reqs-completed': 0, - 'opt-capture': args.capture.get(cam_idx, False), - 'opt-crc': args.crc.get(cam_idx, False), - 'opt-save-frames': args.save_frames.get(cam_idx, False), - 'opt-metadata': args.metadata.get(cam_idx, False), - 'opt-strict-formats': args.strict_formats.get(cam_idx, False), - 'opt-stream': args.stream.get(cam_idx, ['role=viewfinder']), - }) + ctx = CameraContext(camera, cam_idx) + ctx.opt_capture = args.capture.get(cam_idx, 0) + ctx.opt_crc = args.crc.get(cam_idx, False) + ctx.opt_save_frames = args.save_frames.get(cam_idx, False) + 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']) + contexts.append(ctx) for ctx in contexts: - print('Using camera {} as {}'.format(ctx['camera'].id, ctx['id'])) + print('Using camera {} as {}'.format(ctx.camera.id, ctx.id)) for ctx in contexts: if args.list_properties: - do_cmd_list_props(ctx) + ctx.do_cmd_list_props() if args.list_controls: - do_cmd_list_controls(ctx) + ctx.do_cmd_list_controls() if args.info: - do_cmd_info(ctx) + ctx.do_cmd_info() if args.capture: - - state = { - 'cm': cm, - 'contexts': contexts, - 'event_handler': event_handler, - 'request_prcessed': request_prcessed, - } + state = CaptureState(cm, contexts) if args.renderer == 'null': import cam_null @@ -472,9 +483,9 @@ def main(): print('Bad renderer', args.renderer) return -1 - state['renderer'] = renderer + state.renderer = renderer - do_cmd_capture(state) + state.do_cmd_capture() return 0 diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py index 74cd3b38..213e0b03 100644 --- a/src/py/cam/cam_kms.py +++ b/src/py/cam/cam_kms.py @@ -10,8 +10,8 @@ class KMSRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts self.running = False card = pykms.Card() @@ -92,7 +92,7 @@ class KMSRenderer: if old: req = old['camreq'] ctx = old['camctx'] - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) def queue(self, drmreq): if not self.next: @@ -108,7 +108,7 @@ class KMSRenderer: idx = 0 for ctx in self.contexts: - for stream in ctx['streams']: + for stream in ctx.streams: cfg = stream.configuration fmt = cfg.pixel_format @@ -125,7 +125,7 @@ class KMSRenderer: 'size': cfg.size, }) - for fb in ctx['allocator'].buffers(stream): + for fb in ctx.allocator.buffers(stream): w = cfg.size.width h = cfg.size.height fds = [] @@ -148,7 +148,7 @@ class KMSRenderer: self.handle_page_flip(ev.seq, ev.time) def readcam(self, fd): - self.running = self.state['event_handler'](self.state) + self.running = self.state.event_handler() def readkey(self, fileobj): sys.stdin.readline() diff --git a/src/py/cam/cam_null.py b/src/py/cam/cam_null.py index a6da9671..45c5f467 100644 --- a/src/py/cam/cam_null.py +++ b/src/py/cam/cam_null.py @@ -9,8 +9,8 @@ class NullRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts self.running = False @@ -37,11 +37,11 @@ class NullRenderer: print('Exiting...') def readcam(self, fd): - self.running = self.state['event_handler'](self.state) + self.running = self.state.event_handler() def readkey(self, fileobj): sys.stdin.readline() self.running = False def request_handler(self, ctx, req): - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 03096920..d638e9cc 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -176,8 +176,8 @@ class QtRenderer: def __init__(self, state): self.state = state - self.cm = state['cm'] - self.contexts = state['contexts'] + self.cm = state.cm + self.contexts = state.contexts def setup(self): self.app = QtWidgets.QApplication([]) @@ -185,7 +185,7 @@ class QtRenderer: windows = [] for ctx in self.contexts: - for stream in ctx['streams']: + for stream in ctx.streams: window = MainWindow(ctx, stream) window.show() windows.append(window) @@ -206,7 +206,7 @@ class QtRenderer: print('Exiting...') def readcam(self): - running = self.state['event_handler'](self.state) + running = self.state.event_handler() if not running: self.app.quit() @@ -223,7 +223,7 @@ class QtRenderer: wnd.handle_request(stream, fb) - self.state['request_prcessed'](ctx, req) + self.state.request_processed(ctx, req) def cleanup(self): for w in self.windows: @@ -254,7 +254,7 @@ class MainWindow(QtWidgets.QWidget): group.setLayout(groupLayout) controlsLayout.addWidget(group) - lab = QtWidgets.QLabel(ctx['id']) + lab = QtWidgets.QLabel(ctx.id) groupLayout.addWidget(lab) self.frameLabel = QtWidgets.QLabel() @@ -265,7 +265,7 @@ class MainWindow(QtWidgets.QWidget): group.setLayout(groupLayout) controlsLayout.addWidget(group) - camera = ctx['camera'] + camera = ctx.camera for k, v in camera.properties.items(): lab = QtWidgets.QLabel() @@ -308,4 +308,4 @@ class MainWindow(QtWidgets.QWidget): self.label.setPixmap(pix) self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' - .format(ctx['reqs-queued'], ctx['reqs-completed'], ctx['fps'])) + .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps)) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index c9e367a2..5f7ccf1e 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -142,7 +142,7 @@ class QtRenderer: self.window = window def run(self): - camnotif = QtCore.QSocketNotifier(self.state['cm'].efd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.state.cm.efd, QtCore.QSocketNotifier.Read) camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) @@ -155,7 +155,7 @@ class QtRenderer: print('Exiting...') def readcam(self): - running = self.state['event_handler'](self.state) + running = self.state.event_handler() if not running: self.app.quit() @@ -184,12 +184,12 @@ class MainWindow(QtWidgets.QWidget): self.reqqueue = {} self.current = {} - for ctx in self.state['contexts']: + for ctx in self.state.contexts: - self.reqqueue[ctx['idx']] = [] - self.current[ctx['idx']] = [] + self.reqqueue[ctx.idx] = [] + self.current[ctx.idx] = [] - for stream in ctx['streams']: + for stream in ctx.streams: self.textures[stream] = None num_tiles = len(self.textures) @@ -312,12 +312,12 @@ class MainWindow(QtWidgets.QWidget): if len(queue) == 0: continue - ctx = next(ctx for ctx in self.state['contexts'] if ctx['idx'] == ctx_idx) + ctx = next(ctx for ctx in self.state.contexts if ctx.idx == ctx_idx) if self.current[ctx_idx]: old = self.current[ctx_idx] self.current[ctx_idx] = None - self.state['request_prcessed'](ctx, old) + self.state.request_processed(ctx, old) next_req = queue.pop(0) self.current[ctx_idx] = next_req @@ -336,8 +336,8 @@ class MainWindow(QtWidgets.QWidget): size = self.size() - for idx, ctx in enumerate(self.state['contexts']): - for stream in ctx['streams']: + for idx, ctx in enumerate(self.state.contexts): + for stream in ctx.streams: if self.textures[stream] is None: continue @@ -359,5 +359,5 @@ class MainWindow(QtWidgets.QWidget): assert(b) def handle_request(self, ctx, req): - self.reqqueue[ctx['idx']].append(req) + self.reqqueue[ctx.idx].append(req) self.update() From patchwork Tue May 24 11:46:05 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16019 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E14D4C326D for ; Tue, 24 May 2022 11:46:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9765465689; Tue, 24 May 2022 13:46:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392811; bh=Ed51yMM7FH3fIuTwVwblqVMwAFno1yWPdeEfEuUFOKM=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=aR+ivw5CTyi4nJCgdg5pqjQW6iwOYiI3uG8kIRWbs7sGl1ft3u+DgFzBlNwxbJQTX GUN5SS24isXBoZChd87eRdOm6S9f4jaEJ/9RNg84b/Op31DbxivszJAuc4zxYbBCJW rfXpE5u0B/fqdCL7ttdsJfnlgNs/YYvew5lD0L3nP3wBSkOn4sqgmcfEFC4YUe8xeX 0MofGFXVtLpLfaCNuQcBw0Qn9KHfu788dguSVaIL5C0jQE3AHnFbF5ox1GhjLBbia6 oq06Mg44Rg7Y0rhy/h8pwy4paoEGB+fkgob2QwqK+o/zQrLLUug4YCv8+r6Tfca/FA IQb12seKB4QwQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5D5786567A for ; Tue, 24 May 2022 13:46:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HUhx8KvI"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CB2EF8DC; Tue, 24 May 2022 13:46:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392797; bh=Ed51yMM7FH3fIuTwVwblqVMwAFno1yWPdeEfEuUFOKM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HUhx8KvIMnJmzbtinj+V+r2VQCcXudXTcceUVx2CE4AHKwqfpp8KK7bjuwuVIUhLb c9kaL7E4GdHlNAYTtx3K9oB+bcIl2EGfDpiuenlWKvgMrKQHPeqqFXDpg+kahd479G bhxY4/dwNPBWyg66D214ku8PtMHNDLRXD9ebNvtQ= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:05 +0300 Message-Id: <20220524114610.41848-15-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 14/19] py: Add README.md X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a basic README for the Python bindings. While not a proper doc, the README and the examples should give enough guidance for users who are somewhat familiar with libcamera. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/py/README.md diff --git a/src/py/README.md b/src/py/README.md new file mode 100644 index 00000000..0a763030 --- /dev/null +++ b/src/py/README.md @@ -0,0 +1,61 @@ +# Python Bindings for libcamera + +*WARNING* The bindings are under work, and the API will change. + +## Differences to the C++ API + +As a rule of thumb the bindings try to follow the C++ API when possible. This +chapter lists the differences. + +Mostly these differences fall under two categories: + +1. Differences caused by the inherent differences between C++ and Python. +These differences are usually caused by the use of threads or differences in +C++ vs Python memory management. + +2. Differences caused by the code being work-in-progress. It's not always +trivial to create a binding in a satisfying way, and the current bindings +contain simplified versions of the C++ API just to get forward. These +differences are expected to eventually go away. + +### Coding Style + +The C++ code for the bindings follows the libcamera coding style as much as +possible. Note that the indentation does not quite follow the clang-format +style, as clang-format makes a mess of the style used. + +The API visible to Python side follows Python style as much as possible. + +This means that e.g. `Camera::generateConfiguration` maps to +`Camera.generate_configuration`. + +### CameraManager + +The Python API provides a singleton CameraManager via `CameraManager.singleton()`. +There is no need to start or stop the CameraManager. + +### Handling Completed Requests + +The Python bindings does not expose the Camera::requestCompleted signal +directly as the signal is invoked from another thread and it has real-time +constraints. Instead the bindings will internally queue the completed +requests and use an eventfd to inform the user that there are completed +requests. + +The user can wait on the eventfd, and upon getting an event, use +CameraManager.read_event() to clear the eventfd event and +CameraManager.get_ready_requests() to get the completed requests. + +### Controls & Properties + +The classes related to controls and properties are rather complex to implement +directly in the Python bindings. There are some simplifications on the Python +bindings: + +- There is no ControlValue class. Python objects are automatically converted + to ControlValues and vice versa. +- There is no ControlList class. A python dict with ControlId keys and python + object values is used instead. +- There is no ControlInfoMap class. A python dict with ControlId keys and + ControlInfo values is used instead. + From patchwork Tue May 24 11:46:06 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16021 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5859EBD161 for ; Tue, 24 May 2022 11:46:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EE19F65692; Tue, 24 May 2022 13:46:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392813; bh=LCOgD3KxUFzPEi0mLP9cLyY2+pIhr+Mw2Ym96J/B/5c=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=I9cyeJ7xsECRmPcSgHAMtMa5rCBPl8VetQF0sMkm99D7w36L/jVm+fvlF9i0QbF3f UUQf5q8D1DGVuda39RoHj3q+dKfrjXviA6tEW11tidHRCDZ0TEEUqmCNF1MZLdDFEQ 2B9Q1vEze0O3xqjYtXljl8rIwc8+3Waqd1EAi3+Z9WKljeOM1X8KQib4vcvU183T0D 6YACMEiIjckSUzRtpX+8qZOwFUK38706ak7sd/pjX2768gEoyCnFIDRPdTTpbcxp7h hgYQfoLBhHO0IrozPdNoHCpncrsqOWo/Ggv12NDIneU+1R5O3wxFhFRqzsQdSxJZhE TBQEwOSMwpe0g== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E6DB66566C for ; Tue, 24 May 2022 13:46:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KTqXJVuY"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 59E39A16; Tue, 24 May 2022 13:46:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392797; bh=LCOgD3KxUFzPEi0mLP9cLyY2+pIhr+Mw2Ym96J/B/5c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KTqXJVuYJE3KKsnGAdtUgUmVnA299swiI0MgeAOPth9mXC8lUeLg0JcFyF3Y7ACUL Aade9m69PtX0luwiu8n525oeST0VUemHhIXPtoQBK5KuI+aynSV5uwJa/I1ZoewQ+Z PFi/Y6+wdaYXsD59hzrO5SpUokcwDV9mVpG5zAn0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:06 +0300 Message-Id: <20220524114610.41848-16-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 15/19] py: Re-implement controls geneneration X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The Python bindings controls generation was not very good. It only covered the enums and they were in the main namespace. This adds the controls somewhat similarly to the C++ side. We will have e.g.: libcamera.controls.Brightness libcamera.controls.AeMeteringModeEnum.CentreWeighted Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- ...py-control-enums.py => gen-py-controls.py} | 20 +++++++++++-------- src/py/libcamera/meson.build | 16 +++++++++------ ...ed.cpp.in => py_controls_generated.cpp.in} | 17 +++++++++++++--- src/py/libcamera/py_main.cpp | 4 ++-- 4 files changed, 38 insertions(+), 19 deletions(-) rename src/py/libcamera/{gen-py-control-enums.py => gen-py-controls.py} (85%) rename src/py/libcamera/{py_control_enums_generated.cpp.in => py_controls_generated.cpp.in} (50%) diff --git a/src/py/libcamera/gen-py-control-enums.py b/src/py/libcamera/gen-py-controls.py similarity index 85% rename from src/py/libcamera/gen-py-control-enums.py rename to src/py/libcamera/gen-py-controls.py index 6b2b5362..e3e1e178 100755 --- a/src/py/libcamera/gen-py-control-enums.py +++ b/src/py/libcamera/gen-py-controls.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # -# Generate Python bindings enums for controls from YAML +# Generate Python bindings controls from YAML import argparse import string @@ -27,18 +27,22 @@ def generate_py(controls): for ctrl in controls: name, ctrl = ctrl.popitem() - enum = ctrl.get('enum') - if not enum: - continue - if ctrl.get('draft'): ns = 'libcamera::controls::draft::' + container = "draft" else: ns = 'libcamera::controls::' + container = "controls" cpp_enum = name + 'Enum' - out += '\tpy::enum_<{}{}>(m, \"{}\")\n'.format(ns, cpp_enum, name) + out += f'\t{container}.def_readonly_static("{name}", static_cast(&{ns}{name}));\n\n' + + enum = ctrl.get('enum') + if not enum: + continue + + out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum) if name == 'LensShadingMapMode': prefix = 'LensShadingMapMode' @@ -54,9 +58,9 @@ def generate_py(controls): out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum) - out += '\t;\n' + out += '\t;\n\n' - return {'enums': out} + return {'controls': out} def fill_template(template, data): diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index b705ac1f..e8010846 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -18,17 +18,21 @@ pycamera_sources = files([ 'py_main.cpp', ]) -gen_py_control_enums_input_files = files([ +# Generate controls + +gen_py_controls_input_files = files([ '../../libcamera/control_ids.yaml', - 'py_control_enums_generated.cpp.in', + 'py_controls_generated.cpp.in', ]) -gen_py_control_enums = files('gen-py-control-enums.py') +gen_py_controls = files('gen-py-controls.py') pycamera_sources += custom_target('py_gen_controls', - input : gen_py_control_enums_input_files, - output : ['py_control_enums_generated.cpp'], - command : [gen_py_control_enums, '-o', '@OUTPUT@', '@INPUT@']) + input : gen_py_controls_input_files, + output : ['py_controls_generated.cpp'], + command : [gen_py_controls, '-o', '@OUTPUT@', '@INPUT@']) + +# Generate formats gen_py_formats_input_files = files([ '../../libcamera/formats.yaml', diff --git a/src/py/libcamera/py_control_enums_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in similarity index 50% rename from src/py/libcamera/py_control_enums_generated.cpp.in rename to src/py/libcamera/py_controls_generated.cpp.in index ed81fbe7..cb8442ba 100644 --- a/src/py/libcamera/py_control_enums_generated.cpp.in +++ b/src/py/libcamera/py_controls_generated.cpp.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Tomi Valkeinen * - * Python bindings - Auto-generated control enums + * Python bindings - Auto-generated controls * * This file is auto-generated. Do not edit. */ @@ -13,7 +13,18 @@ namespace py = pybind11; -void init_py_control_enums_generated(py::module& m) +class PyControls { -${enums} +}; + +class PyDraftControls +{ +}; + +void init_py_controls_generated(py::module& m) +{ + auto controls = py::class_(m, "controls"); + auto draft = py::class_(controls, "draft"); + +${controls} } diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 5d389942..33ecc1cd 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -131,14 +131,14 @@ static void handleRequestCompleted(Request *req) } void init_py_enums(py::module &m); -void init_py_control_enums_generated(py::module &m); +void init_py_controls_generated(py::module &m); void init_py_formats_generated(py::module &m); void init_py_geometry(py::module &m); PYBIND11_MODULE(_libcamera, m) { init_py_enums(m); - init_py_control_enums_generated(m); + init_py_controls_generated(m); init_py_geometry(m); /* Forward declarations */ From patchwork Tue May 24 11:46:07 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16022 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 30E1CC3275 for ; Tue, 24 May 2022 11:46:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B9F4A6568C; Tue, 24 May 2022 13:46:53 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392813; bh=rvVo0pPc2hl0ogBe7KG8c/TLUjfwH2Z5UiVRR07Nt7c=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=wlt6/kRg4zu8MFPHRVdDAtbFXwSnky1MRJDUK5Ofyax5ce8zqb3/Q7B3jo617ajLs isQQ85lLlL4LY6BTq4zzjtHk50rFUGA8JYQR1NzI0DIU127i3mvEM99saL/DuRwmoe 1kiu8T8fCm8nnZqFK92BbpsgHP+awaLXMre/5h3gsvj/NooWwn67jofS7oenBWvXQS 6w1Nocn29r3nHTRUfHzsrrVSmP2PBm1Yu9vyrSM+uMiSTFi4rXQHWqjOOb5MjWm73e Qy41NGmHgkqlSBauzesC6vOuuRw2gUiCrMBiDjJQU8szk4dH4isfUjs7baFiRKplwr caDvwU4dWrKMQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 711026566F for ; Tue, 24 May 2022 13:46:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Enqe3ILl"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D9CBDAEC; Tue, 24 May 2022 13:46:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392798; bh=rvVo0pPc2hl0ogBe7KG8c/TLUjfwH2Z5UiVRR07Nt7c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Enqe3ILlTNbfA/TIobVXNrN/D0vpHlgoTr0BVADYR49bPaecpyFSZu3BtlWse1nC+ WvRauKmsO0rbmQ/uNvLLfpSaU+/qKbIWZUerXw/CWno9zqIHHCjO7cfcr8pRh/+Ral aonaQWfOKeOa3z0I6gyYFIvJuQskNG4PZnNoLrck= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:07 +0300 Message-Id: <20220524114610.41848-17-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 16/19] py: Re-structure the controls API X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add ControlInfo class and change the controls related methods to resemble the C++ API (e.g. no more string based control methods). We don't implement ControlList or ControlInfoMap but just expose the same data via standard Python dict. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 8 +-- src/py/cam/cam_qt.py | 9 ++-- src/py/libcamera/py_main.cpp | 100 ++++++++++++++++++----------------- 3 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index f6e8232c..f464bd01 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -48,16 +48,16 @@ class CameraContext: print('Properties for', self.id) - for name, prop in camera.properties.items(): - print('\t{}: {}'.format(name, prop)) + for cid, val in camera.properties.items(): + print('\t{}: {}'.format(cid, val)) def do_cmd_list_controls(self): camera = self.camera print('Controls for', self.id) - for name, prop in camera.controls.items(): - print('\t{}: {}'.format(name, prop)) + for cid, info in camera.controls.items(): + print('\t{}: {}'.format(cid, info)) def do_cmd_info(self): camera = self.camera diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index d638e9cc..739e9749 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -267,9 +267,9 @@ class MainWindow(QtWidgets.QWidget): camera = ctx.camera - for k, v in camera.properties.items(): + for cid, cv in camera.properties.items(): lab = QtWidgets.QLabel() - lab.setText(k + ' = ' + str(v)) + lab.setText("{} = {}".format(cid, cv)) groupLayout.addWidget(lab) group = QtWidgets.QGroupBox('Controls') @@ -277,9 +277,10 @@ class MainWindow(QtWidgets.QWidget): group.setLayout(groupLayout) controlsLayout.addWidget(group) - for k, (min, max, default) in camera.controls.items(): + for cid, cinfo in camera.controls.items(): lab = QtWidgets.QLabel() - lab.setText('{} = {}/{}/{}'.format(k, min, max, default)) + lab.setText('{} = {}/{}/{}' + .format(cid, cinfo.min, cinfo.max, cinfo.default)) groupLayout.addWidget(lab) controlsLayout.addStretch() diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 33ecc1cd..80f26c12 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -159,6 +159,7 @@ PYBIND11_MODULE(_libcamera, m) auto pyFrameBuffer = py::class_(m, "FrameBuffer"); auto pyStream = py::class_(m, "Stream"); auto pyControlId = py::class_(m, "ControlId"); + auto pyControlInfo = py::class_(m, "ControlInfo"); auto pyRequest = py::class_(m, "Request"); auto pyRequestStatus = py::enum_(pyRequest, "Status"); auto pyRequestReuse = py::enum_(pyRequest, "Reuse"); @@ -260,28 +261,17 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("id", &Camera::id) .def("acquire", &Camera::acquire) .def("release", &Camera::release) - .def("start", [](Camera &self, py::dict controls) { + .def("start", [](Camera &self, + const std::unordered_map &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(); + ControlList controlList(self.controls()); - 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(hval); - - controlList.set(id->id(), pyToControlValue(obj, id->type())); + for (const auto& [id, obj]: controls) { + auto val = pyToControlValue(obj, id->type()); + controlList.set(id->id(), val); } int ret = self.start(&controlList); @@ -291,7 +281,7 @@ PYBIND11_MODULE(_libcamera, m) } return 0; - }, py::arg("controls") = py::dict()) + }, py::arg("controls") = std::unordered_map()) .def("stop", [](Camera &self) { int ret = self.stop(); @@ -341,40 +331,26 @@ PYBIND11_MODULE(_libcamera, m) 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; + /* Convert ControlInfoMap to std container */ - for (const auto &[id, ci] : self.controls()) { - ret[id->name().c_str()] = std::make_tuple(controlValueToPy(ci.min()), - controlValueToPy(ci.max()), - controlValueToPy(ci.def())); - } + std::unordered_map ret; + + for (const auto &[k, cv] : self.controls()) + ret[k] = cv; return ret; }) .def_property_readonly("properties", [](Camera &self) { - py::dict ret; + /* Convert ControlList to std container */ - for (const auto &[key, cv] : self.properties()) { - const ControlId *id = properties::properties.at(key); - py::object ob = controlValueToPy(cv); + std::unordered_map ret; - ret[id->name().c_str()] = ob; + for (const auto &[k, cv] : self.properties()) { + const ControlId *id = properties::properties.at(k); + py::object ob = controlValueToPy(cv); + ret[id] = ob; } return ret; @@ -464,7 +440,34 @@ PYBIND11_MODULE(_libcamera, m) pyControlId .def_property_readonly("id", &ControlId::id) .def_property_readonly("name", &ControlId::name) - .def_property_readonly("type", &ControlId::type); + .def_property_readonly("type", &ControlId::type) + .def("__str__", [](const ControlId &self) { return self.name(); }) + .def("__repr__", [](const ControlId &self) { + return py::str("libcamera.ControlId({}, {}, {})") + .format(self.id(), self.name(), self.type()); + }); + + pyControlInfo + .def_property_readonly("min", [](const ControlInfo& self) { + return controlValueToPy(self.min()); + }) + .def_property_readonly("max", [](const ControlInfo& self) { + return controlValueToPy(self.max()); + }) + .def_property_readonly("default", [](const ControlInfo& self) { + return controlValueToPy(self.def()); + }) + .def_property_readonly("values", [](const ControlInfo& self) { + py::list l; + for (const auto &v : self.values()) + l.append(controlValueToPy(v)); + return l; + }) + .def("__str__", &ControlInfo::toString) + .def("__repr__", [](const ControlInfo& self) { + return py::str("libcamera.ControlInfo({})") + .format(self.toString()); + }); pyRequest /* \todo Fence is not supported, so we cannot expose addBuffer() directly */ @@ -475,17 +478,18 @@ PYBIND11_MODULE(_libcamera, m) .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) { + .def("set_control", [](Request &self, const ControlId &id, py::object value) { self.controls().set(id.id(), pyToControlValue(value, id.type())); }) .def_property_readonly("metadata", [](Request &self) { - py::dict ret; + /* Convert ControlList to std container */ + + std::unordered_map 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; + ret[id] = ob; } return ret; From patchwork Tue May 24 11:46:08 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16023 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 51049C3276 for ; Tue, 24 May 2022 11:46:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 79A256568B; Tue, 24 May 2022 13:46:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392814; bh=Is73O9BqX93oyE9wi7SihC8QIUCguU6UCufvnn/heN8=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=rh+2VN9+byow/PJszN2s4rUfZXdgNUs7ghMM4fS/fx2G0llj5biP6Flb5uEfHgq7R /Tsh5bOkl8/4gs4drEiyG9hdEfjZt0HfH+RL4LQ6bKPmxRtA1sCdYhwu6vDhi1sm6R +F84pDv3IStRp+MI2STsm+fMgEhagqOg6xE8Nm6cJu1ROcKHl9SryRLLvs9Hys4kSj QeTeWo3Lpa+RGV+t6KfixaVg6IS25qVIkpsa7wHwkA5LIt3DZRS+AcKwUZ9wPrOlO+ mGkYVhP5CEUMv5O1VcIcDGwCaJe0MB/Kp/PTyLlGgVh2J/hnU8De6jyHpwo2mNNmju dKb5OGeDalqPg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1180065685 for ; Tue, 24 May 2022 13:46:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UnAn880U"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 642304A8; Tue, 24 May 2022 13:46:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392798; bh=Is73O9BqX93oyE9wi7SihC8QIUCguU6UCufvnn/heN8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UnAn880UduCX0ei70d3ZVAFTdGVwKPQuBMhR/1lM432bzBuojz3uThJNW8LzCoQ40 Dzr6Z4+t3ojmN61RFaD/7cn5nxxvDV/yEiyIm7AFs7D8i1zWqXLViM5xdEhDsFsgXi 0OIGw10ah3DfigKHaC0D33BiRgH/dMmV6PgCD0Rw= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:08 +0300 Message-Id: <20220524114610.41848-18-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 17/19] py: Rename 'efd' to 'event_fd' X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Cc: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Tomi Valkeinen Perhaps it's better to have a more descriptive name here. I also considered just renaming 'efd' to 'fd', but 'event_fd' won. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_kms.py | 2 +- src/py/cam/cam_null.py | 2 +- src/py/cam/cam_qt.py | 2 +- src/py/cam/cam_qtgl.py | 2 +- src/py/examples/itest.py | 2 +- src/py/examples/simple-continuous-capture.py | 2 +- src/py/libcamera/py_main.cpp | 2 +- test/py/unittests.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py index 213e0b03..49b00211 100644 --- a/src/py/cam/cam_kms.py +++ b/src/py/cam/cam_kms.py @@ -161,7 +161,7 @@ class KMSRenderer: sel = selectors.DefaultSelector() sel.register(self.card.fd, selectors.EVENT_READ, self.readdrm) - sel.register(self.cm.efd, selectors.EVENT_READ, self.readcam) + sel.register(self.cm.event_fd, selectors.EVENT_READ, self.readcam) sel.register(sys.stdin, selectors.EVENT_READ, self.readkey) print('Press enter to exit') diff --git a/src/py/cam/cam_null.py b/src/py/cam/cam_null.py index 45c5f467..40dbd266 100644 --- a/src/py/cam/cam_null.py +++ b/src/py/cam/cam_null.py @@ -23,7 +23,7 @@ class NullRenderer: self.running = True sel = selectors.DefaultSelector() - sel.register(self.cm.efd, selectors.EVENT_READ, self.readcam) + sel.register(self.cm.event_fd, selectors.EVENT_READ, self.readcam) sel.register(sys.stdin, selectors.EVENT_READ, self.readkey) print('Press enter to exit') diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index 739e9749..d8d6a98f 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -193,7 +193,7 @@ class QtRenderer: self.windows = windows def run(self): - camnotif = QtCore.QSocketNotifier(self.cm.efd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Read) camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index 5f7ccf1e..4b43f51d 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -142,7 +142,7 @@ class QtRenderer: self.window = window def run(self): - camnotif = QtCore.QSocketNotifier(self.state.cm.efd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Read) camnotif.activated.connect(lambda _: self.readcam()) keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) diff --git a/src/py/examples/itest.py b/src/py/examples/itest.py index 01e020e2..2d1c6453 100755 --- a/src/py/examples/itest.py +++ b/src/py/examples/itest.py @@ -66,7 +66,7 @@ def capture(ctx): # Use Selector to wait for events from the camera and from the keyboard sel = selectors.DefaultSelector() - sel.register(cm.efd, selectors.EVENT_READ, lambda fd: handle_camera_event(ctx)) + sel.register(cm.event_fd, selectors.EVENT_READ, lambda fd: handle_camera_event(ctx)) reqs = [] diff --git a/src/py/examples/simple-continuous-capture.py b/src/py/examples/simple-continuous-capture.py index 05fa2ada..12708914 100755 --- a/src/py/examples/simple-continuous-capture.py +++ b/src/py/examples/simple-continuous-capture.py @@ -76,7 +76,7 @@ def capture(cm, cam, reqs): # Use Selector to wait for events from the camera and from the keyboard sel = selectors.DefaultSelector() - sel.register(cm.efd, selectors.EVENT_READ, lambda fd: handle_camera_event(cm, cam)) + sel.register(cm.event_fd, selectors.EVENT_READ, lambda fd: handle_camera_event(cm, cam)) sel.register(sys.stdin, selectors.EVENT_READ, handle_key_envet) reqs = [] diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 80f26c12..3fd5c0fc 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -209,7 +209,7 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("version", &CameraManager::version) - .def_property_readonly("efd", [](CameraManager &) { + .def_property_readonly("event_fd", [](CameraManager &) { return gEventfd; }) diff --git a/test/py/unittests.py b/test/py/unittests.py index 8c445bc9..426efb06 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -269,7 +269,7 @@ class SimpleCaptureMethods(CameraTesterBase): gc.collect() sel = selectors.DefaultSelector() - sel.register(cm.efd, selectors.EVENT_READ) + sel.register(cm.event_fd, selectors.EVENT_READ) reqs = [] From patchwork Tue May 24 11:46:09 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16024 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 53183C3277 for ; Tue, 24 May 2022 11:46:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9B977656A2; Tue, 24 May 2022 13:46:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392815; bh=39AzA7pClZWygLVp4g3PMPKmwcQqv7kWaSnSRhk7ilw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=uDxuoKbSgWSUATOztmckUUkWh+FHuDGbkSPaulGF94lTqZYkDgrDFqzsyrrmzxr00 h/iBHicWYExOgPvT0/DcLCXngzYhcPVZLZE76+wZ5Cq42ENondlE+QqKgfXWLlRN/q YIACvRLJpMmziQv5E9w/+OeUNGmgVejmstsz1Xg2lMGX5sysj1Ys8qDlQYaNtV4i42 +nAzx/Rm3uFlSLz7AyHV7FWtv2+ofi3lzB44rSr3PPQkhHb1X29+ildFIguL8O/mKz R+PWBye4mTnIGakDCA4flubMakUdxhzze1K0MB4Qq0CDTbvlP+UvF3ag8PWzQn/smA Ag7tMpqmHtGLQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4F7FA65677 for ; Tue, 24 May 2022 13:46:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QpDIZEJP"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EE6498DC; Tue, 24 May 2022 13:46:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392799; bh=39AzA7pClZWygLVp4g3PMPKmwcQqv7kWaSnSRhk7ilw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QpDIZEJPTvobyKRH6bVl3w+ukJK1Ev67BYpRdxh3Gwa6nH+iSHUpw91MQZaWMfsHI SQ6RQ1OgvTJ8kJHYjzvwFDIiKD+ZWWI+N+O8ilCfKBWGU67yO7bybi/Eo1VeFBJnaQ 2O4GYN5ws4j1+jSzOY9PnF23HEVsCZtC8sojyLm0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:09 +0300 Message-Id: <20220524114610.41848-19-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 18/19] py: Generate bindings for properties X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Generate bindings for properties in a very similar way as done for controls. We do need to distinguish between the two, and thus I added --properties flag to gen-py-controls.py. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/gen-py-controls.py | 27 +++++++++++------ src/py/libcamera/meson.build | 12 ++++++++ .../libcamera/py_properties_generated.cpp.in | 30 +++++++++++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 src/py/libcamera/py_properties_generated.cpp.in diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py index e3e1e178..a9264777 100755 --- a/src/py/libcamera/gen-py-controls.py +++ b/src/py/libcamera/gen-py-controls.py @@ -21,17 +21,19 @@ def find_common_prefix(strings): return prefix -def generate_py(controls): +def generate_py(controls, prop_mode): out = '' + mode_name = "properties" if prop_mode else "controls" + for ctrl in controls: name, ctrl = ctrl.popitem() if ctrl.get('draft'): - ns = 'libcamera::controls::draft::' + ns = 'libcamera::{}::draft::'.format(mode_name) container = "draft" else: - ns = 'libcamera::controls::' + ns = 'libcamera::{}::'.format(mode_name) container = "controls" cpp_enum = name + 'Enum' @@ -44,12 +46,17 @@ def generate_py(controls): out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum) - if name == 'LensShadingMapMode': - prefix = 'LensShadingMapMode' - elif name == 'SceneFlicker': - # If we strip the prefix, we would get '50Hz', which is illegal name - prefix = '' + if not prop_mode: + # 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 = '' + 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]) for entry in enum: @@ -79,12 +86,14 @@ def main(argv): help='Input file name.') parser.add_argument('template', type=str, help='Template file name.') + parser.add_argument('--properties', action='store_true', default=False, + help='Generate bindings for properties instead of controls') args = parser.parse_args(argv[1:]) data = open(args.input, 'rb').read() controls = yaml.safe_load(data)['controls'] - data = generate_py(controls) + data = generate_py(controls, prop_mode=args.properties) data = fill_template(args.template, data) diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index e8010846..e0010353 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -32,6 +32,18 @@ pycamera_sources += custom_target('py_gen_controls', output : ['py_controls_generated.cpp'], command : [gen_py_controls, '-o', '@OUTPUT@', '@INPUT@']) +# Generate properties + +gen_py_property_enums_input_files = files([ + '../../libcamera/property_ids.yaml', + 'py_properties_generated.cpp.in', +]) + +pycamera_sources += custom_target('py_gen_properties', + input : gen_py_property_enums_input_files, + output : ['py_properties_generated.cpp'], + command : [gen_py_controls, '--properties', '-o', '@OUTPUT@', '@INPUT@']) + # Generate formats gen_py_formats_input_files = files([ diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in new file mode 100644 index 00000000..044b2b2a --- /dev/null +++ b/src/py/libcamera/py_properties_generated.cpp.in @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2022, Tomi Valkeinen + * + * Python bindings - Auto-generated properties + * + * This file is auto-generated. Do not edit. + */ + +#include + +#include + +namespace py = pybind11; + +class PyProperties +{ +}; + +class PyDraftProperties +{ +}; + +void init_py_properties_generated(py::module& m) +{ + auto controls = py::class_(m, "properties"); + auto draft = py::class_(controls, "draft"); + +${controls} +} From patchwork Tue May 24 11:46:10 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16025 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E0AAFC3278 for ; Tue, 24 May 2022 11:46:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4AB2965694; Tue, 24 May 2022 13:46:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653392816; bh=7pmVMp5PW1ecMtR5ggP9gGJEZqiuysjK3w7X+uzXjzg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=HYkoEsaw2U3xF8XNUqglTgq7sXwnS7jL64DtnlU16DXpjJgv4dUMchKS1j49qyEFv jirRbzI+iOxwVp95gP6jZebh5F4ZOvSMzvGYn3Htx1Cmd5BHiDDgj20Ov40oGto0cu OWfngh9In2cYGvPlA5bUgi0P9Sjv0Rm+VmEru6uQ4IXvbsMIfwZighfLa2DSDt7Wmn OSMDz+BHzIA9b8I7Ww+tK5ERFgNo0jNua8YYam/Vav3ec09GrUmvlJRqTyE5QCz1Wx B789dJtun+hpCiWhfLhBLrBHoStsag7upNKxlvt/CTx4CKX/UW61M1ScQj8fSLS4ac 5TVH+qOHYtzKQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E20B065688 for ; Tue, 24 May 2022 13:46:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AeEFjaVO"; dkim-atps=neutral Received: from deskari.lan (91-156-85-209.elisa-laajakaista.fi [91.156.85.209]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7B17BAEC; Tue, 24 May 2022 13:46:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653392799; bh=7pmVMp5PW1ecMtR5ggP9gGJEZqiuysjK3w7X+uzXjzg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AeEFjaVOw+u1/1GuwctIpHio/k8X47ipPWONlVLF1NikrQGquqjHn5Dlt82EpqFX5 vmMhK0Tzeum3zqoQVhrlU/dYTXBrxwaXmoAF3YdE16iaezPBjSvhViwZdBcB4atclR kZZ1t+JvJuJjLk2eZY9SRBUrFOUEQS607HpoQ9xg= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Tue, 24 May 2022 14:46:10 +0300 Message-Id: <20220524114610.41848-20-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> References: <20220524114610.41848-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 19/19] py: Use ln --relative to create symlinks X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Tomi Valkeinen via libcamera-devel From: Tomi Valkeinen Reply-To: Tomi Valkeinen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" We create symlinks from the Python bindings build dir to the source dir so that 1) the build dir can be used to use the bindings, and 2) to allow modifications of the source .py files to be used right away without rebuilding. The symlinks were recently fixed and changed to use absolute paths. However, absolute paths ruin one main use case I have: using the bindings from the build dir via nfs from an ARM device. So move back to relative paths, but accomplish this with the --relative parameter for ln, instead of guessing the right relative path as was done before the above-mentioned fix. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index e0010353..3219d3df 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -84,11 +84,11 @@ pycamera = shared_module('_libcamera', # Create symlinks from the build dir to the source dir so that we can use the # Python module directly from the build dir. -run_command('ln', '-fsT', files('__init__.py'), +run_command('ln', '-fsrT', files('__init__.py'), meson.current_build_dir() / '__init__.py', check: true) -run_command('ln', '-fsT', meson.current_source_dir() / 'utils', +run_command('ln', '-fsrT', meson.current_source_dir() / 'utils', meson.current_build_dir() / 'utils', check: true)