Show a patch.

GET /api/patches/16101/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 16101,
    "url": "https://patchwork.libcamera.org/api/patches/16101/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/16101/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20220527144447.94891-31-tomi.valkeinen@ideasonboard.com>",
    "date": "2022-05-27T14:44:47",
    "name": "[libcamera-devel,v3,30/30] py: examples: Add simple-cam.py",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "6bcd5ac00ecccef8c14a33622208e0b7231c6b48",
    "submitter": {
        "id": 109,
        "url": "https://patchwork.libcamera.org/api/people/109/?format=api",
        "name": "Tomi Valkeinen",
        "email": "tomi.valkeinen@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/16101/mbox/",
    "series": [
        {
            "id": 3146,
            "url": "https://patchwork.libcamera.org/api/series/3146/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3146",
            "date": "2022-05-27T14:44:17",
            "name": "More misc Python patches",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/3146/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/16101/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/16101/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id BD4B2C3273\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 27 May 2022 14:45:43 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B02FE656AF;\n\tFri, 27 May 2022 16:45:42 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1049965653\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 27 May 2022 16:45:20 +0200 (CEST)",
            "from deskari.lan (91-156-85-209.elisa-laajakaista.fi\n\t[91.156.85.209])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 744C14E8E;\n\tFri, 27 May 2022 16:45:19 +0200 (CEST)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1653662742;\n\tbh=6g/hoz2maPEpfUMnMG2cGK6We+Z1IdFOdMGdL+Pg1yc=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=tTalZfSDgeb1DYwwGuynfNF4/AzrQthAj/iNZBdDm/xb1blNQlH6mY0ps8K+rRynq\n\tqMyPpfh8+BC96MmW8ImsNC7GFoLoHO032dzLJMvhV4FTi1AWJHpJqk2v7xqxpg8R8p\n\tl6zdFkGsqLCD0Wp0FZOWzAcZEB3xQhFPaheRc37NaPLp+kEMHiymmEiRF+mrv2yzTS\n\t8IAJO7EBErefDv3GARXnle9QJ0gQzu95NPWPZKn1mthsXqBAwa5+mW8sZdlRy3TMbz\n\t8dk3GSHP75UCDZlvT3sZp2IEYiy+GgHhvoeSv7ujO9u5wQT7e/HGd76vjvayUb2Ez/\n\tdBdhG6YctnlZQ==",
            "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1653662719;\n\tbh=6g/hoz2maPEpfUMnMG2cGK6We+Z1IdFOdMGdL+Pg1yc=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=pQXWvaJOclBIVru0uf25umT/0mM5WBMbo3KMan4C9tusns6wxV/aBJD1Shy57TR2+\n\tu8TYPbFs8fwjgetycLZwMHvfw/rQFwVWRQXwAWqARNkv9ug5MIwcmiW2gVOpZYlYuh\n\tTLZsi7pIit61ooCWJZyQff+qDxjfjfG5mW2WVi/U="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"pQXWvaJO\"; dkim-atps=neutral",
        "To": "libcamera-devel@lists.libcamera.org,\n\tDavid Plowman <david.plowman@raspberrypi.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tJacopo Mondi <jacopo@jmondi.org>",
        "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",
        "Content-Transfer-Encoding": "8bit",
        "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": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "From": "Tomi Valkeinen via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Add a Python version of simple-cam from:\n\nhttps://git.libcamera.org/libcamera/simple-cam.git\n\nLet's keep this in the libcamera repository until the Python API has\nstabilized a bit more, and then we could move this to the simple-cam\nrepo.\n\nSigned-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n---\n src/py/examples/simple-cam.py | 352 ++++++++++++++++++++++++++++++++++\n 1 file changed, 352 insertions(+)\n create mode 100755 src/py/examples/simple-cam.py",
    "diff": "diff --git a/src/py/examples/simple-cam.py b/src/py/examples/simple-cam.py\nnew file mode 100755\nindex 00000000..ce17175b\n--- /dev/null\n+++ b/src/py/examples/simple-cam.py\n@@ -0,0 +1,352 @@\n+#!/usr/bin/env python3\n+\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n+\n+# A simple libcamera capture example\n+#\n+# This is a python version of simple-cam from:\n+# https://git.libcamera.org/libcamera/simple-cam.git\n+#\n+# \\todo Move to simple-cam repository when the Python API has stabilized more\n+\n+import libcamera as libcam\n+import selectors\n+import sys\n+import time\n+\n+TIMEOUT_SEC = 3\n+\n+\n+def handle_camera_event(cm):\n+    # cm.read_event() will not block here, as we know there is an event to read.\n+    # We have to read the event to clear it.\n+\n+    cm.read_event()\n+\n+    reqs = cm.get_ready_requests()\n+\n+    # Process the captured frames\n+\n+    for req in reqs:\n+        process_request(req)\n+\n+\n+def process_request(request):\n+    global camera\n+\n+    print()\n+\n+    print(f'Request completed: {request}')\n+\n+    # When a request has completed, it is populated with a metadata control\n+    # list that allows an application to determine various properties of\n+    # the completed request. This can include the timestamp of the Sensor\n+    # capture, or its gain and exposure values, or properties from the IPA\n+    # such as the state of the 3A algorithms.\n+    #\n+    # To examine each request, print all the metadata for inspection. A custom\n+    # application can parse each of these items and process them according to\n+    # its needs.\n+\n+    requestMetadata = request.metadata\n+    for id, value in requestMetadata.items():\n+        print(f'\\t{id.name} = {value}')\n+\n+    # Each buffer has its own FrameMetadata to describe its state, or the\n+    # usage of each buffer. While in our simple capture we only provide one\n+    # buffer per request, a request can have a buffer for each stream that\n+    # is established when configuring the camera.\n+    #\n+    # This allows a viewfinder and a still image to be processed at the\n+    # same time, or to allow obtaining the RAW capture buffer from the\n+    # sensor along with the image as processed by the ISP.\n+\n+    buffers = request.buffers\n+    for _, buffer in buffers.items():\n+        metadata = buffer.metadata\n+\n+        # Print some information about the buffer which has completed.\n+        print(f' seq: {metadata.sequence:06} timestamp: {metadata.timestamp} bytesused: ' +\n+              '/'.join([str(p.bytes_used) for p in metadata.planes]))\n+\n+        # Image data can be accessed here, but the FrameBuffer\n+        # must be mapped by the application\n+\n+    # Re-queue the Request to the camera.\n+    request.reuse()\n+    camera.queue_request(request)\n+\n+\n+# ----------------------------------------------------------------------------\n+# Camera Naming.\n+#\n+# Applications are responsible for deciding how to name cameras, and present\n+# that information to the users. Every camera has a unique identifier, though\n+# this string is not designed to be friendly for a human reader.\n+#\n+# To support human consumable names, libcamera provides camera properties\n+# that allow an application to determine a naming scheme based on its needs.\n+#\n+# In this example, we focus on the location property, but also detail the\n+# model string for external cameras, as this is more likely to be visible\n+# information to the user of an externally connected device.\n+#\n+# The unique camera ID is appended for informative purposes.\n+#\n+def camera_name(camera):\n+    props = camera.properties\n+    location = props.get(libcam.properties.Location, None)\n+\n+    if location == libcam.properties.LocationEnum.Front:\n+        name = 'Internal front camera'\n+    elif location == libcam.properties.LocationEnum.Back:\n+        name = 'Internal back camera'\n+    elif location == libcam.properties.LocationEnum.External:\n+        name = 'External camera'\n+        if libcam.properties.Model in props:\n+            name += f' \"{props[libcam.properties.Model]}\"'\n+    else:\n+        name = 'Undefined location'\n+\n+    name += f' ({camera.id})'\n+\n+    return name\n+\n+\n+def main():\n+    global camera\n+\n+    # --------------------------------------------------------------------\n+    # Get the Camera Manager.\n+    #\n+    # The Camera Manager is responsible for enumerating all the Camera\n+    # in the system, by associating Pipeline Handlers with media entities\n+    # registered in the system.\n+    #\n+    # The CameraManager provides a list of available Cameras that\n+    # applications can operate on.\n+    #\n+    # There can only be a single CameraManager within any process space.\n+\n+    cm = libcam.CameraManager.singleton()\n+\n+    # Just as a test, generate names of the Cameras registered in the\n+    # system, and list them.\n+\n+    for camera in cm.cameras:\n+        print(f' - {camera_name(camera)}')\n+\n+    # --------------------------------------------------------------------\n+    # Camera\n+    #\n+    # Camera are entities created by pipeline handlers, inspecting the\n+    # entities registered in the system and reported to applications\n+    # by the CameraManager.\n+    #\n+    # In general terms, a Camera corresponds to a single image source\n+    # available in the system, such as an image sensor.\n+    #\n+    # Application lock usage of Camera by 'acquiring' them.\n+    # Once done with it, application shall similarly 'release' the Camera.\n+    #\n+    # As an example, use the first available camera in the system after\n+    # making sure that at least one camera is available.\n+    #\n+    # Cameras can be obtained by their ID or their index, to demonstrate\n+    # this, the following code gets the ID of the first camera; then gets\n+    # the camera associated with that ID (which is of course the same as\n+    # cm.cameras[0]).\n+\n+    if not cm.cameras:\n+        print('No cameras were identified on the system.')\n+        return -1\n+\n+    camera_id = cm.cameras[0].id\n+    camera = cm.get(camera_id)\n+    camera.acquire()\n+\n+    # --------------------------------------------------------------------\n+    # Stream\n+    #\n+    # Each Camera supports a variable number of Stream. A Stream is\n+    # produced by processing data produced by an image source, usually\n+    # by an ISP.\n+    #\n+    #   +-------------------------------------------------------+\n+    #   | Camera                                                |\n+    #   |                +-----------+                          |\n+    #   | +--------+     |           |------> [  Main output  ] |\n+    #   | | Image  |     |           |                          |\n+    #   | |        |---->|    ISP    |------> [   Viewfinder  ] |\n+    #   | | Source |     |           |                          |\n+    #   | +--------+     |           |------> [ Still Capture ] |\n+    #   |                +-----------+                          |\n+    #   +-------------------------------------------------------+\n+    #\n+    # The number and capabilities of the Stream in a Camera are\n+    # a platform dependent property, and it's the pipeline handler\n+    # implementation that has the responsibility of correctly\n+    # report them.\n+\n+    # --------------------------------------------------------------------\n+    # Camera Configuration.\n+    #\n+    # Camera configuration is tricky! It boils down to assign resources\n+    # of the system (such as DMA engines, scalers, format converters) to\n+    # the different image streams an application has requested.\n+    #\n+    # Depending on the system characteristics, some combinations of\n+    # sizes, formats and stream usages might or might not be possible.\n+    #\n+    # A Camera produces a CameraConfigration based on a set of intended\n+    # roles for each Stream the application requires.\n+\n+    config = camera.generate_configuration([libcam.StreamRole.Viewfinder])\n+\n+    # The CameraConfiguration contains a StreamConfiguration instance\n+    # for each StreamRole requested by the application, provided\n+    # the Camera can support all of them.\n+    #\n+    # Each StreamConfiguration has default size and format, assigned\n+    # by the Camera depending on the Role the application has requested.\n+\n+    stream_config = config.at(0)\n+    print(f'Default viewfinder configuration is: {stream_config}')\n+\n+    # Each StreamConfiguration parameter which is part of a\n+    # CameraConfiguration can be independently modified by the\n+    # application.\n+    #\n+    # In order to validate the modified parameter, the CameraConfiguration\n+    # should be validated -before- the CameraConfiguration gets applied\n+    # to the Camera.\n+    #\n+    # The CameraConfiguration validation process adjusts each\n+    # StreamConfiguration to a valid value.\n+\n+    # Validating a CameraConfiguration -before- applying it will adjust it\n+    # to a valid configuration which is as close as possible to the one\n+    # requested.\n+\n+    config.validate()\n+    print(f'Validated viewfinder configuration is: {stream_config}')\n+\n+    # Once we have a validated configuration, we can apply it to the\n+    # Camera.\n+\n+    camera.configure(config)\n+\n+    # --------------------------------------------------------------------\n+    # Buffer Allocation\n+    #\n+    # Now that a camera has been configured, it knows all about its\n+    # Streams sizes and formats. The captured images need to be stored in\n+    # framebuffers which can either be provided by the application to the\n+    # library, or allocated in the Camera and exposed to the application\n+    # by libcamera.\n+    #\n+    # An application may decide to allocate framebuffers from elsewhere,\n+    # for example in memory allocated by the display driver that will\n+    # render the captured frames. The application will provide them to\n+    # libcamera by constructing FrameBuffer instances to capture images\n+    # directly into.\n+    #\n+    # Alternatively libcamera can help the application by exporting\n+    # buffers allocated in the Camera using a FrameBufferAllocator\n+    # instance and referencing a configured Camera to determine the\n+    # appropriate buffer size and types to create.\n+\n+    allocator = libcam.FrameBufferAllocator(camera)\n+\n+    for cfg in config:\n+        ret = allocator.allocate(cfg.stream)\n+        if ret < 0:\n+            print('Can\\'t allocate buffers')\n+            return -1\n+\n+        allocated = len(allocator.buffers(cfg.stream))\n+        print(f'Allocated {allocated} buffers for stream')\n+\n+    # --------------------------------------------------------------------\n+    # Frame Capture\n+    #\n+    # libcamera frames capture model is based on the 'Request' concept.\n+    # For each frame a Request has to be queued to the Camera.\n+    #\n+    # A Request refers to (at least one) Stream for which a Buffer that\n+    # will be filled with image data shall be added to the Request.\n+    #\n+    # A Request is associated with a list of Controls, which are tunable\n+    # parameters (similar to v4l2_controls) that have to be applied to\n+    # the image.\n+    #\n+    # Once a request completes, all its buffers will contain image data\n+    # that applications can access and for each of them a list of metadata\n+    # properties that reports the capture parameters applied to the image.\n+\n+    stream = stream_config.stream\n+    buffers = allocator.buffers(stream)\n+    requests = []\n+    for i in range(len(buffers)):\n+        request = camera.create_request()\n+        if not request:\n+            print('Can\\'t create request')\n+            return -1\n+\n+        buffer = buffers[i]\n+        ret = request.add_buffer(stream, buffer)\n+        if ret < 0:\n+            print('Can\\'t set buffer for request')\n+            return -1\n+\n+        # Controls can be added to a request on a per frame basis.\n+        request.set_control(libcam.controls.Brightness, 0.5)\n+\n+        requests.append(request)\n+\n+    # --------------------------------------------------------------------\n+    # Start Capture\n+    #\n+    # In order to capture frames the Camera has to be started and\n+    # Request queued to it. Enough Request to fill the Camera pipeline\n+    # depth have to be queued before the Camera start delivering frames.\n+    #\n+    # When a Request has been completed, it will be added to a list in the\n+    # CameraManager and an event will be raised using eventfd.\n+    #\n+    # The list of completed Requests can be retrieved with\n+    # CameraManager.get_ready_requests(), which will also clear the list in the\n+    # CameraManager.\n+    #\n+    # The eventfd can be retrieved from CameraManager.event_fd, and the fd can\n+    # be waited upon using e.g. Python's selectors.\n+\n+    camera.start()\n+    for request in requests:\n+        camera.queue_request(request)\n+\n+    sel = selectors.DefaultSelector()\n+    sel.register(cm.event_fd, selectors.EVENT_READ, lambda fd: handle_camera_event(cm))\n+\n+    start_time = time.time()\n+\n+    while time.time() - start_time < TIMEOUT_SEC:\n+        events = sel.select()\n+        for key, mask in events:\n+            key.data(key.fileobj)\n+\n+    # --------------------------------------------------------------------\n+    # Clean Up\n+    #\n+    # Stop the Camera, release resources and stop the CameraManager.\n+    # libcamera has now released all resources it owned.\n+\n+    camera.stop()\n+    camera.release()\n+\n+    return 0\n+\n+\n+if __name__ == '__main__':\n+    sys.exit(main())\n",
    "prefixes": [
        "libcamera-devel",
        "v3",
        "30/30"
    ]
}