[{"id":15515,"web_url":"https://patchwork.libcamera.org/comment/15515/","msgid":"<fa92ee00-5a2f-c3ae-8adc-f9de15d81a68@ideasonboard.com>","date":"2021-03-08T14:18:36","subject":"Re: [libcamera-devel] [PATCH v9] Documentation: Add IPA writers\n\tguide","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi Paul,\n\nOn 08/03/2021 07:50, Paul Elder wrote:\n> Add a guide about writing IPAs.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> ---\n> Changes in v9:\n> - update documentation to include customizable init() and direct return\n>   of int32\n> \n> No change in v8\n> \n> Changes in v8:\n> - fix bullet points\n> - update wording about struct field names\n> - fix typos\n> \n> Changes in v7:\n> - fix TODO syntax\n> - update the generated struct fiels\n>   - no more postfix underscore\n> \n> Changes in v6:\n> - namespacing is now required\n> - start() is now customizable\n> - {pipeline_name} is no longer required\n> - fix code block indentations\n> \n> Changes in v5:\n> - fix doxygen compile errors\n> - update example struct names from raspberry pi changes\n> - add todo for restricting pre-start() to sync and post-start() to async\n> \n> Changes in v4.1:\n> - Add section on namespacing, custom data structures, compiling, and\n>   usage of the data structures and interface\n> - Add examples to the main ipa interface and event ipa interface\n>   sections\n> \n> New in v4\n> ---\n>  Documentation/guides/ipa.rst | 508 +++++++++++++++++++++++++++++++++++\n>  Documentation/index.rst      |   1 +\n>  Documentation/meson.build    |   1 +\n>  3 files changed, 510 insertions(+)\n>  create mode 100644 Documentation/guides/ipa.rst\n> \n> diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst\n> new file mode 100644\n> index 00000000..5590d2d5\n> --- /dev/null\n> +++ b/Documentation/guides/ipa.rst\n> @@ -0,0 +1,508 @@\n> +.. SPDX-License-Identifier: CC-BY-SA-4.0\n> +\n> +IPA Writers Guide\n> +=================\n> +\n> +IPA are Image Processing Algorithm modules. They provide functionality that\n\n'IPA are' sounds odd.\n\n'IPA modules are' ?\n\n> +the pipeline handler can use for image processing.\n> +\n> +This guide so far only covers definition the IPA interface, and how to plumb\n\ndefinition the ??\n\n\n'the definition of the' perhaps?\n\n\n> +the connection between the pipeline handler and the IPA.\n> +\n> +The IPA interface and protocol\n> +------------------------------\n> +\n> +The IPA interface defines the interface between the pipeline handler and the\n> +IPA. Specifically, it defines the functions that the IPA exposes that the\n> +pipeline handler can call, and the Signals that the pipeline handler can\n> +connect to to receive data from the IPA asynchronously. In addition, it\n\nto to ?\n\nto, in order to\n\n\n> +contains any custom data structures that the pipeline handler and IPA may\n> +pass to each other.\n> +\n> +The IPA protocol refers to the agreement between the pipeline handler and the\n> +IPA regarding the expected response(s) from the IPA for given calls to the IPA.\n> +This protocol doesn't need to be declared anywhere in code, but it would be\n> +useful to document it, as there may be many different IPAs for one pipeline\n\n/many different IPAs/ multiple IPA implementations / ?\n\n> +handler.\n> +\n> +The IPA interface must be defined in a mojom file. The interface includes:\n> +\n> +- the functions that the pipeline handler can call from the IPA\n> +\n> +- Signals in the pipeline handler that the IPA can emit\n> +\n> +- any data structures that are to be passed between the pipeline handler and the IPA\n> +\n> +All IPA of a given pipeline handler use the same IPA interface. The IPA\n\nAll IPA modules of ...\n\n> +interface definition is thus likely to be written by the pipeline handler\n> +author, based on how they imagine the pipeline handler will interact with\n> +the IPA.\n> +\n> +The entire IPA interface, including the functions, Signals, and any custom\n> +structs shall be defined in in a file named {pipeline_name}.mojom under\n\nin in\n\n\n> +include/libcamera/ipa/ using the mojo interface definition language (IDL). This\n> +will be covered in detail in the following sections.\n> +\n> +Namespacing\n> +-----------\n> +\n> +Namespacing is required, to avoid potential collisions with libcamera types.\n> +Use mojo's module directive for this.\n> +\n> +It must be the first meaningful line in the mojo data definition file, for\n> +example (defining a raspberry pi IPA):\n> +\n> +.. code-block:: none\n> +\n> +        module ipa.rpi;\n> +\n> +This will become the ipa::rpi namespace in C++ code.\n> +\n> +Data containers\n> +---------------\n> +\n> +Since the data passed between the pipeline handler and the IPA must support\n> +serialization, any custom data containers must be defined with the mojo IDL.\n> +\n> +The following list of libcamera objects are supported in the interface\n> +definition, and may be used as function parameter types or struct field types:\n> +\n> +- CameraSensorInfo\n> +\n> +- ControlInfoMap\n> +\n> +- ControlList\n> +\n> +- FileDescriptor\n> +\n> +- IPABuffer\n> +\n> +- IPASettings\n> +\n> +- IPAStream\n> +\n> +- Point\n> +\n> +- Size\n> +\n> +- SizeRange\n> +\n> +- Rectangle\n> +\n> +To use them, core.mojom must be included in the mojo data definition file:\n> +\n> +.. code-block:: none\n> +\n> +        import \"include/libcamera/ipa/core.mojom\";\n> +\n> +Other custom structs may be defined and used as well. There is no requirement\n> +that they must be defined before usage. enums and structs are supported.\n> +\n> +The following is an example of a definition of an enum, for the purpose of\n> +being used as flags:\n> +\n> +.. code-block:: none\n> +\n> +        enum ConfigParameters {\n> +                ConfigLsTable = 0x01,\n> +                ConfigStaggeredWrite = 0x02,\n> +                ConfigSensor = 0x04,\n> +                ConfigDropFrames = 0x08,\n> +        };\n> +\n> +The following is an example of a definition of a struct:\n> +\n> +.. code-block:: none\n> +\n> +        struct ConfigInput {\n> +                uint32 op;\n> +                uint32 transform;\n> +                FileDescriptor lsTableHandle;\n> +                int32 lsTableHandleStatic = -1;\n> +                map<uint32, IPAStream> streamConfig;\n> +                array<IPABuffer> buffers;\n> +        };\n> +\n> +This example has some special things about it. First of all, it uses the\n> +FileDescriptor data type. This type must be used to ensure that the file\n> +descriptor that it contains is translated property across the IPC boundary\n> +(when the IPA is in an isolated process).\n> +\n> +This does mean that if the file descriptor should be sent without being\n> +translated (for example, for the IPA to tell the pipeline handler which\n> +fd *that the pipeline handler holds* to act on), then it must be in a\n> +regular int32 type.\n> +\n> +This example also illustrates that struct fields may have default values, as\n> +is assigned to lsTableHandleStatic. This is the value that the field will\n> +take when the struct is constructed with the default constructor.\n> +\n> +Arrays and maps are supported as well. They are translated to C++ vectors and\n> +maps, respectively. The members of the arrays and maps are embedded, and cannot\n> +be const.\n> +\n> +Note that nullable fields, static-length arrays, handles, and unions, which\n> +are supported by mojo, are not supported by our code generator.\n> +\n> +TODO: what about versioning, and numbered fields?\n\nIs this a comment or the rst? or expected to be viewable in the end\ndocument until it's handled?\n\n\n> +\n> +The Main IPA interface\n> +----------------------\n> +\n> +The IPA interface is split in two parts, the Main IPA interface, which\n> +describes the functions that the pipeline handler can call from the IPA,\n> +and the Event IPA interface, which describes the Signals in the pipeline\n> +handler that the IPA can emit. Both must be defined. This section focuses\n> +on the Main IPA interface.\n> +\n> +The main interface must be named as IPA{pipeline_name}Interface.\n> +\n> +At minimum, the following three functions must be present (and implemented):\n\nAt a minimum\n\n> +\n> +- init();\n> +\n> +- start();\n> +\n> +- stop();\n> +\n> +All three of these functions are synchronous.\n> +\n> +TODO: Restrict pre-start to synchronous, and post-start to asynchronous\n> +\n> +The parameters for start() and init() may be customized.\n> +\n> +A configure() method is recommended. Any ContolInfoMap instances that will be\n> +used by the IPA must be sent to the IPA from the pipeline handler, at configure\n> +time, for example.\n> +\n> +All input parameters will become const references, except for arithmetic types,\n> +which will be passed by value. Output parameters will become pointers, unless\n> +the first output parameter is an int32, or there is only one primitive output\n> +parameter, in which case it will become a a regular return value.\n\na a\n\nSeems we have a word duplicator here somewhere ;-)\n\n\n\n> +\n> +const is not allowed inside of arrays and maps. mojo arrays will become C++\n> +std::vector<>.\n> +\n> +By default, all methods defined in the main interface are synchronous. This\n> +means that in the case of IPC (ie. isolated IPA), the function call will not\n\n/ie./i.e./\n\n\n> +return until the return value or output parameters are ready. To specify an\n> +asynchronous function, the [async] attribute can be used. Asynchronous\n> +methods must not have any return value or output parameters, since in the\n> +case of IPC the call needs to return immediately.\n> +\n> +It is also possible that the IPA will not be run in isolation. In this case,\n> +the IPA thread will not exist until start() is called. This means that in the\n> +case of no isolation, asynchronous calls cannot be made before start(). Since\n> +the IPA interface must be the same regardless of isolation, the same\n> +restriction applies to the case of isolation, and any function that will be\n> +called before start() must be synchronous.\n> +\n> +In addition, any call made after start() and before stop() must be\n> +asynchronous. The motivation for this is to avoid damaging real time\n\nreal-time here I think. Otherwise it could mis-read as 'real .... time\nperformance'\n\n\n> +performance of the pipeline handler. If the pipeline handler wants some data\n> +from the IPA, the IPA should return the data asynchronously via an event\n> +(see \"The Event IPA interface\").\n> +\n> +The following is an example of a main interface definition:\n> +\n> +.. code-block:: none\n> +\n> +        interface IPARPiInterface {\n> +                init(IPASettings settings, string sensorName)\n> +                        => (int32 ret, bool metadataSupport);\n> +                start() => (int32 ret);\n> +                stop();\n> +\n> +                configure(CameraSensorInfo sensorInfo,\n> +                          map<uint32, IPAStream> streamConfig,\n> +                          map<uint32, ControlInfoMap> entityControls,\n> +                          ConfigInput ipaConfig)\n> +                => (int32 ret, ConfigOutput results);\n> +\n> +                mapBuffers(array<IPABuffer> buffers);\n> +                unmapBuffers(array<uint32> ids);\n> +\n> +                [async] signalStatReady(uint32 bufferId);\n> +                [async] signalQueueRequest(ControlList controls);\n> +                [async] signalIspPrepare(ISPConfig data);\n> +        };\n> +\n> +\n> +The first three functions are the required functions. Functions do not need to\n> +have return values, like stop(), mapBuffers(), and unmapBuffers(). In the case\n> +of asynchronous functions, as explained before, they *must not* have return\n> +values.\n> +\n> +The Event IPA interface\n> +-----------------------\n> +\n> +The event IPA interface describes the Signals in the pipeline handler that the\n> +IPA can emit. It must be defined. If there are no event functions, then it may\n> +be empty. These emissions are meant to notify the pipeline handler of some\n> +event, such as reqeust data is ready, and *must not* be used to drive the\n\nrequest\n\n> +camera pipeline from the IPA.\n> +\n> +The event interface must be named as IPA{pipeline_name}EventInterface.\n> +\n> +Methods defined in the event interface are implictly asynchronous.\n\nimplicitly\n\n\n> +They thus cannot return any value. Specifying the [async] tag is not\n\nThey thus? or Thus they?\n\n> +necessary.\n> +\n> +Methods defined in the event interface will become Signals in the IPA\n> +interface. The IPA can emit signals, while the pipeline handler can connect\n> +slots to them.\n> +\n> +The following is an example of an event interface definition:\n> +\n> +.. code-block:: none\n> +\n> +        interface IPARPiEventInterface {\n> +                statsMetadataComplete(uint32 bufferId, ControlList controls);\n> +                runIsp(uint32 bufferId);\n> +                embeddedComplete(uint32 bufferId);\n> +                setIsp(ControlList controls);\n> +                setStaggered(ControlList controls);\n> +        };\n> +\n> +Compiling the IPA interface\n> +---------------------------\n> +\n> +After the IPA interface is defined in include/libcamera/ipa/{pipeline_name}.mojom,\n> +an entry for it must be added in meson so that it can be compiled. The filename\n> +must be added to the ipa_mojom_files object in include/libcamera/ipa/meson.build.\n> +\n> +For example, adding the raspberrypi.mojom file to meson:\n> +\n> +.. code-block:: none\n> +\n> +        ipa_mojom_files = [\n> +            'raspberrypi.mojom',\n> +        ]\n> +\n> +This will cause the mojo data definition file to be compiled. Specifically, it\n> +generates five files:\n> +\n> +- a header describing the custom data structures, and the complete IPA interface\n> +\n> +- a serializer implementing de/serialization for the custom data structures\n> +\n> +- a proxy header describing a specialized IPA proxy\n> +\n> +- a proxy source implementing the IPA proxy\n> +\n> +- a proxy worker source implementing the other end of the IPA proxy\n> +\n> +The pipeline handler and the IPA only require the header and the proxy header.\n> +The serializer is only used internally by the proxy.\n> +\n> +Using the custom data structures\n> +--------------------------------\n> +\n> +To use the custom data structures that are defined in the mojo data definition\n> +file, the following header must be included:\n> +\n> +.. code-block:: C++\n> +\n> +   #include <libcamera/ipa/{pipeline_name}_ipa_interface.h>\n> +\n> +The POD types of the structs simply become their C++ counterparts, eg. uint32\n> +in mojo will become uint32_t in C++. mojo map becomes C++ std::map, and mojo\n> +array becomes C++ std::vector. All members of maps and vectors are embedded,\n> +and are not pointers. The members cannot be const.\n> +\n> +The names of all the fields of structs can be used in C++ exactly the same way\n\n'in C++ exactly the same' sounds odd?\n\n'can be used in C++ in exactly the same way...' ?\n\nNot sure how necessary but in my head I think I need the 'in' or\nsomething there.\n\n\n> +they are defined in the data definition file. For example, the following\n> +struct as defined in the mojo file:\n> +\n> +.. code-block:: none\n> +\n> +   struct SensorConfig {\n> +        uint32 gainDelay = 1;\n> +        uint32 exposureDelay;\n> +        uint32 sensorMetadata;\n> +   };\n> +\n> +Will become this in C++:\n> +\n> +.. code-block:: C++\n> +\n> +   struct SensorConfig {\n> +        uint32_t gainDelay;\n> +        uint32_t exposureDelay;\n> +        uint32_t sensorMetadata;\n> +   };\n\ndoes uint32 really get converted to uint32_t ? or is that a copy/paste\nissue?\n\n\n\n> +\n> +The generated structs will also have two constructors, a constructor that\n> +fills all fields with the default values, and a second constructor that takes\n> +a value for every field. The default value constructor will fill in the fields\n> +with the specified default value, if it exists. In the above example, `gainDelay_`\n\n/value,/value/\n\n> +will be initialized to 1. If no default value is specified, then it will be\n> +filled in as zero (or -1 for a FileDescriptor type).\n> +\n> +All fields and constructors/deconstructors in these generated structs are public.\n\nShouldn't deconstructor be destructor?\n\n> +\n> +Using the IPA interface (pipeline handler)\n> +------------------------------------------\n> +\n> +The following headers are necessary to use an IPA in the pipeline handler\n> +(with raspberrypi as an example):\n> +\n> +.. code-block:: C++\n> +\n> +   #include <libcamera/ipa/raspberrypi_ipa_interface.h>\n> +   #include <libcamera/ipa/raspberrypi_ipa_proxy.h>\n> +\n> +The first header includes definitions of the custom data structures, and\n> +the definition of the complete IPA interface (including both the Main and\n> +the Event IPA interfaces). The name of the header file comes from the name\n> +of the mojom file, which in this case was raspberrypi.mojom.\n> +\n> +The second header inclues the definition of the specialized IPA proxy. It\n\nincludes\n\n\n> +exposes the complete IPA interface. We will see how to use it in this section.\n> +\n> +In the pipeline handler, we first need to construct a specialized IPA proxy.\n> +From the point of view of the pipeline hander, this is the object that is the\n> +IPA.\n> +\n> +To do so, we invoke the IPAManager:\n> +\n> +.. code-block:: C++\n> +\n> +        std::unique_ptr<ipa::rpi::IPAProxyRPi> ipa_ =\n> +                IPAManager::createIPA<ipa::rpi::IPAProxyRPi>(pipe_, 1, 1);\n> +\n> +The ipa::rpi namespace comes from the namespace that we defined in the mojo\n> +data definition file, in the \"Namespacing\" section. The name of the proxy,\n> +IPAProxyRPi, comes from the name given to the main IPA interface,\n> +IPARPiInterface, in the \"The Main IPA interface\" section.\n> +\n> +The return value of IPAManager::createIPA shall be error-checked, to confirm\n> +that the returned pointer is not a nullptr.\n> +\n> +After this, before initializing the IPA, slots should be connected to all of\n> +the IPA's Signals, as defined in the Event IPA interface:\n> +\n> +.. code-block:: C++\n> +\n> +\tipa_->statsMetadataComplete.connect(this, &RPiCameraData::statsMetadataComplete);\n> +\tipa_->runIsp.connect(this, &RPiCameraData::runIsp);\n> +\tipa_->embeddedComplete.connect(this, &RPiCameraData::embeddedComplete);\n> +\tipa_->setIsp.connect(this, &RPiCameraData::setIsp);\n> +\tipa_->setStaggered.connect(this, &RPiCameraData::setStaggered);\n> +\n> +The slot functions have a function signature based on the function definition\n> +in the Event IPA interface. All plain old data (POD) types are as-is (with\n> +their C++ versions, eg. uint32 -> uint32_t), and all structs are const references.\n\nAhh ok - so it was real above.\n\n\n> +\n> +For example, for the following entry in the Event IPA interface:\n> +\n> +.. code-block:: none\n> +\n> +   statsMetadataComplete(uint32 bufferId, ControlList controls);\n> +\n> +A function with the following function signature shall be connected to the\n> +signal:\n> +\n> +.. code-block:: C++\n> +\n> +   statsMetadataComplete(uint32_t bufferId, const ControlList &controls);\n> +\n> +After connecting the slots to the signals, the IPA should be initialized\n> +(using the main interface definition example from earlier):\n> +\n> +.. code-block:: C++\n> +\n> +   IPASettings settings{};\n> +   bool metadataSupport;\n> +   int ret = ipa_->init(settings, \"sensor name\", &metadataSupport);\n> +\n> +At this point, any IPA functions that were defined in the Main IPA interface\n> +can be called as if they were regular member functions, for example (based on\n> +the main interface definition example from earlier):\n> +\n> +.. code-block:: C++\n> +\n> +   ipa_->start();\n> +   int ret = ipa_->configure(sensorInfo_, streamConfig, entityControls, ipaConfig, &result);\n> +   ipa_->signalStatReady(RPi::BufferMask::STATS | static_cast<unsigned int>(index));\n> +\n> +Remember that any functions designated as asynchronous *must not* be called\n> +before start().\n\nCan we trap this in anyway in the code to signal an error?\n\nI.e. can the proxies have a flag 'running' and only sync functions can\nrun when running == false, and async functions when running == true?\n\n(An idea to discuss separately, not an update to this patch).\n\n\n> +\n> +Notice that for both init() and configure(), the first output parameter is a\n> +direct return, since it is an int32, while the other output parameter is a\n> +pointer-based output parameter.\n> +\n> +TODO: anything special about start() and stop() ?\n\nThey're both synchronous? Stop must have stopped everything running, and\nno events should be signalled to the pipeline handler after this point?\n\n\n\n> +\n> +Using the IPA interface (IPA)\n> +-----------------------------\n> +\n> +The following header is necessary to implement an IPA (with raspberrypi as\n> +an example):\n> +\n> +.. code-block:: C++\n> +\n> +   #include <libcamera/ipa/raspberrypi_ipa_interface.h>\n> +\n> +This header includes definitions of the custom data structures, and\n> +the definition of the complete IPA interface (including both the Main and\n> +the Event IPA interfaces). The name of the header file comes from the name\n> +of the mojom file, which in this case was raspberrypi.mojom.\n> +\n> +The IPA must implement the IPA interface class that is defined in the header.\n> +In the case of our example, that is ipa::rpi::IPARPiInterface. The ipa::rpi\n> +namespace comes from the namespace that we defined in the mojo data definition\n> +file, in the \"Namespacing\" section. The name of the interface is the same as\n> +the name given to the Main IPA interface.\n> +\n> +The function signature rules are the same as for the slots in the pipeline\n> +handler side; PODs are passed by value, and structs are passed by const\n> +reference. For the Main IPA interface, output values are also allowed (only\n> +for synchronous calls), so there may be output parameters as well. If the\n> +output parameter is a single POD it will be returned by value, otherwise\n> +(multiple PODs or struct(s)) it will be returned by output parameter pointers.\n> +\n> +For example, for the following function specification in the Main IPA interface\n> +definition:\n> +\n> +.. code-block:: none\n> +\n> +   configure(CameraSensorInfo sensorInfo,\n> +             uint32 exampleNumber,\n> +             map<uint32, IPAStream> streamConfig,\n> +             map<uint32, ControlInfoMap> entityControls,\n> +             ConfigInput ipaConfig)\n> +   => (int32 ret, ConfigOutput results);\n> +\n> +We will need to implement a function with the following function signature:\n> +\n> +.. code-block:: C++\n> +\n> +\tint configure(const CameraSensorInfo &sensorInfo,\n> +                      uint32_t exampleNumber,\n> +\t\t      const std::map<unsigned int, IPAStream> &streamConfig,\n\nsome odd indentation above on exampleNumber?\n\n> +\t\t      const std::map<unsigned int, ControlInfoMap> &entityControls,\n> +\t\t      const ipa::rpi::ConfigInput &data,\n> +\t\t      ipa::rpi::ConfigOutput *response);\n> +\n> +The return value is int, because the first output parameter is int32.  The rest\n> +of the output parameters (in this case, only response) become output parameter\n> +pointers. The non-POD input parameters become const references, and the POD\n> +input parameter is passed by value.\n> +\n> +At any time (though usually only in response to an IPA call), the IPA may send\n> +data to the pipeline handler by emitting signals. These signals are defined\n> +in the C++ IPA interface class (which is in the generated and included header).\n> +\n> +For example, for the following function defined in the Event IPA interface:\n> +\n> +.. code-block:: none\n> +\n> +   statsMetadataComplete(uint32 bufferId, ControlList controls);\n> +\n> +We can emit a signal like so:\n> +\n> +.. code-block:: C++\n> +\n> +   statsMetadataComplete.emit(bufferId & RPi::BufferMask::ID, libcameraMetadata_);\n> diff --git a/Documentation/index.rst b/Documentation/index.rst\n> index 285ca7c3..b40a4586 100644\n> --- a/Documentation/index.rst\n> +++ b/Documentation/index.rst\n> @@ -16,6 +16,7 @@\n>     Developer Guide <guides/introduction>\n>     Application Writer's Guide <guides/application-developer>\n>     Pipeline Handler Writer's Guide <guides/pipeline-handler>\n> +   IPA Writer's guide <guides/ipa>\n>     Tracing guide <guides/tracing>\n>     Environment variables <environment_variables>\n>     Sensor driver requirements <sensor_driver_requirements>\n> diff --git a/Documentation/meson.build b/Documentation/meson.build\n> index 9950465d..8cf68a07 100644\n> --- a/Documentation/meson.build\n> +++ b/Documentation/meson.build\n> @@ -54,6 +54,7 @@ if sphinx.found()\n>          'environment_variables.rst',\n>          'guides/application-developer.rst',\n>          'guides/introduction.rst',\n> +        'guides/ipa.rst',\n>          'guides/pipeline-handler.rst',\n>          'guides/tracing.rst',\n>          'index.rst',\n\n\nWith all that, throw a:\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\non there.","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 17D77BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  8 Mar 2021 14:18:41 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6D71468A9C;\n\tMon,  8 Mar 2021 15:18:40 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D2FB1602ED\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  8 Mar 2021 15:18:38 +0100 (CET)","from [192.168.0.20]\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 4DE3EAC1;\n\tMon,  8 Mar 2021 15:18:38 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"LYXUxet0\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1615213118;\n\tbh=0V8iLYVsgTSInQczLsk0K0CE2+KlaHb46r9KPFpVba8=;\n\th=Reply-To:Subject:To:References:From:Date:In-Reply-To:From;\n\tb=LYXUxet0b5MRVK1ObvqCrr5WCc6lVi3glsK09hxn0N8Bmz/s28V/xcf+5X0qTa+zy\n\tkvEgL7+/D8SuRzU2JepY7DlzJplyEgH06d+mvfcyAp59WgsvqikWq5raR3HFGtlyD2\n\tm2m+NefafEmd6gtdCT4utKHoRyxosUxy1k0+fqYM=","To":"Paul Elder <paul.elder@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20210308075028.13719-1-paul.elder@ideasonboard.com>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\t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\n\tWZw01QYHU/GUV/zHJSFk","Organization":"Ideas on Board","Message-ID":"<fa92ee00-5a2f-c3ae-8adc-f9de15d81a68@ideasonboard.com>","Date":"Mon, 8 Mar 2021 14:18:36 +0000","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101\n\tThunderbird/68.10.0","MIME-Version":"1.0","In-Reply-To":"<20210308075028.13719-1-paul.elder@ideasonboard.com>","Content-Language":"en-GB","Subject":"Re: [libcamera-devel] [PATCH v9] Documentation: Add IPA writers\n\tguide","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>","Reply-To":"kieran.bingham@ideasonboard.com","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]