From patchwork Fri May 27 14:44:18 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16072 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 7E852BD161 for ; Fri, 27 May 2022 14:45:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F0B5965631; Fri, 27 May 2022 16:45:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662708; 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=p98A9eGonr20qoPxbhB8F0CXh7PYJiHHz+yBkp2g+ega/xL2a53UNEpsuuw8xba+q bvHYIntzVZGinM5jwPBXE/2kGm/9JFs+9eH1OdHxvf+8Y0IRuoRSV6CK/8aTezEQwo UR1MCvADWpJUYNlqYQlOqa/ygNOXBCsbaPRYrAAAL9kcoSOpfuPNPl/04+KUSb0BD9 sNHWBnnAXz72prPLZ2y1ui8wH3V71Go21MCUb43M3ZwaA2UHztKW/YSXlX+roPpGsv hcwqFLNm7Gx7LTHpWfrjMMFdlW8dxrG6H9CRxHoo6F0BDkPV/QovNEMTSgIQt1aXSB Y5qtclG4J4IMw== 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 62AC0633A2 for ; Fri, 27 May 2022 16:45:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="k3ktjZrx"; 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 7E1F0A58; Fri, 27 May 2022 16:45:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662704; bh=upYDWDJdApqmzorAWn3YLVaQA6aoQJaTCnP3PF5ggoU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=k3ktjZrxHHp4B9CrK6+IikLvvY9rvFTra2+QoE60lRHeUfcAhqgWpcrijqF8KAmIc cvyFTr8Zrd/VkMZL0d1itR/z00foeZSrN2+XnLP7FPF6lsEvqOvpF2wwRKLeAk0tdT LGkjuw3aDF26m6zNAH6k8IzZJ2gQxybj7wVApTj0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:18 +0300 Message-Id: <20220527144447.94891-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 01/30] 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 Fri May 27 14:44:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16073 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 712B0BD161 for ; Fri, 27 May 2022 14:45:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2A5A16563B; Fri, 27 May 2022 16:45:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662709; 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=Qg3fHcaXGRSEL6b5cjtjPgNSvDT890FAbyaDO1If9b2N9Rh4x/LejWhamspRoGVPo c+X7NmnscG+lucRVUJBIJk/i8Y1qLHjwc2yYEauNU8lfSyQKBZQwjsb9Ds99uHaILf gmSAwP8OOl3NdqamXmfmxlUf5JB+wGEr3Nf0kRjdgjiE7jHuvNoHKBP2y+az1erqM4 DVvZvfYj9SKewCNWtnE5OJ8Zx4XHd0GLmYnvVVG2y9EJJGZQzo/KghRs5y8HItVB3a Y14n4smo/uXeUwKtcmeAyU8IgKzCbuoX+d6DfjHvcvgh07KwnihjtcUXbqIWxffrMU Y+XqeOOrL60oQ== 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 8E762633A4 for ; Fri, 27 May 2022 16:45:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Kh7cbjY3"; 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 08077B1D; Fri, 27 May 2022 16:45:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662705; bh=u2tqjxRj2OwecQxi/1sS9LWYVbQZr9S9oBztjV3FNyo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Kh7cbjY3sQTKEne8uCrA6FfyLnl+5DSXTEPkDB9Z1+xVMjTNpRkAEvkmeypeCohgQ 77bWiTzk20Zl+JYJId4B6T9okQuXdiGkbXeagsnE3ptG1OC+tEJ5Yj8cpXTcaR8P8m ge3eGWNE8nZxjZbg0YHFFB/l7KruevkMUlQrYmZM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:19 +0300 Message-Id: <20220527144447.94891-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 02/30] 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 Fri May 27 14:44:20 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16074 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 23CA6BD161 for ; Fri, 27 May 2022 14:45:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7F76965646; Fri, 27 May 2022 16:45:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662711; bh=1k1mDOSc0wrwJgkzRG4QBagJlYu2dfFoTeBMEZ7zPTw=; 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=VYxO9RFzsWSRcyCiqRMbHDCeE8zgAIgeSZS3UxCjMVoUi9ORgTTy80881fDXROsU9 V5pY+6bZrF2z/0x/JWvFr78KQZqgRd6Rwznl7O3s3taa6NO5N758OdGN8F3c2h8xu6 XdtFwk4ZHv78xd/I/eJZq/QxLSt80a4CC+u9E9/Vlk0cpxR3RpZkbC/yV6yMfuNwnL Z8eeGpZkkehqwVgDc4K/LwLy0iQYTCR52tawnM7NsMxCde0bP/2OQeFONc+6Gio22P 5luWDrFn/nieQk+DXPn4gcYOy8lnl37d29F3bCHxKXhpvvAo6EobUNCN8i68qygAXT p4CCeCGgfvIxA== 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 1476A633A4 for ; Fri, 27 May 2022 16:45:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="j0olAdOe"; 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 8701DB90; Fri, 27 May 2022 16:45:05 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662705; bh=1k1mDOSc0wrwJgkzRG4QBagJlYu2dfFoTeBMEZ7zPTw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j0olAdOecs6qmYraTXst/haPH/dMGH5ZjMrvWpyt5ww9AHBcBL+kcVEXRGXY2Pljm pFLQBWiD2BtjAFmXyuCz1NWmVzqry5HcROJqVMmZBxd5T6wz79NnSuTa0nwzzfRRWW 55nrzOksmN0HWMNeFc4pAOgYdaXH83Udhz14ymJ0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:20 +0300 Message-Id: <20220527144447.94891-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 03/30] 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 Fri May 27 14:44:21 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16075 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 42742C3256 for ; Fri, 27 May 2022 14:45:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B875D65642; Fri, 27 May 2022 16:45:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662712; 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=rPZBgqhCP0yPMfhe4W/qodkHjl3Iw7TaLGAoc4jnsJvHKbpZ0FpUL5JEABDyjbp1w qPZX1WiCA3YBjjUNu2B1e4YMx+GCpi5bUkvuOtRcB3lLiyfOs0fNhkp2DSgC3yDUiO 9h8SbnBAugdG6SqRZMjNBOsO5RJ0nbto+MNuPpbcoBa7VeqwTza/4JWnJ43HjTxsmr W9FYWYaqOdWri9n8GwPyTydHPVIUZjkR8iHlnH5QYnzT97ohF/Y06Gb0ygJo2WKD4B H1Ihtl4+oJKFTuihwzSDLD5A3WGJ39cQysFOdWWUXf8s3WA/8eBN0d7rsPu5JqWtWY wiyOYdNs1H9ng== 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 8D731633A4 for ; Fri, 27 May 2022 16:45:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NJexn7jI"; 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 0CD3DD80; Fri, 27 May 2022 16:45:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662706; bh=J82y7/1hXVMiwHUAH2A1Gwma14uw5Jc//aizDlp0V5U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NJexn7jIz28GyRFzhNrihZBRpCdn1KKQJEKMFiECe3+6RBJSNuyeYQogtU53vZFAH Zl886O3tsnae1KuBh/sTRY/qNClVMQkf0w3CoS1ztAqIyLoDn7GbDChZDTzigYYirR x/R05H8uG9+jk8PJZCAzrKBSF7qNGgPBftfinpO8= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:21 +0300 Message-Id: <20220527144447.94891-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 04/30] 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 Fri May 27 14:44:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16076 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 1F95DBD161 for ; Fri, 27 May 2022 14:45:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 586196564D; Fri, 27 May 2022 16:45:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662713; 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=JOOW3pCOqAa9dr7YKMFzKdp9YhV6G2ZfOVMhOjNyq7fO9ACVKZOfMQCQkLW3PNd1E B8zWo05TP4rb+CXRa5zlVOADbBb2FH0gngfRwMVV06XY+getYvm4cqjjnIt92VPgYZ m8vy6wrA/P4SVNIro0xtEk7ryDVeUGykYx/7VbCZqYIFO4Xhr6NymNDOZtMQnyFtSy 085Qq1xY0dZNSmtxep3r3qJlJ4f594oxSYq5uXh7r3ARCHVPsC0ld393h31K8kjYik ttHoCBtPhi5IUmflhS8knkQoX+nz/6GoEjxi4lDGYMkq0at1jmRS56laQY7I4zPoj3 dDHeWCh7e9Jpw== 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 3E95A65637 for ; Fri, 27 May 2022 16:45:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HK2kAtBB"; 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 8855EFD1; Fri, 27 May 2022 16:45:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662706; bh=gB8wORxbh5RCxUzoZ/j9CClyLL96wF3CiB0KpQy77Fw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HK2kAtBBKCLS0LPZZUH9sooKYhbHt6BRZfOoGaNWyhU8D+x9zzMcB/Ph32WVp2V+Y jzbT3ZQG0StPLAr9hBn1I/0x7Vho5ECbJus5j9FhcFoSPrUa/Ts76V8H/kh60/Icsf oFSNCxnyui9b96tkOsB2RbCWT40qLisIohT5d5fw= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:22 +0300 Message-Id: <20220527144447.94891-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 05/30] 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 Fri May 27 14:44:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16077 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 DF276C3256 for ; Fri, 27 May 2022 14:45:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D5CEB65652; Fri, 27 May 2022 16:45:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662713; 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=nfSLscfAIxUKlHYesqCCqQNZWEI9WguK7zLesqOn3xiX4YEthwOuY0yoAeYyZEVKO e0/GlK2P/88oxppwJsuBBdkOo/F1/7VpGUsGRTYTsbrHag+dko2yS7DPVOcDEBjM1S 0s6Wk6BBxAilka+6MW1TOonTA8F4JDyrcj5S6rk8JxES9Bh7/iN5IVD0KKLwAe32d3 tr+O050JPuYM+LHTvo7rdwta4+DQTLN7B20cn2wYgi74v2WGbfDmRk0QzEvA95MjPk Jgu6DJbnYJ/Mnx2LflxTZwBiVoNsqdST31mpwJdwsfrCRFcQfZIhooYlt5cuJO3QRM 8DxNN8UfEVvQQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8E4EB65638 for ; Fri, 27 May 2022 16:45:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XiBouHCs"; 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 102D8FE0; Fri, 27 May 2022 16:45:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662707; bh=GrOP/OlQSS6f85g7v8fdN0LyeTNIso0TXXdoVOivEro=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XiBouHCsLOtymTd64JtfZUZt6VJBlSdW0imhR1U2dkIBHxPBx/q1uCJ/J2Rw+c/j8 RnrtJT5JpPOaN6u8x7x3k/t5N+prshAm64Kl4hXHCwQnDcAqFxjrYg6ukc8rgf112O JDeTOroOCeVg7sNsKhtf3vGlHH3pIbS+HTgVt3dM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:23 +0300 Message-Id: <20220527144447.94891-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 06/30] 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 Reviewed-by: Laurent Pinchart --- 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 Fri May 27 14:44:24 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16078 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 A20BAC326D for ; Fri, 27 May 2022 14:45:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0B0C065658; Fri, 27 May 2022 16:45:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662716; bh=8XqGJsPOn6Mb//x5iBtbhruddCZgxAWLKxp6Y2o8+n4=; 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=iCAbzqkBWosMmU5ojoNm7ThwbXRgxB64oWotUCXaUMcWtbj5Xhc8njuZbBeM6ZNvx 6CqF6uRISaXMnscRTrHkSHE7j/EqT1mF8ZGGGF3nUASlNo/imKXU9NuZcHvjW2gm2x 4wjNN2r6jM885cV6coDMwvKerVAGVRhapUOxlhK2a36KU5CDd6J9jAoWgO4Mtn6F41 iNf1fv2ssJjkWBznmt6IxCWAw/rJ8FvR2/2COYVO5yM23KyjF5EUCw9wIw4HXnC/no 4nvnxhiYFYmr8yuLcsdY2v6BBm0kvIY/eKUqrRpksp+5/eXTY3l30Y2HFJF467mv4y ZCVsyB3RZ5tCg== 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 1645D65639 for ; Fri, 27 May 2022 16:45:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QwYbFJa2"; 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 8BD2A10CA; Fri, 27 May 2022 16:45:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662707; bh=8XqGJsPOn6Mb//x5iBtbhruddCZgxAWLKxp6Y2o8+n4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QwYbFJa2rjxwKsoUiA/nRlQBOxBCdpwW//d3i/HhDnw+kM9HlvV+EzUac2aOULaeX 5OPGddESFpo96/MzUQ4HlMgui9lvTQsJ58cTt/xkRP1I09rAgMcl0pOtILR8DoRd53 zvPDdFXzZ/0bhLIUJlldJ97QtZbgJHJM1JpIO0Ng= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:24 +0300 Message-Id: <20220527144447.94891-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 07/30] 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 Fri May 27 14:44:25 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16079 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 5CAFABD161 for ; Fri, 27 May 2022 14:45:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 777B865657; Fri, 27 May 2022 16:45:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662716; bh=O1E83xEQOu12v5ZIMjQFl7+ZKcLZq6RJVyYUYyL7Wyo=; 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=eqIyXm8DtW+QJVuYWrdtNZ/TkRFCll5B+LngZ6yai/L/DT4NkxIn3HVnmOJ77pkhF DFWI5zgm44KzVaj9BbRud4QkInYSowKgAJxwOsgIeRz4tuEMZjBlnezPnJIWpdRmVM SX3UsV7yKLbhZv+TqtwlpudPnhabNTjlXFK+LKR4ZDyeaYBCOwUQjipwUKEhEq+xI3 GUSVIIkw1bIdrtLvn14kI5OKPvr4L1swxKnx+pebZQsPQQfF0HKZSE+K12KqciKuzk mWdqq8NDXP66IuoxjQ/fxVvavgG5UzdqrPFMRA03efkPO2WNCQXPoSMPhTvFlN7OYN msAkharm873ag== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A5B91633A3 for ; Fri, 27 May 2022 16:45:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="o6lzaJB7"; 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 19815EF2; Fri, 27 May 2022 16:45:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662708; bh=O1E83xEQOu12v5ZIMjQFl7+ZKcLZq6RJVyYUYyL7Wyo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=o6lzaJB7AgaGEc7/O6MXE8FKGOPYufZP6ustVbvgm1cxizACoxe24WrI2GURBVU6b oFxN+OUioY0Q55jHVr2JeHTJ6tA3tuOi01C/b83pvt4pC9Gs0ANoGwf2mfVK8UHEwH Nf7ifxQtpSU+NqFjjZv6SGgTiAE68wSS9bwximE0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:25 +0300 Message-Id: <20220527144447.94891-9-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 08/30] 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 Fri May 27 14:44:26 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16080 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 4EB66C326E for ; Fri, 27 May 2022 14:45:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B13FD65655; Fri, 27 May 2022 16:45:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662717; bh=mRI4K8BM6gjIEgTrzyaBiW5fZ9Mcb7eDb1PdyXPd77Y=; 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=QGMSTlUO8wzWQ71c7ckJDk7AgZZMHIvUqMuW0iYRg7+8Uetnjkd10aOwqJ9zgZ668 /fOF2iE8djwcBgs9bWuANPg4cFxrIA2dRMY0t9c7iUM2SNI4AlrnZ0plsm2clrztwQ RQGfjOfgKDvbwyN52BQPihcNOMGS/1TSdRAu94Wqct0QK+yr7I94J39Wq4HwziamSv Lles7oNV7L/7WDY9orFXpcfAX8btJ1sYsHyStM7q+uEiQhEs/GN1gq6g3nfeDe11Tk 5gi99GWNdblG12HOoif8xSAHuCswOAe1T4aaxC7byce76akP7Q3HnnC/C9tG9VLIHO zY7TrSeYSlrAg== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 314AC6563D for ; Fri, 27 May 2022 16:45:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PitKxECe"; 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 940D811CD; Fri, 27 May 2022 16:45:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662708; bh=mRI4K8BM6gjIEgTrzyaBiW5fZ9Mcb7eDb1PdyXPd77Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PitKxECeBu4NZ0reWrhzxZwAAs56ZYkvipR177CzZHCEnhhVjt98uiXtIP/OG5cfo fzxwFXQylmf7r82m8X5PwYc6rItXF9iXUCARoAgX55qTeiQmBYd+qD+1lzCF9l6CuR NCqu/KAU1Ab4xEp5FSgCjikkvS/qvcTSABggMDrU= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:26 +0300 Message-Id: <20220527144447.94891-10-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 09/30] 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 Fri May 27 14:44:27 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16082 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 6E646BD161 for ; Fri, 27 May 2022 14:45:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E9DF565634; Fri, 27 May 2022 16:45:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662723; bh=3XTegwOALyIVfX+/RY3EzWuXBIi8GmuOGgF1U0zPDdM=; 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=QKxe/16iatbShzLa/EigQNFFKfuPGcQqCCravbCGBt9MMkCXa+650Jw42qidQVYvZ vP4Q98dBDhiiOa5ftNXQ+mh5iplitAeki+IIA+/lhYSfTwB6iLiM/meRTKK6PdtMqr HXc54s8MtXyQoNDBIaQ85w3HGDqm1oJJLlKSWPQlpDVGetavjcv4ImX787XISCUsYZ ldSy8R+VcJh3fslBJCAofnAlu5LTWK5jOtv1UrTadOQp67IKiee0eD8hopPvtT9OrH N+o6J7MqzAbSR8RDSyYy7CWEGDrcX1hb5vs0a8i7JX835Z0edxD6C8seyvsgDJfPew cyxaL8hSyVVxQ== 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 B36E665632 for ; Fri, 27 May 2022 16:45:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nBmAeiJ8"; 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 1D68412F3; Fri, 27 May 2022 16:45:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662709; bh=3XTegwOALyIVfX+/RY3EzWuXBIi8GmuOGgF1U0zPDdM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=nBmAeiJ8ikwjqxpRRAdu/WJYHol/rxmyV1/3e/1BSSHA2bI5kKmkjcvzRI1f2JGW3 KlhyWGFJCmpvHraFCltAITNRK0GfVjxVXVQf46VC7FcCUJcOosGB3O7pxU2naMPBJy zXDKPRDE2sLaku4sEZ7XvnmHF3Eq0XzDUSIAjY60= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:27 +0300 Message-Id: <20220527144447.94891-11-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 10/30] 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 Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Convert ctx and state dicts to classes. No functional changes. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 580 ++++++++++++++++++++--------------------- 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, 310 insertions(+), 328 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 64f67e86..c8ffb084 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,371 @@ import sys import traceback -class CustomAction(argparse.Action): - def __init__(self, option_strings, dest, **kwargs): - super().__init__(option_strings, dest, default={}, **kwargs) - - 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) - - if self.type == bool: - values = True - - current = namespace.camera[-1] - - data = getattr(namespace, self.dest) - - if self.nargs == '+': - if current not in data: - data[current] = [] - - data[current] += values - else: - data[current] = values - - -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']) +class CameraContext: + camera: libcam.Camera + id: str + idx: int - for name, prop in camera.properties.items(): - print('\t{}: {}'.format(name, prop)) + opt_stream: str + opt_strict_formats: bool + opt_crc: bool + opt_metadata: bool + opt_save_frames: bool + opt_capture: int + 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 -def do_cmd_list_controls(ctx): - camera = ctx['camera'] + def __init__(self, camera, idx): + self.camera = camera + self.idx = idx + self.id = 'cam' + str(idx) + self.reqs_queued = 0 + self.reqs_completed = 0 - print('Controls for', ctx['id']) + def do_cmd_list_props(self): + print('Properties for', self.id) - for name, prop in camera.controls.items(): - print('\t{}: {}'.format(name, prop)) + for name, prop in self.camera.properties.items(): + print('\t{}: {}'.format(name, prop)) + def do_cmd_list_controls(self): + print('Controls for', self.id) -def do_cmd_info(ctx): - camera = ctx['camera'] + for name, prop in self.camera.controls.items(): + print('\t{}: {}'.format(name, prop)) - print('Stream info for', ctx['id']) + def do_cmd_info(self): + 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 = self.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): + self.camera.acquire() -def acquire(ctx): - camera = ctx['camera'] + def release(self): + self.camera.release() - camera.acquire() + def __parse_streams(self): + streams = [] + for stream_desc in self.opt_stream: + stream_opts: dict[str, Any] + stream_opts = {'role': libcam.StreamRole.Viewfinder} -def release(ctx): - camera = ctx['camera'] + for stream_opt in stream_desc.split(','): + if stream_opt == 0: + continue - camera.release() - - -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) - - if role is None: - print('Bad stream role', value) + arr = stream_opt.split('=') + if len(arr) != 2: + print('Bad stream option', stream_opt) sys.exit(-1) - value = role - elif key == 'pixelformat': - pass - else: - print('Bad stream option key', key) - sys.exit(-1) - - stream_opts[key] = value - - streams.append(stream_opts) + 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) - return streams + stream_opts[key] = value + streams.append(stream_opts) -def configure(ctx): - camera = ctx['camera'] + return streams - streams = parse_streams(ctx) + def configure(self): + streams = self.__parse_streams() - roles = [opts['role'] for opts in streams] + roles = [opts['role'] for opts in streams] - camconfig = camera.generate_configuration(roles) - if camconfig is None: - raise Exception('Generating config failed') + camconfig = self.camera.generate_configuration(roles) + if camconfig is None: + raise Exception('Generating config failed') - for idx, stream_opts in enumerate(streams): - stream_config = camconfig.at(idx) + for idx, stream_opts in enumerate(streams): + stream_config = camconfig.at(idx) - if 'width' in stream_opts: - stream_config.size.width = stream_opts['width'] + if 'width' in stream_opts: + stream_config.size.width = stream_opts['width'] - if 'height' in stream_opts: - stream_config.size.height = stream_opts['height'] + if 'height' in stream_opts: + stream_config.size.height = stream_opts['height'] - if 'pixelformat' in stream_opts: - stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) + if 'pixelformat' in stream_opts: + stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat']) - stat = camconfig.validate() + stat = camconfig.validate() - 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') + 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) - print('Camera configuration adjusted') - - r = camera.configure(camconfig) - if r != 0: - raise Exception('Configure failed') - - ctx['stream-names'] = {} - ctx['streams'] = [] - - 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)) - - -def alloc_buffers(ctx): - camera = ctx['camera'] - - allocator = libcam.FrameBufferAllocator(camera) + print('Camera configuration adjusted') - for idx, stream in enumerate(ctx['streams']): - ret = allocator.allocate(stream) - if ret < 0: - print('Cannot allocate buffers') - exit(-1) + r = self.camera.configure(camconfig) + if r != 0: + raise Exception('Configure failed') - allocated = len(allocator.buffers(stream)) + self.stream_names = {} + self.streams = [] - print('{}-{}: Allocated {} buffers'.format(ctx['id'], ctx['stream-names'][stream], allocated)) + 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['allocator'] = allocator + def alloc_buffers(self): + allocator = libcam.FrameBufferAllocator(self.camera) + for stream in self.streams: + ret = allocator.allocate(stream) + if ret < 0: + print('Cannot allocate buffers') + exit(-1) -def create_requests(ctx): - camera = ctx['camera'] + allocated = len(allocator.buffers(stream)) - ctx['requests'] = [] + print('{}-{}: Allocated {} buffers'.format(self.id, self.stream_names[stream], allocated)) - # Identify the stream with the least number of buffers - num_bufs = min([len(ctx['allocator'].buffers(stream)) for stream in ctx['streams']]) + self.allocator = allocator - requests = [] + def create_requests(self): + self.requests = [] - for buf_num in range(num_bufs): - request = camera.create_request(ctx['idx']) + # Identify the stream with the least number of buffers + num_bufs = min([len(self.allocator.buffers(stream)) for stream in self.streams]) - if request is None: - print('Can not create request') - exit(-1) + requests = [] - for stream in ctx['streams']: - buffers = ctx['allocator'].buffers(stream) - buffer = buffers[buf_num] + for buf_num in range(num_bufs): + request = self.camera.create_request(self.idx) - ret = request.add_buffer(stream, buffer) - if ret < 0: - print('Can not set buffer for request') + if request is None: + print('Can not create request') exit(-1) - requests.append(request) + for stream in self.streams: + buffers = self.allocator.buffers(stream) + buffer = buffers[buf_num] - ctx['requests'] = requests + ret = request.add_buffer(stream, buffer) + if ret < 0: + print('Can not set buffer for request') + exit(-1) + requests.append(request) -def start(ctx): - camera = ctx['camera'] + self.requests = requests - camera.start() + def start(self): + self.camera.start() + def stop(self): + self.camera.stop() -def stop(ctx): - camera = ctx['camera'] + def queue_requests(self): + for request in self.requests: + self.camera.queue_request(request) + self.reqs_queued += 1 - camera.stop() + del self.requests -def queue_requests(ctx): - camera = ctx['camera'] +class CaptureState: + cm: libcam.CameraManager + contexts: list[CameraContext] + renderer: Any - for request in ctx['requests']: - camera.queue_request(request) - ctx['reqs-queued'] += 1 + def __init__(self, cm, contexts): + self.cm = cm + self.contexts = contexts - del ctx['requests'] + # Called from renderer when there is a libcamera event + def event_handler(self): + try: + self.cm.read_event() + reqs = self.cm.get_ready_requests() -def capture_init(contexts): - for ctx in contexts: - acquire(ctx) + for req in reqs: + ctx = next(ctx for ctx in self.contexts if ctx.idx == req.cookie) + self.__request_handler(ctx, req) - for ctx in contexts: - configure(ctx) + running = any(ctx.reqs_completed < ctx.opt_capture for ctx in self.contexts) + return running + except Exception: + traceback.print_exc() + return False - for ctx in contexts: - alloc_buffers(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: - create_requests(ctx) + buffers = req.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.last + fps = 1000000000.0 / (ts - last) if (last != 0 and (ts - last) != 0) else 0 + ctx.last = ts + ctx.fps = fps -def capture_start(contexts): - for ctx in contexts: - start(ctx) + for stream, fb in buffers.items(): + stream_name = ctx.stream_names[stream] - for ctx in contexts: - queue_requests(ctx) + 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) + meta = fb.metadata -# Called from renderer when there is a libcamera event -def event_handler(state): - try: - cm = state['cm'] - contexts = state['contexts'] + print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' + .format(ts / 1000000000, fps, + ctx.id, stream_name, + meta.sequence, meta.bytesused, + crcs)) - cm.read_event() + if ctx.opt_metadata: + reqmeta = req.metadata + for ctrl, val in reqmeta.items(): + print(f'\t{ctrl} = {val}') - reqs = cm.get_ready_requests() + 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) - for req in reqs: - ctx = next(ctx for ctx in contexts if ctx['idx'] == req.cookie) - request_handler(state, ctx, req) + self.renderer.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 + ctx.reqs_completed += 1 + # Called from renderer when it has finished with a request + def request_processed(self, ctx, req): + if ctx.reqs_queued < ctx.opt_capture: + req.reuse() + ctx.camera.queue_request(req) + ctx.reqs_queued += 1 -def request_handler(state, ctx, req): - if req.status != libcam.Request.Status.Complete: - raise Exception('{}: Request failed: {}'.format(ctx['id'], req.status)) + def __capture_init(self): + for ctx in self.contexts: + ctx.acquire() - buffers = req.buffers + for ctx in self.contexts: + ctx.configure() - # 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.alloc_buffers() - for stream, fb in buffers.items(): - stream_name = ctx['stream-names'][stream] + for ctx in self.contexts: + ctx.create_requests() - 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(self): + for ctx in self.contexts: + ctx.start() - meta = fb.metadata + for ctx in self.contexts: + ctx.queue_requests() - print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' - .format(ts / 1000000000, fps, - ctx['id'], stream_name, - meta.sequence, meta.bytesused, - crcs)) + def __capture_deinit(self): + for ctx in self.contexts: + ctx.stop() - if ctx['opt-metadata']: - reqmeta = req.metadata - for ctrl, val in reqmeta.items(): - print(f'\t{ctrl} = {val}') + for ctx in self.contexts: + ctx.release() - 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) + def do_cmd_capture(self): + self.__capture_init() - state['renderer'].request_handler(ctx, req) + self.renderer.setup() - ctx['reqs-completed'] += 1 + self.__capture_start() + self.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 +415,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 +454,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 Fri May 27 14:44:28 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16083 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 7F1F4C326D for ; Fri, 27 May 2022 14:45:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1543A6564A; Fri, 27 May 2022 16:45:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662724; bh=k7Le+XS6rpj2VkjjYXVlhnP5FKg9Q20/vIYaMFUH6WQ=; 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=qxRljEObcG5aJ9Dyrlw8g4Am0LsjjYfdHxakYgxisyXn8mVdElQLu9roNyq/1zB1i tmA857P1I5paz9mOjbxD41yr5KOO5m7kmhYK7wFPu8rf7VVrLf4r4mxhyNJWSsHTXH 7bURZVNKhPB23h7nBMmAJZMWT+Vm45RKfRf3PSPpXt9TjoCM7FaT7VM1RMEb3PhOak Mgq0kDN26Zt3RUsm1bN4bbrr3g2N50DRrqzuHoM+F8goQ35KEyypjC9wVrs4OX3QIV w3JYEerI2NqBEEJX9GpCHwFAkffKR0IedcbqIthmh36NDZpzPJhOSRu+3daVbhnR+E cGUQtaBZllASg== 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 3B89065634 for ; Fri, 27 May 2022 16:45:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pXwr1K4U"; 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 A52B71248; Fri, 27 May 2022 16:45:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662710; bh=k7Le+XS6rpj2VkjjYXVlhnP5FKg9Q20/vIYaMFUH6WQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pXwr1K4UQ344VM2zKDggc9+7X6MKQR69tvfHVCY5uWoah/72JAJ2YBCgAu59fProu VYvJGcZ1D+oUIOQYAQvFjoaebyRsRWN4gSwrdvd69gWAe6ElIfkBnx705IPBG9KSDe YHAuJvQ9DDAB9DRs+oco8XarF+t5NKqpNsH2K+sY= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:28 +0300 Message-Id: <20220527144447.94891-12-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 11/30] 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 | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 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..74a8cd9f --- /dev/null +++ b/src/py/README.md @@ -0,0 +1,60 @@ +# 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 the Python side follows the 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 do 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 Fri May 27 14:44:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16081 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 34E14C3256 for ; Fri, 27 May 2022 14:45:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AD5D76569B; Fri, 27 May 2022 16:45:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662721; bh=xjZDfH58LT4jSfynEsxb2kPyIgf1vbSIf/kjeuwUJB4=; 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=q1I9zsiyHW90dBoPOCUS/XEu7C1V/hn3cKyMDAltDT2V+RKKJ6SquAFghqXPYrqjK IYknmmo70pjnMUFVFLkNN9RinzJpZahBMQiyDu71Xbrr3a1K/KbBVyHMcDynV3BkBJ p0tLiEs79XOdK48aavelDvNp8Eh3PN5VUUR0CJ7hyZDmJBJ0vJi9TYYWN7p4kShNGp xoMhlCcbDUGpXVJb2yPRdU+ZdDQWG6wnTWVJYd64c/jDBq3NmMQezGsEu89mvBwDWt gu3wU2l/2KFPphKbgg4Oa+2A5D+JgIPvcStDR7V+S6fvFf6hrKeHyoTQp6xER60kRE rSwsmijZTyvbg== 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 B1D3465641 for ; Fri, 27 May 2022 16:45:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SxStX9KD"; 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 2E7A9A58; Fri, 27 May 2022 16:45:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662710; bh=xjZDfH58LT4jSfynEsxb2kPyIgf1vbSIf/kjeuwUJB4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SxStX9KDNKfDXdXwYWlxv7Y6/S+e/ETyPyD8FnpY4aQDLAj6nwXY6TGcBiSZO3m7c N3apYhfuDkAxHu9S0Wk/6fqWBfg+fHOUJcMAxEec3PO9TezikqBZlveFTBZmgBAKPX TXq5aHY9BBRTed4iq9KY2K6WlKC+iRdn4HqgF4aM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:29 +0300 Message-Id: <20220527144447.94891-13-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 12/30] 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..4c072e60 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' + + out += f'\t{container}.def_readonly_static("{name}", static_cast(&{ns}{name}));\n\n' + + enum = ctrl.get('enum') + if not enum: + continue cpp_enum = name + 'Enum' - out += '\tpy::enum_<{}{}>(m, \"{}\")\n'.format(ns, cpp_enum, name) + 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 Fri May 27 14:44:30 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16084 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 B30CDC3256 for ; Fri, 27 May 2022 14:45:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4EBA165697; Fri, 27 May 2022 16:45:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662725; bh=KjPMCtIKmKLBPOuGyREMU890hdHrQ1ucqHg7xWG38eo=; 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=m4x8yLKb0TNSfQwx8e0qIOWgX+4BaktUO7LvB+QPfhy5a4Rrq5Q6xsruPlq4w/w8B LRjwXU/8HToef2L/OkrMau8lf+R1J5OppQYBH6U8OFEfq2FxAzvGHvQIKhWJZwrVJm 5xAYrMG1RYZ1UzEFeeFyyiq3tM28Ni3fcqwiBLkmBdNP/dfYcOlP2bWpxT5KaVA6F5 JqGzTUrlViTiWIzJ6+kK7vXnipurjfANCulN2nEsMjKIm+vfc1SNC1YIKW41Jn3Nx7 XXyYqucMkwpkPPRHBky/FMbgKjkKebLUXHtkiF+GQPcFt1QrVKoTFr16hICgvQ2PP+ kFhZrfdxP2uhA== 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 410E865644 for ; Fri, 27 May 2022 16:45:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tE2Xh/B8"; 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 AA0B81440; Fri, 27 May 2022 16:45:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662711; bh=KjPMCtIKmKLBPOuGyREMU890hdHrQ1ucqHg7xWG38eo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tE2Xh/B88ZhbKPJypIhMcEM2UCgdg4RAeHo243iQYH7gITNuehi9P3+ilqdHo3gU/ C0Tf6Dery1F2T/EoYMA1z6oB2kVKZEH6STMb2rBtSMUP/9L2h3QoP+Fu6mXJ8dqKcn ZqK2NU8XwjFCrrmq+YoMiyXR8beCXKlGrkWXyEBo= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:30 +0300 Message-Id: <20220527144447.94891-14-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 13/30] 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 | 104 +++++++++++++++++------------------ 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index c8ffb084..677dd680 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -46,14 +46,14 @@ class CameraContext: def do_cmd_list_props(self): print('Properties for', self.id) - for name, prop in self.camera.properties.items(): - print('\t{}: {}'.format(name, prop)) + for cid, val in self.camera.properties.items(): + print('\t{}: {}'.format(cid, val)) def do_cmd_list_controls(self): print('Controls for', self.id) - for name, prop in self.camera.controls.items(): - print('\t{}: {}'.format(name, prop)) + for cid, info in self.camera.controls.items(): + print('\t{}: {}'.format(cid, info)) def do_cmd_info(self): print('Stream info for', self.id) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index d638e9cc..e2395c4b 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..4fb66866 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -5,10 +5,6 @@ * Python bindings */ -/* - * \todo Add bindings for the ControlInfo class - */ - #include #include #include @@ -159,6 +155,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 +257,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(); - - auto it = std::find_if(controlMap.begin(), controlMap.end(), - [&key](const auto &kvp) { - return kvp.first->name() == key; - }); + ControlList controlList(self.controls()); - 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 +277,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 +327,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 +436,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 +474,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 Fri May 27 14:44:31 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16085 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 7A12FC326E for ; Fri, 27 May 2022 14:45:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F3A656569D; Fri, 27 May 2022 16:45:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662726; bh=toxqdvjDi4gk1YUfvMhAi3fdnpw79VdIr0qdXJMmdUs=; 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=rBbenwRz00L1p47UI23xJCgUcfVm/RPW8twJKhO5apfC329JrnEYA4eGXi1Qc7ord mpxqkmrc+HyBC/7lRqFV/zoWyi2WVAKJP3lDzJbx/9vVc5U84pVkQAbVgHyquoOzti cGaMwf9hy1E4hAUzAB9rfRRKKCncBMWPG93/OZkmKR7VnKeb9eCWIazLzlZvYmapUe 1Yp7heheh2RC1Yr1R9SQEXma2u09KIHMlnq4A7aLRaNf3cE/piY9/vERX+6jAubak4 cXAIPAyR4aMcFF3N7D7RSlUZmUfZWnQ50Bfn4y9rxYUdKEO1ekvcGK+p07PjbfPiLx uGsrk1jHPor1g== 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 B2ECC65633 for ; Fri, 27 May 2022 16:45:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZfYb17pf"; 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 306BF1BCB; Fri, 27 May 2022 16:45:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662711; bh=toxqdvjDi4gk1YUfvMhAi3fdnpw79VdIr0qdXJMmdUs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZfYb17pfkg6O33L/zEAszaKTkmPPt1zCDmuTs1NV/XipYeIjAiV4zyp6BHOpO3AfN Vaql2Aq1P4Z/+h1N3voRVxDLhgJ5hzKzmKU5FeAqT7BOz+NXUmYDTzZgBswt1VQFub iDp7O3QKguUF/eLto/ck9QaEFVYVVYjMkhpexLGs= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:31 +0300 Message-Id: <20220527144447.94891-15-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 14/30] 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 Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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/libcamera/py_main.cpp | 2 +- test/py/unittests.py | 2 +- 6 files changed, 6 insertions(+), 6 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 e2395c4b..c294c999 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/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 4fb66866..f903f1d2 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -205,7 +205,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 Fri May 27 14:44:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16087 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 BD12EC326D for ; Fri, 27 May 2022 14:45:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4F19F65636; Fri, 27 May 2022 16:45:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662728; bh=6RKw8LfMXsi6/Us3IdxwjAaG3NpSyQzKwfTXix2+MuA=; 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=uuGpUQpYZV4dNBX72JJisO5TwHye6E1XiZQ3EKH5niOD5B5tTCdeOQ50bXH/4S8Yo PJXVTF/bEAjWlDP+kOavobmBD3rfomhFB97VAad/0TZjf99cgqL/mT0us+lJP/FuBu b5TXmhaghcp6WBuu0REBwgeajzaFppLEtJpS2olRHctAv32wAuP/VWlWC+6ndDAlvn CKuR9BlkNPTT7KKDB1M6JNm0Kv9wK8SdsFoTPU7tHq6iUYscGm4gRe6V80AYdcI+Si /1Xitruc6+4kFi+QoO/BdsQwjFK7/MxpplyUY7wYKzxvhf+GhroQvNKVzCBR0/AHg7 cvn8IlDWkWNkg== 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 3B8F3633A6 for ; Fri, 27 May 2022 16:45:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cPuMpdnO"; 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 A916E1C26; Fri, 27 May 2022 16:45:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662712; bh=6RKw8LfMXsi6/Us3IdxwjAaG3NpSyQzKwfTXix2+MuA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cPuMpdnOn6GBWA+Wvu/2aSKB9gBwfTksm8/1Y9uz93e7WAWUg4btwzQsmuRFvNWMU qxqpADdm7xMtiRXgbrOdic2rPVsxB4zUuSWDgjn86w1T2+iU32+BUveqGVZHK5sL2p QnKmWrmA5zs+mAGt+0KqK15WGn0Z8CtHwB6z3qIQ= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:32 +0300 Message-Id: <20220527144447.94891-16-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 15/30] 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 | 29 ++++++++++++------ src/py/libcamera/meson.build | 14 ++++++++- src/py/libcamera/py_main.cpp | 2 ++ .../libcamera/py_properties_generated.cpp.in | 30 +++++++++++++++++++ 4 files changed, 65 insertions(+), 10 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 4c072e60..99f3bbcf 100755 --- a/src/py/libcamera/gen-py-controls.py +++ b/src/py/libcamera/gen-py-controls.py @@ -21,17 +21,17 @@ def find_common_prefix(strings): return prefix -def generate_py(controls): +def generate_py(controls, mode): out = '' for ctrl in controls: name, ctrl = ctrl.popitem() if ctrl.get('draft'): - ns = 'libcamera::controls::draft::' + ns = 'libcamera::{}::draft::'.format(mode) container = 'draft' else: - ns = 'libcamera::controls::' + ns = 'libcamera::{}::'.format(mode) container = 'controls' out += f'\t{container}.def_readonly_static("{name}", static_cast(&{ns}{name}));\n\n' @@ -44,12 +44,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 mode == 'controls': + # Adjustments for controls + if name == 'LensShadingMapMode': + prefix = 'LensShadingMapMode' + elif name == 'SceneFlicker': + # If we strip the prefix, we would get '50Hz', which is illegal name + prefix = '' + 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 +84,18 @@ def main(argv): help='Input file name.') parser.add_argument('template', type=str, help='Template file name.') + parser.add_argument('--mode', type=str, required=True, + help='Mode is either "controls" or "properties"') args = parser.parse_args(argv[1:]) + if args.mode not in ['controls', 'properties']: + print(f'Invalid mode option "{args.mode}"', file=sys.stderr) + return -1 + data = open(args.input, 'rb').read() controls = yaml.safe_load(data)['controls'] - data = generate_py(controls) + data = generate_py(controls, args.mode) data = fill_template(args.template, data) diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index e8010846..afa7efed 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -30,7 +30,19 @@ gen_py_controls = files('gen-py-controls.py') pycamera_sources += custom_target('py_gen_controls', input : gen_py_controls_input_files, output : ['py_controls_generated.cpp'], - command : [gen_py_controls, '-o', '@OUTPUT@', '@INPUT@']) + command : [gen_py_controls, '--mode', '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, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@']) # Generate formats diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index f903f1d2..3220a9e6 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -130,12 +130,14 @@ void init_py_enums(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); +void init_py_properties_generated(py::module &m); PYBIND11_MODULE(_libcamera, m) { init_py_enums(m); init_py_controls_generated(m); init_py_geometry(m); + init_py_properties_generated(m); /* Forward declarations */ 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 Fri May 27 14:44:33 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16088 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 E68FCC3256 for ; Fri, 27 May 2022 14:45:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6DDCF65698; Fri, 27 May 2022 16:45:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662729; bh=AcfzRhTB4PZcNAUcD0JfUuGlTuBR72Av7afYHoJuei0=; 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=JMNXpP6VBTsHYXW3RixR14sJnl6X+kJtIXHYzJ+cIES4Fc9lz6Kq8oLyh5/7oRQr5 CqxKHbIsupa8gtDWrjm9oz8bQ3X8+enKaWqB9cKZ4gzAoqCapqWulwfxjzQAgbua35 Wkf3ruczoFO4mC6mBph9IrdMNQBiwmYmZ7MgoPxqCZsIDan5mlSLf+eUgnP/SGhyEj 46tIUV0bru8j+dnzdV1Wn5Z4bS0bcykjGvxXY7qUpgQCdjgvYUms4fwcRT482DwL9s M1Rx0p2W2lHQNtKnEGdwjyNf+oA1XsHLeDT6xBoXW9qU0C6YmkXwM92dJak5aOk2rf TJ5jcT62AVcqQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C638D6564A for ; Fri, 27 May 2022 16:45:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LAzqLM9A"; 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 37DA132A; Fri, 27 May 2022 16:45:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662712; bh=AcfzRhTB4PZcNAUcD0JfUuGlTuBR72Av7afYHoJuei0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LAzqLM9AL3REozNcZNWcHVd+4jVLGDjwoJwuFHYTU+4qgzGbfp3o6eNmZaYLZ7BGk MwKYndZa+pdaIqM9B5Hzqf2RLY5nT2UEvs98p8dTWw+zOSPNZMumuYueA8cWwSgZSl I3yJU//3CNNzRqLcrT3no+l0RuUv7VoMi2UBBLbU= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:33 +0300 Message-Id: <20220527144447.94891-17-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 16/30] 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 afa7efed..eb884538 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) From patchwork Fri May 27 14:44:34 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16086 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 18F7CBD161 for ; Fri, 27 May 2022 14:45:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F0AC65699; Fri, 27 May 2022 16:45:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662726; bh=VBDpZl+zXtAcncwxGXSiCPQILej/92/BtQsMZ+bUMPw=; 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=0tSJMHfJSmol8huyYipkHf7UJ8jbtPAYPOvobTX0Y7Bwu5NE+9dz0S8+6uusmqMs3 s7GduJh0OZThX5whL8EVIEVBahyRQzMyoobh1c55Jmka9fbfDbjQkcOjY+Szb2UCZE ZYy7a8Fp+IH0KOvXp1FiPAYT88/fccUisjmNXRK/e12wXHs4vh137QLHrIUGT+gd64 WeKucppt0EBCjB4oAbY9/P4TClc/V64Fffo2UC76AkaPNfToLtIouuO+rhoTcdgtHf VVOulI+QjMWK5/yiU9aUAw7hX5l69jGEustllH9a+gAZjjqdzfZqiZ0UxGorQ47Zu7 +c1p8TapwNNFA== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4FC226564C for ; Fri, 27 May 2022 16:45:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Cshmfq0e"; 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 B4F751447; Fri, 27 May 2022 16:45:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662713; bh=VBDpZl+zXtAcncwxGXSiCPQILej/92/BtQsMZ+bUMPw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Cshmfq0ex8YZ2lgaGrYFdKpMUZw7ycgNgUlijw36J38JtnZ2g6kBA4ZblXnUiU8P/ GOJSpaOFXGVnWlxFMYaxUzxiGxULdLceBzYmfpRpaRMDV2u/hb5i06yHI1iiGCIQAo ZXXijID/bhk7abtwlIxvhAMV2+dZPNE71bYExkwM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:34 +0300 Message-Id: <20220527144447.94891-18-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 17/30] py: unittests: Fix test_sleep() 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" Waiting for 0.5 secs and expecting that the requests have been completed is... bad. Fix the test case by using cam.read_event() as a blocking wait, and wait until we have received all the requests that we queued. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- test/py/unittests.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/py/unittests.py b/test/py/unittests.py index 426efb06..45b35223 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -161,7 +161,7 @@ class AllocatorTestMethods(CameraTesterBase): class SimpleCaptureMethods(CameraTesterBase): - def test_sleep(self): + def test_blocking(self): cm = self.cm cam = self.cam @@ -207,11 +207,17 @@ class SimpleCaptureMethods(CameraTesterBase): reqs = None gc.collect() - time.sleep(0.5) + reqs = [] - reqs = cm.get_ready_requests() + while True: + cm.read_event() - self.assertTrue(len(reqs) == num_bufs) + ready_reqs = cm.get_ready_requests() + + reqs += ready_reqs + + if len(reqs) == num_bufs: + break for i, req in enumerate(reqs): self.assertTrue(i == req.cookie) From patchwork Fri May 27 14:44:35 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16090 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 5D112BD161 for ; Fri, 27 May 2022 14:45:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C99636564E; Fri, 27 May 2022 16:45:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662731; bh=H/Jb8v2zc35+zHkHqMvpT5G28m2rg6O/MIcSOu1mjLo=; 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=mQA4iUZ11tNCHCLTPciYZYErv99z0C3wZHtC8nuDFEqpd//tRWg4xjnVONusd2Dhf ccmWCH+vThcVhB2HwikMjoyruHRCGAjiOSvizU6O3vpusHNLSNEUJ8shHqRLd0EUQv 8lJO3jJjhbhXtu+KsW4CDHUh6eG0UqXvmn48bLpeTcv/tCYJNLhU9wy+aKH2jJIdSZ ipkM6lEr9anaUjC3sXHCOtuC9/95/FjYIu5homC+LbRfr861SHC6Cmw8vZ6QIdi6ZS mkfhCDPYcWoWOaxHT3HWnPa3y0Ej5iXqO1LyfAxrSOXFvb0B0Qe/WcLOXHUELg7oHB HS3yeDikkO77w== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C7E5465651 for ; Fri, 27 May 2022 16:45:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="D82FgnI2"; 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 3B4A33C16; Fri, 27 May 2022 16:45:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662713; bh=H/Jb8v2zc35+zHkHqMvpT5G28m2rg6O/MIcSOu1mjLo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=D82FgnI2vMm6k3pKCA4HJC1aTI2CsuOBFnejOn1Ek2ac36eTp3Tvo05wkJDhcOOLU iaXg3pePMBgUQs9DLmf93FcYqDjCpduqwchXJOrFTSPGHaIFUb9IM7NMEyrNy99GnK Y/cpT+j/77vyxEwfOh//Sm0t3wvaHmPyZFODcYQg= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:35 +0300 Message-Id: <20220527144447.94891-19-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 18/30] py: unittests: Fix test_select() 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 test_select() current uses self.assertTrue(len(ready_reqs) > 0) to see that cm.get_ready_requests() returns something. This is not always the case, as there may be two eventfd events queued, and the first call to cm.get_ready_requests() returns all the requests, and thus the second call returns none. Remove the self.assertTrue(len(ready_reqs) > 0) assert. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- test/py/unittests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/py/unittests.py b/test/py/unittests.py index 45b35223..33b35a0a 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -287,8 +287,6 @@ class SimpleCaptureMethods(CameraTesterBase): ready_reqs = cm.get_ready_requests() - self.assertTrue(len(ready_reqs) > 0) - reqs += ready_reqs if len(reqs) == num_bufs: From patchwork Fri May 27 14:44:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16089 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 051E6C326E for ; Fri, 27 May 2022 14:45:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 93B34656A0; Fri, 27 May 2022 16:45:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662730; bh=sx7lC/g+oXgtl0/KoyDSOnBN1Tlvwis7V8iZQfxBzJk=; 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=xOxE5gEhI8Ulw5PQbr7PHqpB4RUOTHBk2owLV+QAwtIvQTfgzQ3FGvP5VnBcq8hr8 7dD5IteqWMbSPn6Rqt3BIKF4EVUekcZx2kqEYMjgd63E/JYFs8rkSEkXyf06f6Yxhw H3JdCLm4hAVcF9saPW1cgo2CXh2eqIeGGkL14urAshdQge8gHrRPigTIqbLUaOcJvK gQSdtbynhJ27/FKo4pzBuCSzKkFO0nMFoFJnWiraWAWh6Gdel2CLo/WT3n4bfkIQ7c ysa35hG6wDarVU4xPryGoigYE5K4kFBtft21aKBUpKGROqiSN70sXF5NP/6FBPJNt1 el8xTlgCpQ6PQ== 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 5B70A65639 for ; Fri, 27 May 2022 16:45:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hj/sO7Vg"; 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 B833D3B8C; Fri, 27 May 2022 16:45:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662714; bh=sx7lC/g+oXgtl0/KoyDSOnBN1Tlvwis7V8iZQfxBzJk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hj/sO7VgTApSxyzCBdGqHFnRl024RG46X/5hnEV5yFwHiWM/L7lxAROXy72HyzhyZ 6YERr+8ZhU1hx0q8Cj4q+ufiW5QneAYLQSe4WtekW1m4ChDwyhA8odAk/qeSPH2F2R Kc7MBjvUwMgPv17Ewa5F+k542MgxTbmqwxo/hI+o= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:36 +0300 Message-Id: <20220527144447.94891-20-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 19/30] py: cam: Move conversion funcs to helpers.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" Move conversion functions from cam_qt.py to helpers.py to clean up the code and so that they can be used from other cam renderers. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_qt.py | 157 +-------------------------- src/py/cam/{cam_qt.py => helpers.py} | 155 +------------------------- 2 files changed, 2 insertions(+), 310 deletions(-) copy src/py/cam/{cam_qt.py => helpers.py} (55%) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index c294c999..af4b0f86 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -1,19 +1,15 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2022, Tomi Valkeinen -# -# Debayering code from PiCamera documentation +from helpers import mfb_to_rgb from io import BytesIO -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 libcamera.utils -import numpy as np import sys - def rgb_to_pix(rgb): img = Image.frombuffer('RGB', (rgb.shape[1], rgb.shape[0]), rgb) qim = ImageQt(img).copy() @@ -21,157 +17,6 @@ def rgb_to_pix(rgb): return pix -def demosaic(data, r0, g0, g1, b0): - # Separate the components from the Bayer data to RGB planes - - rgb = np.zeros(data.shape + (3,), dtype=data.dtype) - rgb[r0[1]::2, r0[0]::2, 0] = data[r0[1]::2, r0[0]::2] # Red - rgb[g0[1]::2, g0[0]::2, 1] = data[g0[1]::2, g0[0]::2] # Green - rgb[g1[1]::2, g1[0]::2, 1] = data[g1[1]::2, g1[0]::2] # Green - rgb[b0[1]::2, b0[0]::2, 2] = data[b0[1]::2, b0[0]::2] # Blue - - # Below we present a fairly naive de-mosaic method that simply - # calculates the weighted average of a pixel based on the pixels - # surrounding it. The weighting is provided by a byte representation of - # the Bayer filter which we construct first: - - bayer = np.zeros(rgb.shape, dtype=np.uint8) - bayer[r0[1]::2, r0[0]::2, 0] = 1 # Red - bayer[g0[1]::2, g0[0]::2, 1] = 1 # Green - bayer[g1[1]::2, g1[0]::2, 1] = 1 # Green - bayer[b0[1]::2, b0[0]::2, 2] = 1 # Blue - - # Allocate an array to hold our output with the same shape as the input - # data. After this we define the size of window that will be used to - # calculate each weighted average (3x3). Then we pad out the rgb and - # bayer arrays, adding blank pixels at their edges to compensate for the - # size of the window when calculating averages for edge pixels. - - output = np.empty(rgb.shape, dtype=rgb.dtype) - window = (3, 3) - borders = (window[0] - 1, window[1] - 1) - border = (borders[0] // 2, borders[1] // 2) - - rgb = np.pad(rgb, [ - (border[0], border[0]), - (border[1], border[1]), - (0, 0), - ], 'constant') - bayer = np.pad(bayer, [ - (border[0], border[0]), - (border[1], border[1]), - (0, 0), - ], 'constant') - - # For each plane in the RGB data, we use a nifty numpy trick - # (as_strided) to construct a view over the plane of 3x3 matrices. We do - # the same for the bayer array, then use Einstein summation on each - # (np.sum is simpler, but copies the data so it's slower), and divide - # the results to get our weighted average: - - for plane in range(3): - p = rgb[..., plane] - b = bayer[..., plane] - pview = as_strided(p, shape=( - p.shape[0] - borders[0], - p.shape[1] - borders[1]) + window, strides=p.strides * 2) - bview = as_strided(b, shape=( - b.shape[0] - borders[0], - b.shape[1] - borders[1]) + window, strides=b.strides * 2) - psum = np.einsum('ijkl->ij', pview) - bsum = np.einsum('ijkl->ij', bview) - output[..., plane] = psum // bsum - - return output - - -def to_rgb(fmt, size, data): - w = size.width - h = size.height - - if fmt == libcam.formats.YUYV: - # YUV422 - yuyv = data.reshape((h, w // 2 * 4)) - - # YUV444 - yuv = np.empty((h, w, 3), dtype=np.uint8) - yuv[:, :, 0] = yuyv[:, 0::2] # Y - yuv[:, :, 1] = yuyv[:, 1::4].repeat(2, axis=1) # U - yuv[:, :, 2] = yuyv[:, 3::4].repeat(2, axis=1) # V - - m = np.array([ - [1.0, 1.0, 1.0], - [-0.000007154783816076815, -0.3441331386566162, 1.7720025777816772], - [1.4019975662231445, -0.7141380310058594, 0.00001542569043522235] - ]) - - rgb = np.dot(yuv, m) - rgb[:, :, 0] -= 179.45477266423404 - rgb[:, :, 1] += 135.45870971679688 - rgb[:, :, 2] -= 226.8183044444304 - rgb = rgb.astype(np.uint8) - - elif fmt == libcam.formats.RGB888: - rgb = data.reshape((h, w, 3)) - rgb[:, :, [0, 1, 2]] = rgb[:, :, [2, 1, 0]] - - elif fmt == libcam.formats.BGR888: - rgb = data.reshape((h, w, 3)) - - 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 str(fmt).startswith('S'): - fmt = str(fmt) - bayer_pattern = fmt[1:5] - bitspp = int(fmt[5:]) - - # \todo shifting leaves the lowest bits 0 - if bitspp == 8: - data = data.reshape((h, w)) - data = data.astype(np.uint16) << 8 - elif bitspp in [10, 12]: - data = data.view(np.uint16) - data = data.reshape((h, w)) - data = data << (16 - bitspp) - else: - raise Exception('Bad bitspp:' + str(bitspp)) - - idx = bayer_pattern.find('R') - assert(idx != -1) - r0 = (idx % 2, idx // 2) - - idx = bayer_pattern.find('G') - assert(idx != -1) - g0 = (idx % 2, idx // 2) - - idx = bayer_pattern.find('G', idx + 1) - assert(idx != -1) - g1 = (idx % 2, idx // 2) - - idx = bayer_pattern.find('B') - assert(idx != -1) - b0 = (idx % 2, idx // 2) - - rgb = demosaic(data, r0, g0, g1, b0) - rgb = (rgb >> 8).astype(np.uint8) - - else: - rgb = None - - return rgb - - -# A naive format conversion to 24-bit RGB -def mfb_to_rgb(mfb, cfg): - data = np.array(mfb.planes[0], dtype=np.uint8) - rgb = to_rgb(cfg.pixel_format, cfg.size, data) - return rgb - - class QtRenderer: def __init__(self, state): self.state = state diff --git a/src/py/cam/cam_qt.py b/src/py/cam/helpers.py similarity index 55% copy from src/py/cam/cam_qt.py copy to src/py/cam/helpers.py index c294c999..d571c753 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/helpers.py @@ -3,23 +3,10 @@ # # Debayering code from PiCamera documentation -from io import BytesIO 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 libcamera.utils import numpy as np -import sys - - -def rgb_to_pix(rgb): - img = Image.frombuffer('RGB', (rgb.shape[1], rgb.shape[0]), rgb) - qim = ImageQt(img).copy() - pix = QtGui.QPixmap.fromImage(qim) - return pix - def demosaic(data, r0, g0, g1, b0): # Separate the components from the Bayer data to RGB planes @@ -166,147 +153,7 @@ def to_rgb(fmt, size, data): # A naive format conversion to 24-bit RGB -def mfb_to_rgb(mfb, cfg): +def mfb_to_rgb(mfb: libcamera.utils.MappedFrameBuffer, cfg: libcam.StreamConfiguration): data = np.array(mfb.planes[0], dtype=np.uint8) rgb = to_rgb(cfg.pixel_format, cfg.size, data) return rgb - - -class QtRenderer: - def __init__(self, state): - self.state = state - - self.cm = state.cm - self.contexts = state.contexts - - def setup(self): - self.app = QtWidgets.QApplication([]) - - windows = [] - - for ctx in self.contexts: - for stream in ctx.streams: - window = MainWindow(ctx, stream) - window.show() - windows.append(window) - - self.windows = windows - - def run(self): - 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) - keynotif.activated.connect(lambda _: self.readkey()) - - print('Capturing...') - - self.app.exec() - - print('Exiting...') - - def readcam(self): - running = self.state.event_handler() - - if not running: - self.app.quit() - - def readkey(self): - sys.stdin.readline() - self.app.quit() - - def request_handler(self, ctx, req): - buffers = req.buffers - - for stream, fb in buffers.items(): - wnd = next(wnd for wnd in self.windows if wnd.stream == stream) - - wnd.handle_request(stream, fb) - - self.state.request_processed(ctx, req) - - def cleanup(self): - for w in self.windows: - w.close() - - -class MainWindow(QtWidgets.QWidget): - def __init__(self, ctx, stream): - super().__init__() - - self.ctx = ctx - self.stream = stream - - self.label = QtWidgets.QLabel() - - windowLayout = QtWidgets.QHBoxLayout() - self.setLayout(windowLayout) - - windowLayout.addWidget(self.label) - - controlsLayout = QtWidgets.QVBoxLayout() - windowLayout.addLayout(controlsLayout) - - windowLayout.addStretch() - - group = QtWidgets.QGroupBox('Info') - groupLayout = QtWidgets.QVBoxLayout() - group.setLayout(groupLayout) - controlsLayout.addWidget(group) - - lab = QtWidgets.QLabel(ctx.id) - groupLayout.addWidget(lab) - - self.frameLabel = QtWidgets.QLabel() - groupLayout.addWidget(self.frameLabel) - - group = QtWidgets.QGroupBox('Properties') - groupLayout = QtWidgets.QVBoxLayout() - group.setLayout(groupLayout) - controlsLayout.addWidget(group) - - camera = ctx.camera - - for cid, cv in camera.properties.items(): - lab = QtWidgets.QLabel() - lab.setText('{} = {}'.format(cid, cv)) - groupLayout.addWidget(lab) - - group = QtWidgets.QGroupBox('Controls') - groupLayout = QtWidgets.QVBoxLayout() - group.setLayout(groupLayout) - controlsLayout.addWidget(group) - - for cid, cinfo in camera.controls.items(): - lab = QtWidgets.QLabel() - lab.setText('{} = {}/{}/{}' - .format(cid, cinfo.min, cinfo.max, cinfo.default)) - groupLayout.addWidget(lab) - - controlsLayout.addStretch() - - def buf_to_qpixmap(self, stream, fb): - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - cfg = stream.configuration - - if cfg.pixel_format == libcam.formats.MJPEG: - img = Image.open(BytesIO(mfb.planes[0])) - qim = ImageQt(img).copy() - pix = QtGui.QPixmap.fromImage(qim) - else: - rgb = mfb_to_rgb(mfb, cfg) - if rgb is None: - raise Exception('Format not supported: ' + cfg.pixel_format) - - pix = rgb_to_pix(rgb) - - return pix - - def handle_request(self, stream, fb): - ctx = self.ctx - - pix = self.buf_to_qpixmap(stream, fb) - self.label.setPixmap(pix) - - self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' - .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps)) From patchwork Fri May 27 14:44:37 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16091 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 7D236C326D for ; Fri, 27 May 2022 14:45:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 24A0F6563F; Fri, 27 May 2022 16:45:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662734; bh=fOxQi3MV/7Kaw5ML4agaGNJFUHdB1JvWz5wxvrcHCjE=; 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=CZPoipOVKsA9HQB8wQ2GPWkIp43CUrhOyJN4jp9TWKZ4yeIK66367N5MrFHmW1RED ZV86xqosPRAgykNlAoM5xZwDyXGUcEZGOYOP+lWN02JncB7gz0LFNJCtysNQ7SZy2T cWlSv1ZUMH2AK9SNsBEVOOYoLLLfJthl1jPDbsWw0dqTnbgOely8twaeRpYXXMxeNm uYF+D9I/cGkGPwvX9fkjA58IKsat51F/+2+R9QcL4did7gSDIU7PhhstS+rQ5zNPXb aA4/uBzhiwCJJY7FXBg9NNIHHktLO5shtBRsdYyLfCAC4w4TaZi69uEeNybLjQjG8l k4UkmKRBMJwgg== 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 C9483633A3 for ; Fri, 27 May 2022 16:45:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OJHlm/EB"; 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 3FB7E4326; Fri, 27 May 2022 16:45:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662714; bh=fOxQi3MV/7Kaw5ML4agaGNJFUHdB1JvWz5wxvrcHCjE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OJHlm/EBi+Zb1TZ/STmCJqJ+7LyonVjhNyCGqXhXS1pyD5SNBkcZsic+6rPIeLSzv +TvBdnvEhxY7FsM8z8q91JdowD0uIqT2X5dHQYnu6B/xcQMtZ6lfYJa9TTyt/tvvhh B/6x3eE+BcLFlqInsWQB4S1znh7iLcMCnRIv1FvQ= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:37 +0300 Message-Id: <20220527144447.94891-21-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 20/30] py: cam: Drop PIL dependency 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 can use Qt directly to accomplish the same as we do with PIL. A minor downside is that loading MJPEG frame with Qt produces a "Corrupt JPEG data" warning. The resulting picture looks fine, though. So add a message handler to ignore that warning. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_qt.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index af4b0f86..b6412bdf 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -2,17 +2,32 @@ # Copyright (C) 2022, Tomi Valkeinen from helpers import mfb_to_rgb -from io import BytesIO -from PIL import Image -from PIL.ImageQt import ImageQt from PyQt5 import QtCore, QtGui, QtWidgets import libcamera as libcam import libcamera.utils import sys + +# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these. +def qt_message_handler(msg_type, msg_log_context, msg_string): + if msg_string.startswith("Corrupt JPEG data"): + return + + # For some reason qInstallMessageHandler returns None, so we won't + # call the old handler + if old_msg_handler is not None: + old_msg_handler(msg_type, msg_log_context, msg_string) + else: + print(msg_string) + + +old_msg_handler = QtCore.qInstallMessageHandler(qt_message_handler) + + def rgb_to_pix(rgb): - img = Image.frombuffer('RGB', (rgb.shape[1], rgb.shape[0]), rgb) - qim = ImageQt(img).copy() + w = rgb.shape[1] + h = rgb.shape[0] + qim = QtGui.QImage(rgb, w, h, QtGui.QImage.Format.Format_RGB888) pix = QtGui.QPixmap.fromImage(qim) return pix @@ -135,9 +150,8 @@ class MainWindow(QtWidgets.QWidget): cfg = stream.configuration if cfg.pixel_format == libcam.formats.MJPEG: - img = Image.open(BytesIO(mfb.planes[0])) - qim = ImageQt(img).copy() - pix = QtGui.QPixmap.fromImage(qim) + pix = QtGui.QPixmap(cfg.size.width, cfg.size.height) + pix.loadFromData(mfb.planes[0]) else: rgb = mfb_to_rgb(mfb, cfg) if rgb is None: From patchwork Fri May 27 14:44:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16093 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 E262EC326F for ; Fri, 27 May 2022 14:45:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 603A865642; Fri, 27 May 2022 16:45:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662735; bh=nQjPqigPkN6HPUbxyWkm7VNP9KvL1x/u73XNvTzHcus=; 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=nMZytV28J+bun7m2IrcfXz1LKnrKBw123eguQ9ddVspV9Jk/9InKsSt6J+5bXs8CD T/Yyg7nrXsv492WNyY8GvkS/qoJNWzADoDDW2q1T01JIUAjzcwQurwd1mOhOV456yZ tpBvIhJr35BZiaR21f+Oo93zFC7WNjVtSjFs9MsSLFjSF6jd77mhlGx+kUxeXGfRzg Ata4yhjMyUc0SWz5eFP8CGKGJE0jCpz7rcIaX5ynwJ7v5cevpAxlFERMf6Qiu46QMb 2Y6/nW2DYTr/ev3pgs1rP0cJIxZ4JIsDPY7ykCm5PO5RUhk+eG0GJq/8szeQgVuxSb okTFWZxoU4Ktw== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 597F165640 for ; Fri, 27 May 2022 16:45:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="wAJ36Ttv"; 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 BF147B1D; Fri, 27 May 2022 16:45:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662715; bh=nQjPqigPkN6HPUbxyWkm7VNP9KvL1x/u73XNvTzHcus=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wAJ36TtvYzP5JUp289Z7npPItRxjS+53qMPEGwVQFEGqXyBO/vWgINBMEs6NrT+ee DbdNIEREyyynHsTI50HTx/o/GuCE3qALWSztWqjA+0xU3V8aEnZ55M+tqCy7r5/lwZ +4YXKVWt/Id8/fQctHgpVWq7wtdspEzfKyfzs9qM= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:38 +0300 Message-Id: <20220527144447.94891-22-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 21/30] py: Add Request.__str__() 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 Request.__str__() which maps directly to Request::toString(). Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/py_main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 3220a9e6..9e827845 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -496,7 +496,8 @@ PYBIND11_MODULE(_libcamera, m) * \todo As we add a keep_alive to the fb in addBuffers(), we * can only allow reuse with ReuseBuffers. */ - .def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); }); + .def("reuse", [](Request &self) { self.reuse(Request::ReuseFlag::ReuseBuffers); }) + .def("__str__", &Request::toString); pyRequestStatus .value("Pending", Request::RequestPending) From patchwork Fri May 27 14:44:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16095 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 62E0BC3270 for ; Fri, 27 May 2022 14:45:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 05644656A4; Fri, 27 May 2022 16:45:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662737; bh=jsTL+qIbxJSn7ZLPLtfga4ld9MZGQRdgxhEB4elyv3o=; 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=iq6KTxvDzcGAV5SCNRkuzrVoNwFhM65ziulbuasr6fw4l+GT9s+McmmZVH1hcznmY L1M6QSm5s/9eovyWtPiTadjIfWcw3WrqBN7x6AS6wbdk1E+Cxf4DsQ4eFeRROvS+F7 wLMU9ktogumnDV1i1Ix43BPYE3e/sUvpHhOnFYOHz4Y/9fnvenuwrEDdJEoFir0EZB IBkijo/vFMtGgiPwOvHHm90mVyQBGtEb7d0APc8NBLk0E2n54/jeZk44lqRArk4IkH fS++lmgR1+gtYjPrB9kF9/2gR+RW10hVelswMwDBod/6YVT7wUA+PuDmni1NaSPhlE jdelWNsKMx03w== 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 DD3BD6563F for ; Fri, 27 May 2022 16:45:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KevzTX83"; 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 53B9FD80; Fri, 27 May 2022 16:45:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662715; bh=jsTL+qIbxJSn7ZLPLtfga4ld9MZGQRdgxhEB4elyv3o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KevzTX83ebK2wqEdOUpFCDKUxx2zjW6fTpaZk6pcM5J8Lj0xdNwApXfvVTjb6B+o3 d8TxlLclD3Td0QrrrHRChvMNmDTSCs1VhNvdfx5q78Yns5Br9YkMyvoSKQ7HlO0eSX 6328V9tVMXZh2hIW7hLll7cDddPlVGkmDmhQhWw8= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:39 +0300 Message-Id: <20220527144447.94891-23-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 22/30] py: Add FrameMetadataPlane 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 FrameMetadataPlane class and adjust the methods and uses accordingly. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam.py | 3 ++- src/py/libcamera/py_main.cpp | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py index 677dd680..bf8529d9 100755 --- a/src/py/cam/cam.py +++ b/src/py/cam/cam.py @@ -285,7 +285,8 @@ class CaptureState: print('{:.6f} ({:.2f} fps) {}-{}: seq {}, bytes {}, CRCs {}' .format(ts / 1000000000, fps, ctx.id, stream_name, - meta.sequence, meta.bytesused, + meta.sequence, + '/'.join([str(p.bytes_used) for p in meta.planes]), crcs)) if ctx.opt_metadata: diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 9e827845..52f70811 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -163,6 +163,7 @@ PYBIND11_MODULE(_libcamera, m) auto pyRequestReuse = py::enum_(pyRequest, "Reuse"); auto pyFrameMetadata = py::class_(m, "FrameMetadata"); auto pyFrameMetadataStatus = py::enum_(pyFrameMetadata, "Status"); + auto pyFrameMetadataPlane = py::class_(pyFrameMetadata, "Plane"); auto pyTransform = py::class_(m, "Transform"); auto pyColorSpace = py::class_(m, "ColorSpace"); auto pyColorSpacePrimaries = py::enum_(pyColorSpace, "Primaries"); @@ -512,11 +513,10 @@ PYBIND11_MODULE(_libcamera, m) .def_readonly("status", &FrameMetadata::status) .def_readonly("sequence", &FrameMetadata::sequence) .def_readonly("timestamp", &FrameMetadata::timestamp) - /* \todo Implement FrameMetadata::Plane properly */ - .def_property_readonly("bytesused", [](FrameMetadata &self) { - std::vector v; - v.resize(self.planes().size()); - transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; }); + .def_property_readonly("planes", [](const FrameMetadata &self) { + /* Convert from Span<> to std::vector<> */ + /* Note: this creates a copy */ + std::vector v(self.planes().begin(), self.planes().end()); return v; }); @@ -525,6 +525,9 @@ PYBIND11_MODULE(_libcamera, m) .value("Error", FrameMetadata::FrameError) .value("Cancelled", FrameMetadata::FrameCancelled); + pyFrameMetadataPlane + .def_readwrite("bytes_used", &FrameMetadata::Plane::bytesused); + pyTransform .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) { bool ok; From patchwork Fri May 27 14:44:40 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16092 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 23A3DC3256 for ; Fri, 27 May 2022 14:45:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B2DBB6563D; Fri, 27 May 2022 16:45:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662734; bh=dXzV8DfLo7pNLIj8LFBrLz4vdQqYZJkEXG2sBTSVd2U=; 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=mE0OM7xgpcDbRShdC6acqDSKlGeu8nxhQVn4N8PmvBi8ZuWH3onJSGiKcyHwce/7X H2iEKVjPqm5d6OA+MvS1NVn9zNzRWpvWK4jpjh0v2Pa2yh1GYjdYQs9Pm12YKN+GEM l65GCbV7PrFMz9VaBqWtWbADNUFLkaSBo4u1gtB5csMNtrb6UQmGJSaXjAKtWffNwA /3O74jHUuZZIYhwMRIYlMYiRoN1cd3FeIN9fceuR/VBstnlSzAQCau3IfYLzZL8i0/ QHwNrGOgK5xO5sjPJ9fAwRyCUmpwEifQC5vPy2AFdopg0jIzpa+FtV0CQxJ62rFRTv uVQbpcE83L1pg== 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 676526565A for ; Fri, 27 May 2022 16:45:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gx4KA2My"; 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 D02FBFD1; Fri, 27 May 2022 16:45:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662716; bh=dXzV8DfLo7pNLIj8LFBrLz4vdQqYZJkEXG2sBTSVd2U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gx4KA2MyzHiX4t4n4/njxfJseTH6cxkw6xv29sVc2C0tzAbkEYF99rMhMwfLqksKf BKkvlh30W8WhPEMh8d0TFsTTjJopIQdYrvxDyyIMQb0/E1rh6K5Xp7CTTGaXFC+EiF 9J5m/0JIQ8zxYjtYrnYbeJLeT4maMDJFXHJ3/rzo= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:40 +0300 Message-Id: <20220527144447.94891-24-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 23/30] py: Implement FrameBufferPlane 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" Implement FrameBufferPlane class and adjust the methods and uses accordingly. Note that we don't expose the fd as a SharedFD, but as an int. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_kms.py | 6 +-- src/py/cam/cam_qtgl.py | 2 +- src/py/libcamera/py_main.cpp | 45 +++++++++++---------- src/py/libcamera/utils/MappedFrameBuffer.py | 18 ++++----- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py index 49b00211..3d1e8c27 100644 --- a/src/py/cam/cam_kms.py +++ b/src/py/cam/cam_kms.py @@ -131,10 +131,10 @@ class KMSRenderer: fds = [] strides = [] offsets = [] - for i in range(fb.num_planes): - fds.append(fb.fd(i)) + for p in fb.planes: + fds.append(p.fd) strides.append(cfg.stride) - offsets.append(fb.offset(i)) + offsets.append(p.offset) drmfb = pykms.DmabufFramebuffer(self.card, w, h, fmt, fds, strides, offsets) diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index 4b43f51d..6cfbd347 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -269,7 +269,7 @@ class MainWindow(QtWidgets.QWidget): EGL_WIDTH, w, EGL_HEIGHT, h, EGL_LINUX_DRM_FOURCC_EXT, fmt, - EGL_DMA_BUF_PLANE0_FD_EXT, fb.fd(0), + EGL_DMA_BUF_PLANE0_FD_EXT, fb.planes[0].fd, EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, EGL_DMA_BUF_PLANE0_PITCH_EXT, cfg.stride, EGL_NONE, diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index 52f70811..fcf009f0 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -155,6 +155,7 @@ PYBIND11_MODULE(_libcamera, m) auto pyStreamFormats = py::class_(m, "StreamFormats"); auto pyFrameBufferAllocator = py::class_(m, "FrameBufferAllocator"); auto pyFrameBuffer = py::class_(m, "FrameBuffer"); + auto pyFrameBufferPlane = py::class_(pyFrameBuffer, "Plane"); auto pyStream = py::class_(m, "Stream"); auto pyControlId = py::class_(m, "ControlId"); auto pyControlInfo = py::class_(m, "ControlInfo"); @@ -408,31 +409,31 @@ PYBIND11_MODULE(_libcamera, m) }); pyFrameBuffer - /* \todo implement FrameBuffer::Plane properly */ - .def(py::init([](std::vector> planes, unsigned int cookie) { - std::vector v; - for (const auto &t : planes) - v.push_back({ SharedFD(std::get<0>(t)), FrameBuffer::Plane::kInvalidOffset, std::get<1>(t) }); - return new FrameBuffer(v, cookie); - })) + .def(py::init, unsigned int>(), + py::arg("planes"), py::arg("cookie") = 0) .def_property_readonly("metadata", &FrameBuffer::metadata, py::return_value_policy::reference_internal) - .def_property_readonly("num_planes", [](const FrameBuffer &self) { - return self.planes().size(); - }) - .def("length", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.length; - }) - .def("fd", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.fd.get(); - }) - .def("offset", [](FrameBuffer &self, uint32_t idx) { - const FrameBuffer::Plane &plane = self.planes()[idx]; - return plane.offset; - }) + .def_property_readonly("planes", &FrameBuffer::planes) .def_property("cookie", &FrameBuffer::cookie, &FrameBuffer::setCookie); + pyFrameBufferPlane + .def(py::init()) + .def(py::init([](int fd, unsigned int offset, unsigned int length) { + auto p = FrameBuffer::Plane(); + p.fd = SharedFD(fd); + p.offset = offset; + p.length = length; + return p; + }), py::arg("fd"), py::arg("offset"), py::arg("length")) + .def_property("fd", + [](const FrameBuffer::Plane &self) { + return self.fd.get(); + }, + [](FrameBuffer::Plane &self, int fd) { + self.fd = SharedFD(fd); + }) + .def_readwrite("offset", &FrameBuffer::Plane::offset) + .def_readwrite("length", &FrameBuffer::Plane::length); + pyStream .def_property_readonly("configuration", &Stream::configuration); diff --git a/src/py/libcamera/utils/MappedFrameBuffer.py b/src/py/libcamera/utils/MappedFrameBuffer.py index fc2726b6..69315e76 100644 --- a/src/py/libcamera/utils/MappedFrameBuffer.py +++ b/src/py/libcamera/utils/MappedFrameBuffer.py @@ -21,8 +21,8 @@ class MappedFrameBuffer: bufinfos = {} - for i in range(fb.num_planes): - fd = fb.fd(i) + for p in fb.planes: + fd = p.fd if fd not in bufinfos: buflen = os.lseek(fd, 0, os.SEEK_END) @@ -30,11 +30,11 @@ class MappedFrameBuffer: else: buflen = bufinfos[fd]['buflen'] - if fb.offset(i) > buflen or fb.offset(i) + fb.length(i) > buflen: + if p.offset > buflen or p.offset + p.length > buflen: raise RuntimeError(f'plane is out of buffer: buffer length={buflen}, ' + - f'plane offset={fb.offset(i)}, plane length={fb.length(i)}') + f'plane offset={p.offset}, plane length={p.length}') - bufinfos[fd]['maplen'] = max(bufinfos[fd]['maplen'], fb.offset(i) + fb.length(i)) + bufinfos[fd]['maplen'] = max(bufinfos[fd]['maplen'], p.offset + p.length) # mmap the buffers @@ -51,14 +51,14 @@ class MappedFrameBuffer: planes = [] - for i in range(fb.num_planes): - fd = fb.fd(i) + for p in fb.planes: + fd = p.fd info = bufinfos[fd] mv = memoryview(info['map']) - start = fb.offset(i) - end = fb.offset(i) + fb.length(i) + start = p.offset + end = p.offset + p.length mv = mv[start:end] From patchwork Fri May 27 14:44:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16094 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 B123EC326E for ; Fri, 27 May 2022 14:45:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 70A186565D; Fri, 27 May 2022 16:45:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662736; bh=uHPpsYlvDhUb28WKb9TZp8sVFeHN/J/uHXXmuPnsmS8=; 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=d6F4UMDPLJ7tpdQzt6ABXRiE4shjH8w6JoDevXW1L0TCuBWiIXeJ5HkOwQc8pIVLt akCyw8hrLpezEt1/djYnHOaAS5oZ4CSWNazcSuuFyccMXE0eALrJsJ7C1/S70TsT4z nInRumRhlTnmD5gDIA3+Ipf36uI13l8OE3PUlOE2KbVDxLwjic/ksaUgioFtOn7yqs bhBLCDtMF3YLneP7zCLjgwde2xZhPKgmmPb6CHb7WbQZKxuggfk0JHogHBLH9+lRio lc8rrHYtt3oN+fywSzE9FK1VtaDvIsjM9GUIBeBpFL6CfWxKDU1qfi1vHk1dE2wy1M LJ1tlgVI727UA== 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 E5F306565C for ; Fri, 27 May 2022 16:45:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="u81QOrZg"; 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 58F2E10CA; Fri, 27 May 2022 16:45:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662716; bh=uHPpsYlvDhUb28WKb9TZp8sVFeHN/J/uHXXmuPnsmS8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=u81QOrZgvKCvTPk/CNc4Z27JH+vESqBRwWYGkmw3ohSW+OyckaoVBBAYGwdnFuJIW TB9TrfQg9gDQk66ec1UvYDq0BAziWBWknTaW5Np9Y5+qjef8KXdGyeeM+c8mw0wUnY iiJ5C0n1P0AixgmTyuLnoSMqJbFAXAOxSj1LZZvs= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:41 +0300 Message-Id: <20220527144447.94891-25-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 24/30] py: MappedFrameBuffer: Support non-contextmanager use 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" Implement non-contextmanager use to MappedFrameBuffer so that we can either: with MappedFrameBuffer(fb) as mfb: ... or mfb = MappedFrameBuffer(fb) mfb.mmap() ... mfb.munmap() While at it, improve the error handling a bit. Note that the mmap() returns self. In other words, one can do this: mfb = MappedFrameBuffer(fb).mmap() ... mfb.munmap() Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/utils/MappedFrameBuffer.py | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/py/libcamera/utils/MappedFrameBuffer.py b/src/py/libcamera/utils/MappedFrameBuffer.py index 69315e76..4b20f904 100644 --- a/src/py/libcamera/utils/MappedFrameBuffer.py +++ b/src/py/libcamera/utils/MappedFrameBuffer.py @@ -10,8 +10,19 @@ class MappedFrameBuffer: """ def __init__(self, fb: libcamera.FrameBuffer): self.__fb = fb + self.__planes = () + self.__maps = () def __enter__(self): + return self.mmap() + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.munmap() + + def mmap(self): + if self.__planes: + raise RuntimeError('MappedFrameBuffer already mmapped') + import os import mmap @@ -68,14 +79,23 @@ class MappedFrameBuffer: return self - def __exit__(self, exc_type, exc_value, exc_traceback): + def munmap(self): + if not self.__planes: + raise RuntimeError('MappedFrameBuffer not mmapped') + for p in self.__planes: p.release() for mm in self.__maps: mm.close() + self.__planes = () + self.__maps = () + @property def planes(self) -> Tuple[memoryview, ...]: """memoryviews for the planes""" + if not self.__planes: + raise RuntimeError('MappedFrameBuffer not mmapped') + return self.__planes From patchwork Fri May 27 14:44:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16096 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 81FE8BD161 for ; Fri, 27 May 2022 14:45:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 378C4656B1; Fri, 27 May 2022 16:45:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662739; bh=WwZVdA5xllMNfedF2g2168gk5rN6XtXsc1rEUATCMxo=; 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=Tz/QxnlzHlbIyKRdMWUrIOGOXwZX5dLNwHCFxehI6+p5PFNEj5z939Cy1MrszZ1Cr jkoPH74/BZStu1iTjBzkblH2JHAagzD8nONRQFcu9tFDxkY23lFfQnskhjf4qmBn1C 38akzt7pvrEZfXdBJ4WnDhrxLZxrv4Ci8+vWfoAqH6RWo0qZAfZtl62FFx4vo5Gl6V WZgN7F7mQbUqSwK01kz61w/qquiVDpp07aPEpj9RZbzC4c5ITdV4Dld3ignLCA0/kP W5hNkKa2zCA3WVKZOglHEnQ+vesV2LM5hgk6VadLHSDheTS+zD1EZYyH1PyAQey/Ht uk7cK0D9IGg4A== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6E36365649 for ; Fri, 27 May 2022 16:45:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="n4D64ak8"; 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 D3285EF2; Fri, 27 May 2022 16:45:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662717; bh=WwZVdA5xllMNfedF2g2168gk5rN6XtXsc1rEUATCMxo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n4D64ak8SruUUc9XbBdMWRNdS2KQxe0O0n08MgtK+cKmNJUqEGCI81jBNUKJs+usp +OeAcQaix8zThfHJW1bsCue0UMv+x/PhBSrVrM+ndoeRfCCo57uOrUIBfod3RdEsPw yq+//BsKyPdYVcKJJGNVNmloZu5lKqdFFx9eF9ks= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:42 +0300 Message-Id: <20220527144447.94891-26-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 25/30] py: MappedFrameBuffer: Add 'fb' property 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 'fb' property to expose the underlying FrameBuffer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/libcamera/utils/MappedFrameBuffer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/py/libcamera/utils/MappedFrameBuffer.py b/src/py/libcamera/utils/MappedFrameBuffer.py index 4b20f904..c25371c2 100644 --- a/src/py/libcamera/utils/MappedFrameBuffer.py +++ b/src/py/libcamera/utils/MappedFrameBuffer.py @@ -99,3 +99,7 @@ class MappedFrameBuffer: raise RuntimeError('MappedFrameBuffer not mmapped') return self.__planes + + @property + def fb(self): + return self.__fb From patchwork Fri May 27 14:44:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16097 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 D15D1C326D for ; Fri, 27 May 2022 14:45:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CA24E65652; Fri, 27 May 2022 16:45:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662739; bh=Okl+wkWXIhIj8TacF37C+99ssAxg9vTV88RCzBhwMzM=; 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=YC/Tt6sXq//S5o8f5jsJfCuYgXzJNj0oXJe5dgRYnTmjb0PBNNiNjwy68yBSmeaNS mhRrOFwTIMYSokEsMFyooygxPhOeglChBAGeEYG+duZBoUOAa2kEc4K5leJ0u8Veex ddiVNLR1YfV31cddPx/HndzsGkR+Zeuq004CO4BD3I8P9ydKOEVE3zWa4Vk6kuFvRY mVNOzozkPzogPue0xWLtFzig3+6s026AEUdccap8ksgrGuEEDpCz+44GfglhSQYi2e w4aqi/1nwrBno+yApE9nTtt5ycdklydhX48i0MdrhisbGVeYT1NFyiS9Iz5Z2stXya cCNq+grj8rI0A== 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 026326565E for ; Fri, 27 May 2022 16:45:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rjPiDnjn"; 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 652AE4CDA; Fri, 27 May 2022 16:45:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662717; bh=Okl+wkWXIhIj8TacF37C+99ssAxg9vTV88RCzBhwMzM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rjPiDnjnXp7x8EKv4ozDUH1L2TYMCYmejQhVg2VwQogIh+11m+XH3MUxXeydFSN1u xNUT3fXNiB87A36DQJhSWRoizi3f71bMUlNlkA9ykAaUze2mDgSHUSP2pqbTAhrjN1 OiW0M2paJAbhnYTDZxvH8jfucxqQdH6OlToaysvY= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:43 +0300 Message-Id: <20220527144447.94891-27-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 26/30] py: cam: cam_qt: mmap the fbs only once 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" Instead of doing an mmap and munmap every time a Request is complete, mmap all the buffers once at the start of the program. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- src/py/cam/cam_qt.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index b6412bdf..c1723b44 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -52,6 +52,16 @@ class QtRenderer: self.windows = windows + buf_mmap_map = {} + + for ctx in self.contexts: + for stream in ctx.streams: + for buf in ctx.allocator.buffers(stream): + mfb = libcamera.utils.MappedFrameBuffer(buf).mmap() + buf_mmap_map[buf] = mfb + + self.buf_mmap_map = buf_mmap_map + def run(self): camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Read) camnotif.activated.connect(lambda _: self.readcam()) @@ -81,7 +91,9 @@ class QtRenderer: for stream, fb in buffers.items(): wnd = next(wnd for wnd in self.windows if wnd.stream == stream) - wnd.handle_request(stream, fb) + mfb = self.buf_mmap_map[fb] + + wnd.handle_request(stream, mfb) self.state.request_processed(ctx, req) @@ -145,26 +157,25 @@ class MainWindow(QtWidgets.QWidget): controlsLayout.addStretch() - def buf_to_qpixmap(self, stream, fb): - with libcamera.utils.MappedFrameBuffer(fb) as mfb: - cfg = stream.configuration + def buf_to_qpixmap(self, stream, mfb): + cfg = stream.configuration - if cfg.pixel_format == libcam.formats.MJPEG: - pix = QtGui.QPixmap(cfg.size.width, cfg.size.height) - pix.loadFromData(mfb.planes[0]) - else: - rgb = mfb_to_rgb(mfb, cfg) - if rgb is None: - raise Exception('Format not supported: ' + cfg.pixel_format) + if cfg.pixel_format == libcam.formats.MJPEG: + pix = QtGui.QPixmap(cfg.size.width, cfg.size.height) + pix.loadFromData(mfb.planes[0]) + else: + rgb = mfb_to_rgb(mfb, cfg) + if rgb is None: + raise Exception('Format not supported: ' + cfg.pixel_format) - pix = rgb_to_pix(rgb) + pix = rgb_to_pix(rgb) return pix - def handle_request(self, stream, fb): + def handle_request(self, stream, mfb): ctx = self.ctx - pix = self.buf_to_qpixmap(stream, fb) + pix = self.buf_to_qpixmap(stream, mfb) self.label.setPixmap(pix) self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' From patchwork Fri May 27 14:44:44 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16098 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 1A3A2C3271 for ; Fri, 27 May 2022 14:45:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 863A7656A5; Fri, 27 May 2022 16:45:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662740; bh=QUnYvFxz4Ygo344WFDS1YECj2E5+A8bUHAIf/hS7j6o=; 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=DQvr4xJ/Vq7TusWU4aiv5cQrjMO2JjIzQtNxWXOXy5jg0iDx2vWZzfG5COSs3Y+g0 u2qFwkIef5SjT5WWaJ8S/2Ieo5xbMB9O8e25U+vQnHSFQy8iMO+1agK8xw8pRYUlRJ 6Zey2XS2YxlHb0xu7v8QoSvhMYxCYZJQpp4+28bGWCl76/0YRnkVKtquDYBtj0mS0q f2NIrCaKAgxftcS+yMLFklUWWP48afiAK/yBhk5e6TOzktUKyTnjo9CBTFgFxmeRvx U7GQ6pLWArMsFtmt/F5VcNFsNXLOe4II+3KsklZvrAdwM2BgZptfcUfvGbtO46u0fH TiQf4MBdMOruQ== Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 79C8B65648 for ; Fri, 27 May 2022 16:45:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TEtV9mo/"; 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 DF5C74D18; Fri, 27 May 2022 16:45:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662718; bh=QUnYvFxz4Ygo344WFDS1YECj2E5+A8bUHAIf/hS7j6o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TEtV9mo/tW0vAXvy0raevSM68gfHE57m0DpWtz6JfsN1edSuzeAXXjFCUrlHmqUyb vlxsB4or4YvK/a2JeSgAGmrFz4MVBX2420m3FKO/p74k7zDTUqbS1rCXW+Xm3XBv8R ymb8Ptz+c/zQggzMRPEt/eGznNS8e1Soy4+OeOH0= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:44 +0300 Message-Id: <20220527144447.94891-28-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 27/30] 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 | 159 ++++++++++++++++++++++++++++++ 1 file changed, 159 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..4b0c3f0c --- /dev/null +++ b/src/py/examples/simple-capture.py @@ -0,0 +1,159 @@ +#!/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 +# +# This simple example is, in many ways, too simple. The purpose of the example +# is to introduce the concepts. A more realistic example is given in +# simple-continuous-capture.py. + +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 + + cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder]) + + stream_config = cam_config.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + stream_config.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + stream_config.size = libcam.Size(w, h) + + ret = cam.configure(cam_config) + assert ret == 0 + + stream = stream_config.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 {stream_config}') + + # 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, + '/'.join([str(p.bytes_used) for p in meta.planes]), + 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 Fri May 27 14:44:45 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16100 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 64DBBC3272 for ; Fri, 27 May 2022 14:45:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0F430656AE; Fri, 27 May 2022 16:45:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662742; bh=4bUvZI08LJR8WBjJhEchsq9ky0F9ZRthxJqNtX2L1Ag=; 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=oWlTvOo5x2BEQkoSb7yMMoD4AtglG/8Q+5ib6ZSYkmX9xK8iUDRJmyN9KQYeLKBs0 +b6h/ut8aZy+kvXBgfwdHuuqeZ8JK7GHAl9X0GMdZZmavFtb4qBdeIatYhmOkgQzoE UZMElfXN8F1QzAN8G3xPdlkpMgIkRFLc471fHXZpKWAtjHba4KECcSX1M96HUI6exx jZNXqGtrzPTTfHlcHe1QP69YIeeMmCR7CfryexJLlIHO7UYPjfRveFtDsDNW7eh49E kqJBbwMpX6JeNpO8M2cBXeEHPRQ9WonVAA6r5eteQarLFXrLwdh0Du6r+vX+hmwvkW cNQFcJ+DSfkrg== 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 014F66565F for ; Fri, 27 May 2022 16:45:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YvBrNj83"; 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 6A1AB4D1A; Fri, 27 May 2022 16:45:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662718; bh=4bUvZI08LJR8WBjJhEchsq9ky0F9ZRthxJqNtX2L1Ag=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YvBrNj83K7gP8nqBN6EaD7WHTnQwJE53SGhSp9dFvoLzaUMDXPyONJ0pOtM06rNsI /r2TAGfLfeOZyTDYZckR8nH1tU0IOQ6yg4sZSocXtr9FSMluXxcrLc6Ko25eHRjwWG zohEILLZ0yP09pCzm4FSX1oVjNhChF2GuxH70mqs= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:45 +0300 Message-Id: <20220527144447.94891-29-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 28/30] 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 | 201 +++++++++++++++++++ 1 file changed, 201 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..1c4595f6 --- /dev/null +++ b/src/py/examples/simple-continuous-capture.py @@ -0,0 +1,201 @@ +#!/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 + + +# A simple container class for our objects +class CaptureContext: + cm: libcam.CameraManager + cam: libcam.Camera + reqs: list[libcam.Request] + mfbs: dict[libcam.FrameBuffer, libcamera.utils.MappedFrameBuffer] + + +def handle_camera_event(ctx: CaptureContext): + # 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. + + ctx.cm.read_event() + + reqs = ctx.cm.get_ready_requests() + + # Process the captured frames + + for req in reqs: + handle_request(ctx, req) + + return True + + +def handle_request(ctx: CaptureContext, req: libcam.Request): + 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 the MappedFrameBuffer to access the pixel data with CPU. We calculate + # the crc for each plane. + + mfb = ctx.mfbs[fb] + crcs = [binascii.crc32(p) for p in mfb.planes] + + meta = fb.metadata + + print('buf {}, seq {}, bytes {}, CRCs {}' + .format(req.cookie, + meta.sequence, + '/'.join([str(p.bytes_used) for p in meta.planes]), + 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() + ctx.cam.queue_request(req) + + +def handle_key_event(): + sys.stdin.readline() + print('Exiting...') + return False + + +def capture(ctx: CaptureContext): + # Queue the requests to the camera + + for req in ctx.reqs: + ret = ctx.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(ctx.cm.event_fd, selectors.EVENT_READ, lambda: handle_camera_event(ctx)) + sel.register(sys.stdin, selectors.EVENT_READ, handle_key_event) + + running = True + + while running: + events = sel.select() + for key, mask in events: + # If the handler return False, we should exit + if not key.data(): + running = False + + +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 + + cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder]) + + stream_config = cam_config.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + stream_config.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + stream_config.size = libcam.Size(w, h) + + ret = cam.configure(cam_config) + assert ret == 0 + + stream = stream_config.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 {stream_config} 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 + + ctx = CaptureContext() + ctx.cm = cm + ctx.cam = cam + ctx.reqs = reqs + ctx.mfbs = dict([(fb, libcamera.utils.MappedFrameBuffer(fb).mmap()) for fb in allocator.buffers(stream)]) + + capture(ctx) + + # 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 Fri May 27 14:44:46 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16099 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 CBCDAC3256 for ; Fri, 27 May 2022 14:45:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6DCBC656A7; Fri, 27 May 2022 16:45:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662741; bh=fcqqYcs6VliTJudAvdYJA1aJXju/bobKdJyo5MNSPQE=; 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=utxlFWZx3oNXAh8n/VdC8ftNR9LeUuVM/PXctR77PD81CxF4fzQ5bmtwAk/uosrAA YlJQjTCA9L30LQsm429pKwQA8idF0wK14X5f/uhp1ACRJ60BjdKIbLg2kRyvm/QYxW odt6Kw+b2HtwGpt5XDWva/LyODbSVOH0WCbQfvlGEHzXVFJ421PWmczVj6+Wclwrqk Y/iXM9MChUdvwAduaYRCLI4kHdNq+vOMg+6D0z0HzHUUP/tfPjRkXeG1/D7R9IwEs3 2LOcj5d9yCTfayXVQ+yagZNs4X2MshWsp+JMA4rvhf0DAo5PwYRiJ3xE/no1oPCeb6 Wr9emEzUnrREw== 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 7C9F86563D for ; Fri, 27 May 2022 16:45:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BAApwR7X"; 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 E97ADB90; Fri, 27 May 2022 16:45:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662719; bh=fcqqYcs6VliTJudAvdYJA1aJXju/bobKdJyo5MNSPQE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BAApwR7XcR4OuCHPF3v8rO1ywQghLMXMQnt2KSXqrAK8udGWnMeJiy6BuawSIjb4L jVZpuuD0cs/haoMEnh9HTA9B1U5Lvj8457Zkb/ix0r55XDmk3Y4f6Cl6fOIYuO1HeT NySmXCDXNrcqP3a8O23v6ia2n3kRVA4/6ExmbYZc= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:46 +0300 Message-Id: <20220527144447.94891-30-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 29/30] 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..96a8e42d --- /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.event_fd, 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 + + cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder]) + + stream_config = cam_config.at(0) + + if args.format: + fmt = libcam.PixelFormat(args.format) + stream_config.pixel_format = fmt + + if args.size: + w, h = [int(v) for v in args.size.split('x')] + stream_config.size = libcam.Size(w, h) + + ret = cam.configure(cam_config) + assert ret == 0 + + stream = stream_config.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 {stream_config} 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 Fri May 27 14:44:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 16101 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 BD4B2C3273 for ; Fri, 27 May 2022 14:45:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B02FE656AF; Fri, 27 May 2022 16:45:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1653662742; bh=6g/hoz2maPEpfUMnMG2cGK6We+Z1IdFOdMGdL+Pg1yc=; 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=tTalZfSDgeb1DYwwGuynfNF4/AzrQthAj/iNZBdDm/xb1blNQlH6mY0ps8K+rRynq qMyPpfh8+BC96MmW8ImsNC7GFoLoHO032dzLJMvhV4FTi1AWJHpJqk2v7xqxpg8R8p l6zdFkGsqLCD0Wp0FZOWzAcZEB3xQhFPaheRc37NaPLp+kEMHiymmEiRF+mrv2yzTS 8IAJO7EBErefDv3GARXnle9QJ0gQzu95NPWPZKn1mthsXqBAwa5+mW8sZdlRy3TMbz 8dk3GSHP75UCDZlvT3sZp2IEYiy+GgHhvoeSv7ujO9u5wQT7e/HGd76vjvayUb2Ez/ dBdhG6YctnlZQ== 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 1049965653 for ; Fri, 27 May 2022 16:45:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pQXWvaJO"; 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 744C14E8E; Fri, 27 May 2022 16:45:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1653662719; bh=6g/hoz2maPEpfUMnMG2cGK6We+Z1IdFOdMGdL+Pg1yc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pQXWvaJOclBIVru0uf25umT/0mM5WBMbo3KMan4C9tusns6wxV/aBJD1Shy57TR2+ u8TYPbFs8fwjgetycLZwMHvfw/rQFwVWRQXwAWqARNkv9ug5MIwcmiW2gVOpZYlYuh TLZsi7pIit61ooCWJZyQff+qDxjfjfG5mW2WVi/U= To: libcamera-devel@lists.libcamera.org, David Plowman , Kieran Bingham , Laurent Pinchart , Jacopo Mondi Date: Fri, 27 May 2022 17:44:47 +0300 Message-Id: <20220527144447.94891-31-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> References: <20220527144447.94891-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 30/30] py: examples: Add simple-cam.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 Python version of simple-cam from: https://git.libcamera.org/libcamera/simple-cam.git Let's keep this in the libcamera repository until the Python API has stabilized a bit more, and then we could move this to the simple-cam repo. Signed-off-by: Tomi Valkeinen --- src/py/examples/simple-cam.py | 352 ++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100755 src/py/examples/simple-cam.py diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py new file mode 100755 index 00000000..ce17175b --- /dev/null +++ b/src/py/examples/simple-cam.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2022, Tomi Valkeinen + +# A simple libcamera capture example +# +# This is a python version of simple-cam from: +# https://git.libcamera.org/libcamera/simple-cam.git +# +# \todo Move to simple-cam repository when the Python API has stabilized more + +import libcamera as libcam +import selectors +import sys +import time + +TIMEOUT_SEC = 3 + + +def handle_camera_event(cm): + # 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: + process_request(req) + + +def process_request(request): + global camera + + print() + + print(f'Request completed: {request}') + + # When a request has completed, it is populated with a metadata control + # list that allows an application to determine various properties of + # the completed request. This can include the timestamp of the Sensor + # capture, or its gain and exposure values, or properties from the IPA + # such as the state of the 3A algorithms. + # + # To examine each request, print all the metadata for inspection. A custom + # application can parse each of these items and process them according to + # its needs. + + requestMetadata = request.metadata + for id, value in requestMetadata.items(): + print(f'\t{id.name} = {value}') + + # Each buffer has its own FrameMetadata to describe its state, or the + # usage of each buffer. While in our simple capture we only provide one + # buffer per request, a request can have a buffer for each stream that + # is established when configuring the camera. + # + # This allows a viewfinder and a still image to be processed at the + # same time, or to allow obtaining the RAW capture buffer from the + # sensor along with the image as processed by the ISP. + + buffers = request.buffers + for _, buffer in buffers.items(): + metadata = buffer.metadata + + # Print some information about the buffer which has completed. + print(f' seq: {metadata.sequence:06} timestamp: {metadata.timestamp} bytesused: ' + + '/'.join([str(p.bytes_used) for p in metadata.planes])) + + # Image data can be accessed here, but the FrameBuffer + # must be mapped by the application + + # Re-queue the Request to the camera. + request.reuse() + camera.queue_request(request) + + +# ---------------------------------------------------------------------------- +# Camera Naming. +# +# Applications are responsible for deciding how to name cameras, and present +# that information to the users. Every camera has a unique identifier, though +# this string is not designed to be friendly for a human reader. +# +# To support human consumable names, libcamera provides camera properties +# that allow an application to determine a naming scheme based on its needs. +# +# In this example, we focus on the location property, but also detail the +# model string for external cameras, as this is more likely to be visible +# information to the user of an externally connected device. +# +# The unique camera ID is appended for informative purposes. +# +def camera_name(camera): + props = camera.properties + location = props.get(libcam.properties.Location, None) + + if location == libcam.properties.LocationEnum.Front: + name = 'Internal front camera' + elif location == libcam.properties.LocationEnum.Back: + name = 'Internal back camera' + elif location == libcam.properties.LocationEnum.External: + name = 'External camera' + if libcam.properties.Model in props: + name += f' "{props[libcam.properties.Model]}"' + else: + name = 'Undefined location' + + name += f' ({camera.id})' + + return name + + +def main(): + global camera + + # -------------------------------------------------------------------- + # Get the Camera Manager. + # + # The Camera Manager is responsible for enumerating all the Camera + # in the system, by associating Pipeline Handlers with media entities + # registered in the system. + # + # The CameraManager provides a list of available Cameras that + # applications can operate on. + # + # There can only be a single CameraManager within any process space. + + cm = libcam.CameraManager.singleton() + + # Just as a test, generate names of the Cameras registered in the + # system, and list them. + + for camera in cm.cameras: + print(f' - {camera_name(camera)}') + + # -------------------------------------------------------------------- + # Camera + # + # Camera are entities created by pipeline handlers, inspecting the + # entities registered in the system and reported to applications + # by the CameraManager. + # + # In general terms, a Camera corresponds to a single image source + # available in the system, such as an image sensor. + # + # Application lock usage of Camera by 'acquiring' them. + # Once done with it, application shall similarly 'release' the Camera. + # + # As an example, use the first available camera in the system after + # making sure that at least one camera is available. + # + # Cameras can be obtained by their ID or their index, to demonstrate + # this, the following code gets the ID of the first camera; then gets + # the camera associated with that ID (which is of course the same as + # cm.cameras[0]). + + if not cm.cameras: + print('No cameras were identified on the system.') + return -1 + + camera_id = cm.cameras[0].id + camera = cm.get(camera_id) + camera.acquire() + + # -------------------------------------------------------------------- + # Stream + # + # Each Camera supports a variable number of Stream. A Stream is + # produced by processing data produced by an image source, usually + # by an ISP. + # + # +-------------------------------------------------------+ + # | Camera | + # | +-----------+ | + # | +--------+ | |------> [ Main output ] | + # | | Image | | | | + # | | |---->| ISP |------> [ Viewfinder ] | + # | | Source | | | | + # | +--------+ | |------> [ Still Capture ] | + # | +-----------+ | + # +-------------------------------------------------------+ + # + # The number and capabilities of the Stream in a Camera are + # a platform dependent property, and it's the pipeline handler + # implementation that has the responsibility of correctly + # report them. + + # -------------------------------------------------------------------- + # Camera Configuration. + # + # Camera configuration is tricky! It boils down to assign resources + # of the system (such as DMA engines, scalers, format converters) to + # the different image streams an application has requested. + # + # Depending on the system characteristics, some combinations of + # sizes, formats and stream usages might or might not be possible. + # + # A Camera produces a CameraConfigration based on a set of intended + # roles for each Stream the application requires. + + config = camera.generate_configuration([libcam.StreamRole.Viewfinder]) + + # The CameraConfiguration contains a StreamConfiguration instance + # for each StreamRole requested by the application, provided + # the Camera can support all of them. + # + # Each StreamConfiguration has default size and format, assigned + # by the Camera depending on the Role the application has requested. + + stream_config = config.at(0) + print(f'Default viewfinder configuration is: {stream_config}') + + # Each StreamConfiguration parameter which is part of a + # CameraConfiguration can be independently modified by the + # application. + # + # In order to validate the modified parameter, the CameraConfiguration + # should be validated -before- the CameraConfiguration gets applied + # to the Camera. + # + # The CameraConfiguration validation process adjusts each + # StreamConfiguration to a valid value. + + # Validating a CameraConfiguration -before- applying it will adjust it + # to a valid configuration which is as close as possible to the one + # requested. + + config.validate() + print(f'Validated viewfinder configuration is: {stream_config}') + + # Once we have a validated configuration, we can apply it to the + # Camera. + + camera.configure(config) + + # -------------------------------------------------------------------- + # Buffer Allocation + # + # Now that a camera has been configured, it knows all about its + # Streams sizes and formats. The captured images need to be stored in + # framebuffers which can either be provided by the application to the + # library, or allocated in the Camera and exposed to the application + # by libcamera. + # + # An application may decide to allocate framebuffers from elsewhere, + # for example in memory allocated by the display driver that will + # render the captured frames. The application will provide them to + # libcamera by constructing FrameBuffer instances to capture images + # directly into. + # + # Alternatively libcamera can help the application by exporting + # buffers allocated in the Camera using a FrameBufferAllocator + # instance and referencing a configured Camera to determine the + # appropriate buffer size and types to create. + + allocator = libcam.FrameBufferAllocator(camera) + + for cfg in config: + ret = allocator.allocate(cfg.stream) + if ret < 0: + print('Can\'t allocate buffers') + return -1 + + allocated = len(allocator.buffers(cfg.stream)) + print(f'Allocated {allocated} buffers for stream') + + # -------------------------------------------------------------------- + # Frame Capture + # + # libcamera frames capture model is based on the 'Request' concept. + # For each frame a Request has to be queued to the Camera. + # + # A Request refers to (at least one) Stream for which a Buffer that + # will be filled with image data shall be added to the Request. + # + # A Request is associated with a list of Controls, which are tunable + # parameters (similar to v4l2_controls) that have to be applied to + # the image. + # + # Once a request completes, all its buffers will contain image data + # that applications can access and for each of them a list of metadata + # properties that reports the capture parameters applied to the image. + + stream = stream_config.stream + buffers = allocator.buffers(stream) + requests = [] + for i in range(len(buffers)): + request = camera.create_request() + if not request: + print('Can\'t create request') + return -1 + + buffer = buffers[i] + ret = request.add_buffer(stream, buffer) + if ret < 0: + print('Can\'t set buffer for request') + return -1 + + # Controls can be added to a request on a per frame basis. + request.set_control(libcam.controls.Brightness, 0.5) + + requests.append(request) + + # -------------------------------------------------------------------- + # Start Capture + # + # In order to capture frames the Camera has to be started and + # Request queued to it. Enough Request to fill the Camera pipeline + # depth have to be queued before the Camera start delivering frames. + # + # When a Request has been completed, it will be added to a list in the + # CameraManager and an event will be raised using eventfd. + # + # The list of completed Requests can be retrieved with + # CameraManager.get_ready_requests(), which will also clear the list in the + # CameraManager. + # + # The eventfd can be retrieved from CameraManager.event_fd, and the fd can + # be waited upon using e.g. Python's selectors. + + camera.start() + for request in requests: + camera.queue_request(request) + + sel = selectors.DefaultSelector() + sel.register(cm.event_fd, selectors.EVENT_READ, lambda fd: handle_camera_event(cm)) + + start_time = time.time() + + while time.time() - start_time < TIMEOUT_SEC: + events = sel.select() + for key, mask in events: + key.data(key.fileobj) + + # -------------------------------------------------------------------- + # Clean Up + # + # Stop the Camera, release resources and stop the CameraManager. + # libcamera has now released all resources it owned. + + camera.stop() + camera.release() + + return 0 + + +if __name__ == '__main__': + sys.exit(main())