diff options
1025 files changed, 81969 insertions, 12424 deletions
diff --git a/.clang-format b/.clang-format index cac7029f..7fc30f61 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only # -# clang-format configuration file. Intended for clang-format >= 7. +# clang-format configuration file. Intended for clang-format >= 12. # # For more information, see: # @@ -75,11 +75,12 @@ IncludeCategories: Priority: 9 # Qt includes (match before C++ standard library) - Regex: '<Q([A-Za-z0-9\-_])+>' + CaseSensitive: true Priority: 9 # Headers in <> with an extension. (+system libraries) - Regex: '<([A-Za-z0-9\-_])+\.h>' Priority: 2 - # System headers + # System headers - Regex: '<sys/.*>' Priority: 2 # C++ standard library includes (no extension) @@ -99,7 +100,7 @@ IncludeCategories: # IPA Interfaces - Regex: '<libcamera/ipa/.*\.h>' Priority: 7 - # libcamera Internal headers in "" + # libcamera Internal headers in "" - Regex: '"libcamera/internal/.*\.h"' Priority: 8 # Other libraries headers with one group per library (.h or .hpp) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a76b36a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: CC0-1.0 + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{cpp,h}] +indent_size = 8 +indent_style = tab + +[*.json] +indent_size = 4 +indent_style = space + +[*.py] +indent_size = 4 +indent_style = space + +[*.yaml] +indent_size = 2 +indent_style = space + +[{meson.build,meson_options.txt}] +indent_size = 4 +indent_style = space @@ -6,3 +6,4 @@ *.patch *.pyc __pycache__/ +venv/ diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile-common.in index a86ea6c1..045c19dd 100644 --- a/Documentation/Doxyfile.in +++ b/Documentation/Doxyfile-common.in @@ -20,35 +20,19 @@ TOC_INCLUDE_HEADINGS = 0 CASE_SENSE_NAMES = YES QUIET = YES - -INPUT = "@TOP_SRCDIR@/include/libcamera" \ - "@TOP_SRCDIR@/src/ipa/ipu3" \ - "@TOP_SRCDIR@/src/ipa/libipa" \ - "@TOP_SRCDIR@/src/libcamera" \ - "@TOP_BUILDDIR@/include/libcamera" \ - "@TOP_BUILDDIR@/src/libcamera" +WARN_AS_ERROR = @WARN_AS_ERROR@ FILE_PATTERNS = *.c \ *.cpp \ + *.dox \ *.h RECURSIVE = YES -EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ - @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \ - @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \ - @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \ - @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \ - @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \ - @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \ - @TOP_SRCDIR@/src/libcamera/pipeline/ \ - @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ - @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ - @TOP_BUILDDIR@/src/libcamera/proxy/ - EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ @TOP_BUILDDIR@/include/libcamera/ipa/*_proxy.h \ @TOP_BUILDDIR@/include/libcamera/ipa/ipu3_*.h \ + @TOP_BUILDDIR@/include/libcamera/ipa/mali-c55_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \ @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h @@ -68,8 +52,6 @@ EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \ EXCLUDE_SYMLINKS = YES -HTML_OUTPUT = api-html - GENERATE_LATEX = NO MACRO_EXPANSION = YES diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in new file mode 100644 index 00000000..5343bc2b --- /dev/null +++ b/Documentation/Doxyfile-internal.in @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 + +@INCLUDE_PATH = @TOP_BUILDDIR@/Documentation +@INCLUDE = Doxyfile-common + +HIDE_UNDOC_CLASSES = NO +HIDE_UNDOC_MEMBERS = NO +HTML_OUTPUT = internal-api-html +INTERNAL_DOCS = YES +ENABLED_SECTIONS = internal + +INPUT = "@TOP_SRCDIR@/Documentation" \ + "@TOP_SRCDIR@/include/libcamera" \ + "@TOP_SRCDIR@/src/ipa/ipu3" \ + "@TOP_SRCDIR@/src/ipa/libipa" \ + "@TOP_SRCDIR@/src/libcamera" \ + "@TOP_BUILDDIR@/include/libcamera" \ + "@TOP_BUILDDIR@/src/libcamera" + +EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ + @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \ + @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \ + @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \ + @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \ + @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \ + @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \ + @TOP_SRCDIR@/src/libcamera/pipeline/ \ + @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \ + @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \ + @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ + @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ + @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \ + @TOP_BUILDDIR@/src/libcamera/proxy/ diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in new file mode 100644 index 00000000..36bb5758 --- /dev/null +++ b/Documentation/Doxyfile-public.in @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 + +@INCLUDE_PATH = @TOP_BUILDDIR@/Documentation +@INCLUDE = Doxyfile-common + +HIDE_UNDOC_CLASSES = YES +HIDE_UNDOC_MEMBERS = YES +HTML_OUTPUT = api-html +INTERNAL_DOCS = NO + +INPUT = "@TOP_SRCDIR@/Documentation" \ + ${inputs} + +EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/class.h \ + @TOP_SRCDIR@/include/libcamera/base/object.h \ + @TOP_SRCDIR@/include/libcamera/base/span.h \ + @TOP_SRCDIR@/src/libcamera/base/class.cpp \ + @TOP_SRCDIR@/src/libcamera/base/object.cpp + +PREDEFINED += __DOXYGEN_PUBLIC__ diff --git a/Documentation/api-html/index.rst b/Documentation/api-html/index.rst index 9e630fc0..2f09833d 100644 --- a/Documentation/api-html/index.rst +++ b/Documentation/api-html/index.rst @@ -2,7 +2,7 @@ .. _api: -API -=== +API Reference +============= :: Placeholder for Doxygen documentation diff --git a/Documentation/camera-sensor-model.rst b/Documentation/camera-sensor-model.rst index 58bf70e1..87a25bf4 100644 --- a/Documentation/camera-sensor-model.rst +++ b/Documentation/camera-sensor-model.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + .. _camera-sensor-model: .. todo: Move to Doxygen-generated documentation @@ -105,35 +107,34 @@ control: will be downscaled in its vertical and horizontal sizes by the specified factor. - .. code-block:: + .. code-block:: c :caption: Definition: The horizontal and vertical binning factors horizontal_binning = xBin; vertical_binning = yBin; - - skipping Skipping reduces the image resolution by skipping the read-out of a number of adjacent pixels. The skipping factor is specified by the 'increment' number (number of pixels to 'skip') in the vertical and horizontal directions and for even and odd rows and columns. - .. code-block:: + .. code-block:: c :caption: Definition: The horizontal and vertical skipping factors - horizontal_skipping = (xOddInc + xEvenInc) / 2 - vertical_skipping = (yOddInc + yEvenInc) / 2 + horizontal_skipping = (xOddInc + xEvenInc) / 2; + vertical_skipping = (yOddInc + yEvenInc) / 2; Different sensors perform the binning and skipping stages in different orders. For the sake of computing the final output image size the order of execution is not relevant. The overall down-scaling factor is obtained by combining the binning and skipping factors. - .. code-block:: + .. code-block:: c :caption: Definition: The total scaling factor (binning + sub-sampling) - total_horizontal_downscale = horizontal_binning + horizontal_skipping - total_vertical_downscale = vertical_binning + vertical_skipping + total_horizontal_downscale = horizontal_binning + horizontal_skipping; + total_vertical_downscale = vertical_binning + vertical_skipping; 4. The output size is used to specify any additional cropping on the sub-sampled @@ -159,16 +160,16 @@ configurations: the *pixel rate* of the data sent on the MIPI CSI-2 bus allows to compute the image stream frame rate. The equation is the well known: - .. code-block:: + .. code-block:: c - frame_duration = total_frame_size / pixel_rate - frame_rate = 1 / frame_duration + frame_duration = total_frame_size / pixel_rate; + frame_rate = 1 / frame_duration; where the *pixel_rate* parameter is the result of the sensor's configuration of the MIPI CSI-2 bus *(the following formula applies to MIPI CSI-2 when used on MIPI D-PHY physical protocol layer only)* - .. code-block:: + .. code-block:: c - pixel_rate = CSI-2_link_freq * 2 * nr_of_lanes / bits_per_sample + pixel_rate = csi_2_link_freq * 2 * nr_of_lanes / bits_per_sample; diff --git a/Documentation/code-of-conduct.rst b/Documentation/code-of-conduct.rst new file mode 100644 index 00000000..0edd1e99 --- /dev/null +++ b/Documentation/code-of-conduct.rst @@ -0,0 +1,96 @@ +.. SPDX-License-Identifier: CC-BY-4.0 + +.. include:: documentation-contents.rst + +.. _code-of-conduct: + +Contributor Covenant Code of Conduct +==================================== + +Our Pledge +---------- + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +Our Standards +------------- + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +-------------------- + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +Scope +----- + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at conduct@libcamera.org, or directly to +any member of the code of conduct team: + +* Kieran Bingham <kieran.bingham@ideasonboard.com> +* Laurent Pinchart <laurent.pinchart@ideasonboard.com> + +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. The project team +is obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +.. _Contributor Covenant: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + diff --git a/Documentation/coding-style.rst b/Documentation/coding-style.rst index 053fdd99..6ac3a4a0 100644 --- a/Documentation/coding-style.rst +++ b/Documentation/coding-style.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + .. _coding-style-guidelines: Coding Style Guidelines @@ -59,7 +61,7 @@ document: underscores in between * All formatting rules specified in the selected sections of the Linux kernel Code Style for indentation, braces, spacing, etc -* Header guards are formatted as '__LIBCAMERA_FILE_NAME_H__' +* Headers are guarded by the use of '#pragma once' Order of Includes ~~~~~~~~~~~~~~~~~ @@ -215,7 +217,7 @@ shall be avoided when possible, but are allowed when required (for instance to implement factories with auto-registration). They shall not depend on any other global variable, should run a minimal amount of code in the constructor and destructor, and code that contains dependencies should be moved to a later -point in time. +point in time. Error Handling ~~~~~~~~~~~~~~ diff --git a/Documentation/conf.py b/Documentation/conf.py index 7eeea7f3..089f114c 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -37,8 +37,11 @@ author = u'Kieran Bingham, Jacopo Mondi, Laurent Pinchart, Niklas Söderlund' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx.ext.graphviz' ] +graphviz_output_format = 'svg' + # Add any paths that contain templates here, relative to this directory. templates_path = [] @@ -61,7 +64,12 @@ language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [ + '_build', + 'Thumbs.db', + '.DS_Store', + 'documentation-contents.rst', +] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None diff --git a/Documentation/contributing.rst b/Documentation/contributing.rst index 2f0b4921..18b1914a 100644 --- a/Documentation/contributing.rst +++ b/Documentation/contributing.rst @@ -8,6 +8,10 @@ Whether you would like to help with coding, documentation, testing, proposing new features, or just discussing the project with the community, you can join our official public communication channels, or simply check out the code. +The project adheres to a :ref:`code of conduct <code-of-conduct>` that +maintainers, contributors and community members are expected to follow in all +online and offline communication. + Mailing List ------------ @@ -134,4 +138,5 @@ By making a contribution to this project, I certify that: .. toctree:: :hidden: + Code of Conduct <code-of-conduct> Coding Style <coding-style> diff --git a/Documentation/design/ae.rst b/Documentation/design/ae.rst new file mode 100644 index 00000000..df9b1fa7 --- /dev/null +++ b/Documentation/design/ae.rst @@ -0,0 +1,331 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +Design of Exposure and Gain controls +==================================== + +This document explains the design and rationale of the controls related to +exposure and gain. This includes the all-encompassing auto-exposure (AE), the +manual exposure control, and the manual gain control. + +Description of the problem +-------------------------- + +Sub controls +^^^^^^^^^^^^ + +There are more than one control that make up total exposure: exposure time, +gain, and aperture (though for now we will not consider aperture). We already +had individual controls for setting the values of manual exposure and manual +gain, but for switching between auto mode and manual mode we only had a +high-level boolean AeEnable control that would set *both* exposure and gain to +auto mode or manual mode; we had no way to set one to auto and the other to +manual. + +So, we need to introduce two new controls to act as "levers" to indicate +individually for exposure and gain if the value would come from AEGC or if it +would come from the manual control value. + +Aperture priority +^^^^^^^^^^^^^^^^^ + +We eventually may need to support aperture, and so whatever our solution is for +having only some controls on auto and the others on manual needs to be +extensible. + +Flickering when going from auto to manual +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When a manual exposure or gain value is requested by the application, it costs +a few frames worth of time for them to take effect. This means that during a +transition from auto to manual, there would be flickering in the control values +and the transition won't be smooth. + +Take for instance the following flow, where we start on auto exposure (which +for the purposes of the example increments by 1 each frame) and we want to +switch seamlessly to manual exposure, which involves copying the exposure value +computed by the auto exposure algorithm: + +:: + + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + | N | | N+1 | | N+2 | | N+3 | | N+4 | | N+5 | | N+6 | + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + + Mode requested: Auto Auto Auto Manual Manual Manual Manual + Exp requested: N/A N/A N/A 2 2 2 2 + Set in Frame: N+2 N+3 N+4 N+5 N+6 N+7 N+8 + + Mode used: Auto Auto Auto Auto Auto Manual Manual + Exp used: 0 1 2 3 4 2 2 + +As we can see, after frame N+2 completes, we copy the exposure value that was +used for frame N+2 (which was computed by AE algorithm), and queue that value +into request N+3 with manual mode on. However, as it takes two frames for the +exposure to be set, the exposure still changes since it is set by AE, and we +get a flicker in the exposure during the switch from auto to manual. + +A solution is to *not submit* any exposure value when manual mode is enabled, +and wait until the manual mode as been "applied" before copying the exposure +value: + +:: + + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + | N | | N+1 | | N+2 | | N+3 | | N+4 | | N+5 | | N+6 | + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + + Mode requested: Auto Auto Auto Manual Manual Manual Manual + Exp requested: N/A N/A N/A None None None 5 + Set in Frame: N+2 N+3 N+4 N+5 N+6 N+7 N+8 + + Mode used: Auto Auto Auto Auto Auto Manual Manual + Exp used: 0 1 2 3 4 5 5 + +In practice, this works. However, libcamera has a policy where once a control +is submitted, its value is saved and does not need to be resubmitted. If the +manual exposure value was set while auto mode was on, in theory the value would +be saved, so when manual mode is enabled, the exposure value that was +previously set would immediately be used. Clearly this solution isn't correct, +but it can serve as the basis for a proper solution, with some more rigorous +rules. + +Existing solutions +------------------ + +Raspberry Pi +^^^^^^^^^^^^ + +The Raspberry Pi IPA gets around the lack of individual AeEnable controls for +exposure and gain by using magic values. When AeEnable is false, if one of the +manual control values was set to 0 then the value computed by AEGC would be +used for just that control. This solution isn't desirable, as it prevents +that magic value from being used as a valid value. + +To get around the flickering issue, when AeEnable is false, the Raspberry Pi +AEGC simply stops updating the values to be set, without restoring the +previously set manual exposure time and gain. This works, but is not a proper +solution. + +Android +^^^^^^^ + +The Android HAL specification requires that exposure and gain (sensitivity) +must both be manual or both be auto. It cannot be that one is manual while the +other is auto, so they simply don't support sub controls. + +For the flickering issue, the Android HAL has an AeLock control. To transition +from auto to manual, the application would keep AE on auto, and turn on the +lock. Once the lock has propagated through, then the value can be copied from +the result into the request and the lock disabled and the mode set to manual. + +The problem with this solution is, besides the extra complexity, that it is +ambiguous what happens if there is a state transition from manual to locked +(even though it's a state transition that doesn't make sense). If locked is +defined to "use the last automatically computed values" then it could use the +values from the last time it AE was set to auto, or it would be undefined if AE +was never auto (eg. it started out as manual), or if AE is implemented to run +in the background it could just use the current values that are computed. If +locked is defined to "use the last value that was set" there would be less +ambiguity. Still, it's better if we can make it impossible to execute this +nonsensical state transition, and if we can reduce the complexity of having +this extra control or extra setting on a lever. + +Summary of goals +---------------- + +- We need a lock of some sort, to instruct the AEGC to not update output + results + +- We need manual modes, to override the values computed by the AEGC + +- We need to support seamless transitions from auto to manual, and do so + without flickering + +- We need custom minimum values for the manual controls; that is, no magic + values for enabling/disabling auto + +- All of these need to be done with AE sub-controls (exposure time, analogue + gain) and be extensible to aperture in the future + +Our solution +------------ + +A diagram of our solution: + +:: + + +----------------------------+-------------+------------------+-----------------+ + | INPUT | ALGORITHM | RESULT | OUTPUT | + +----------------------------+-------------+------------------+-----------------+ + + ExposureTimeMode ExposureTimeMode + ---------------------+----------------------------------------+-----------------> + 0: Auto | | + 1: Manual | V + | |\ + | | \ + | /----------------------------------> | 1| ExposureTime + | | +-------------+ exposure time | | --------------> + \--)--> | | --------------> | 0| + ExposureTime | | | | / + ------------------------+--> | | |/ + | | AeState + | AEGC | -----------------------------------> + AnalogueGain | | + ------------------------+--> | | |\ + | | | | \ + /--)--> | | --------------> | 0| AnalogueGain + | | +-------------+ analogue gain | | --------------> + | \----------------------------------> | 1| + | | / + | |/ + | ^ + AnalogueGainMode | | AnalogueGainMode + ---------------------+----------------------------------------+-----------------> + 0: Auto + 1: Manual + + AeEnable + - True -> ExposureTimeMode:Auto + AnalogueGainMode:Auto + - False -> ExposureTimeMode:Manual + AnalogueGainMode:Manual + + +The diagram is divided in four sections horizontally: + +- Input: The values received from the request controls + +- Algorithm: The algorithm itself + +- Result: The values calculated by the algorithm + +- Output: The values reported in result metadata and applied to the device + +The four input controls are divided between manual values (ExposureTime and +AnalogueGain), and operation modes (ExposureTimeMode and AnalogueGainMode). The +former are the manual values, the latter control how they're applied. The two +modes are independent from each other, and each can take one of two values: + +- Auto (0): The AGC computes the value normally. The AGC result is applied + to the output. The manual value is ignored *and is not retained*. + +- Manual (1): The AGC uses the manual value internally. The corresponding + manual control from the request is applied to the output. The AGC result + is ignored. + +The AeState control reports the state of the unified AEGC block. If both +ExposureTimeMode and AnalogueGainMode are set to manual then it will report +Idle. If at least one of the two is set to auto, then AeState will report +if the AEGC has Converged or not (Searching). This control replaces the old +AeLocked control, as it was insufficient for reporting the AE state. + +There is a caveat to manual mode: the manual control value is not retained if +it is set during auto mode. This means that if manual mode is entered without +also setting the manual value, then it will enter a state similar to "locked", +where the last automatically computed value while the mode was auto will be +used. Once the manual value is set, then that will be used and retained as +usual. + +This simulates an auto -> locked -> manual or auto -> manual state transition, +and makes it impossible to do the nonsensical manual -> locked state +transition. + +AeEnable still exists to allow applications to set the mode of all the +sub-controls at once. Besides being for convenience, this will also be useful +when we eventually implement an aperture control. This is because applications +that will be made before aperture will have been available would still be able +to set aperture mode to auto or manual, as opposed to having the aperture stuck +at auto while the application really wanted manual. Although the aperture would +still be stuck at an uncontrollable value, at least it would be at a static +usable value as opposed to varying via the AEGC algorithm. + +With this solution, the earlier example would become: + +:: + + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + | N+2 | | N+3 | | N+4 | | N+5 | | N+6 | | N+7 | | N+8 | | N+9 | | N+10| + +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ + Mode requested: Auto Manual Manual Manual Manual Manual Manual Manual Manual + Exp requested: N/A None None None None 10 None 10 10 + Set in Frame: N+4 N+5 N+6 N+7 N+8 N+9 N+10 N+11 N+12 + + Mode used: Auto Auto Auto Manual Manual Manual Manual Manual Manual + Exp used: 2 3 4 5 5 5 5 10 10 + +This example is extended by a few frames to exhibit the simulated "locked" +state. At frame N+5 the application has confirmed that the manual mode has been +entered, but does not provide a manual value until request N+7. Thus, the value +that is used in requests N+5 and N+6 (where the mode is disabled), comes from +the last value that was used when the mode was auto, which comes from frame +N+4. + +Then, in N+7, a manual value of 10 is supplied. It takes until frame N+9 for +the exposure to be applied. N+8 does not supply a manual value, but the last +supplied value is retained, so a manual value of 10 is still used and set in +frame N+10. + +Although this behavior is the same as what we had with waiting for the manual +mode to propagate (in the section "Description of the problem"), this time it +is correct as we have defined specifically that if a manual value was specified +while the mode was auto, it will not be retained. + +Description of the controls +--------------------------- + +As described above, libcamera offers the following controls related to exposure +and gain: + +- AnalogueGain + +- AnalogueGainMode + +- ExposureTime + +- ExposureTimeMode + +- AeState + +- AeEnable + +Auto-exposure and auto-gain can be enabled and disabled separately using the +ExposureTimeMode and AnalogueGainMode controls respectively. The AeEnable +control can also be used, as it sets both of the modes simultaneously. The +AeEnable control is not returned in metadata. + +When the respective mode is set to auto, the respective value that is computed +by the AEGC algorithm is applied to the image sensor. Any value that is +supplied in the manual ExposureTime/AnalogueGain control is ignored and not +retained. Another way to understand this is that when the mode transitions from +auto to manual, the internally stored control value is overwritten with the +last value computed by the auto algorithm. + +This means that when we transition from auto to manual without supplying a +manual control value, the last value that was set by the AEGC algorithm will +keep be used. This can be used to do a flickerless transition from auto to +manual as described earlier. If the camera started out in manual mode and no +corresponding value has been supplied yet, then a best-effort default value +shall be set. + +The manual control value can be set in the same request as setting the mode to +auto if the desired manual control value is already known. + +Transitioning from manual to auto shall be implicitly flickerless, as the AEGC +algorithms are expected to start running from the last manual value. + +The AeState metadata reports the state of the AE algorithm. As AE cannot +compute exposure and gain separately, the state of the AE component is +unified. There are three states: Idle, Searching, and Converged. + +The state shall be Idle if both ExposureTimeMode and AnalogueGainMode +are set to Manual. If the camera only supports one of the two controls, +then the state shall be Idle if that one control is set to Manual. If +the camera does not support Manual for at least one of the two controls, +then the state will never be Idle, as AE will always be running. + +The state shall be Searching if at least one of exposure or gain calculated +by the AE algorithm is used (that is, at least one of the two modes is Auto), +*and* the value(s) have not converged yet. + +The state shall be Converged if at least one of exposure or gain calculated +by the AE algorithm is used (that is, at least one of the two modes is Auto), +*and* the value(s) have converged. diff --git a/Documentation/docs.rst b/Documentation/docs.rst deleted file mode 100644 index a6e8a59a..00000000 --- a/Documentation/docs.rst +++ /dev/null @@ -1,400 +0,0 @@ -.. SPDX-License-Identifier: CC-BY-SA-4.0 - -.. contents:: - :local: - -************* -Documentation -************* - -.. toctree:: - :hidden: - - API <api-html/index> - -API -=== - -The libcamera API is extensively documented using Doxygen. The :ref:`API -nightly build <api>` contains the most up-to-date API documentation, built from -the latest master branch. - -Feature Requirements -==================== - -Device enumeration ------------------- - -The library shall support enumerating all camera devices available in the -system, including both fixed cameras and hotpluggable cameras. It shall -support cameras plugged and unplugged after the initialization of the -library, and shall offer a mechanism to notify applications of camera plug -and unplug. - -The following types of cameras shall be supported: - -* Internal cameras designed for point-and-shoot still image and video - capture usage, either controlled directly by the CPU, or exposed through - an internal USB bus as a UVC device. - -* External UVC cameras designed for video conferencing usage. - -Other types of camera, including analog cameras, depth cameras, thermal -cameras, external digital picture or movie cameras, are out of scope for -this project. - -A hardware device that includes independent camera sensors, such as front -and back sensors in a phone, shall be considered as multiple camera devices -for the purpose of this library. - -Independent Camera Devices --------------------------- - -When multiple cameras are present in the system and are able to operate -independently from each other, the library shall expose them as multiple -camera devices and support parallel operation without any additional usage -restriction apart from the limitations inherent to the hardware (such as -memory bandwidth, CPU usage or number of CSI-2 receivers for instance). - -Independent processes shall be able to use independent cameras devices -without interfering with each other. A single camera device shall be -usable by a single process at a time. - -Multiple streams support ------------------------- - -The library shall support multiple video streams running in parallel -for each camera device, within the limits imposed by the system. - -Per frame controls ------------------- - -The library shall support controlling capture parameters for each stream -on a per-frame basis, on a best effort basis based on the capabilities of the -hardware and underlying software stack (including kernel drivers and -firmware). It shall apply capture parameters to the frame they target, and -report the value of the parameters that have effectively been used for each -captured frame. - -When a camera device supports multiple streams, the library shall allow both -control of each stream independently, and control of multiple streams -together. Streams that are controlled together shall be synchronized. No -synchronization is required for streams controlled independently. - -Capability Enumeration ----------------------- - -The library shall expose capabilities of each camera device in a way that -allows applications to discover those capabilities dynamically. Applications -shall be allowed to cache capabilities for as long as they are using the -library. If capabilities can change at runtime, the library shall offer a -mechanism to notify applications of such changes. Applications shall not -cache capabilities in long term storage between runs. - -Capabilities shall be discovered dynamically at runtime from the device when -possible, and may come, in part or in full, from platform configuration -data. - -Device Profiles ---------------- - -The library may define different camera device profiles, each with a minimum -set of required capabilities. Applications may use those profiles to quickly -determine the level of features exposed by a device without parsing the full -list of capabilities. Camera devices may implement additional capabilities -on top of the minimum required set for the profile they expose. - -3A and Image Enhancement Algorithms ------------------------------------ - -The camera devices shall implement auto exposure, auto gain and auto white -balance. Camera devices that include a focus lens shall implement auto -focus. Additional image enhancement algorithms, such as noise reduction or -video stabilization, may be implemented. - -All algorithms may be implemented in hardware or firmware outside of the -library, or in software in the library. They shall all be controllable by -applications. - -The library shall be architectured to isolate the 3A and image enhancement -algorithms in a component with a documented API, respectively called the 3A -component and the 3A API. The 3A API shall be stable, and shall allow both -open-source and closed-source implementations of the 3A component. - -The library may include statically-linked open-source 3A components, and -shall support dynamically-linked open-source and closed-source 3A -components. - -Closed-source 3A Component Sandboxing -------------------------------------- - -For security purposes, it may be desired to run closed-source 3A components -in a separate process. The 3A API would in such a case be transported over -IPC. The 3A API shall make it possible to use any IPC mechanism that -supports passing file descriptors. - -The library may implement an IPC mechanism, and shall support third-party -platform-specific IPC mechanisms through the implementation of a -platform-specific 3A API wrapper. No modification to the library shall be -needed to use such third-party IPC mechanisms. - -The 3A component shall not directly access any device node on the system. -Such accesses shall instead be performed through the 3A API. The library -shall validate all accesses and restrict them to what is absolutely required -by 3A components. - -V4L2 Compatibility Layer ------------------------- - -The project shall support traditional V4L2 application through an additional -libcamera wrapper library. The wrapper library shall trap all accesses to -camera devices through `LD_PRELOAD`, and route them through libcamera to -emulate a high-level V4L2 camera device. It shall expose camera device -features on a best-effort basis, and aim for the level of features -traditionally available from a UVC camera designed for video conferencing. - -Android Camera HAL v3 Compatibility ------------------------------------ - -The library API shall expose all the features required to implement an -Android Camera HAL v3 on top of libcamera. Some features of the HAL may be -omitted as long as they can be implemented separately in the HAL, such as -JPEG encoding, or YUV reprocessing. - - -Camera Stack -============ - -:: - - a c / +-------------+ +-------------+ +-------------+ +-------------+ - p a | | Native | | Framework | | Native | | Android | - p t | | V4L2 | | Application | | libcamera | | Camera | - l i | | Application | | (gstreamer) | | Application | | Framework | - i o \ +-------------+ +-------------+ +-------------+ +-------------+ - n ^ ^ ^ ^ - | | | | - l a | | | | - i d v v | v - b a / +-------------+ +-------------+ | +-------------+ - c p | | V4L2 | | Camera | | | Android | - a t | | Compat. | | Framework | | | Camera | - m a | | | | (gstreamer) | | | HAL | - e t \ +-------------+ +-------------+ | +-------------+ - r i ^ ^ | ^ - a o | | | | - n | | | | - / | ,................................................ - | | ! : Language : ! - l f | | ! : Bindings : ! - i r | | ! : (optional) : ! - b a | | \...............................................' - c m | | | | | - a e | | | | | - m w | v v v v - e o | +----------------------------------------------------------------+ - r r | | | - a k | | libcamera | - | | | - \ +----------------------------------------------------------------+ - ^ ^ ^ - Userspace | | | - ------------------------ | ---------------- | ---------------- | --------------- - Kernel | | | - v v v - +-----------+ +-----------+ +-----------+ - | Media | <--> | Video | <--> | V4L2 | - | Device | | Device | | Subdev | - +-----------+ +-----------+ +-----------+ - -The camera stack comprises four software layers. From bottom to top: - -* The kernel drivers control the camera hardware and expose a - low-level interface to userspace through the Linux kernel V4L2 - family of APIs (Media Controller API, V4L2 Video Device API and - V4L2 Subdev API). - -* The libcamera framework is the core part of the stack. It - handles all control of the camera devices in its core component, - libcamera, and exposes a native C++ API to upper layers. Optional - language bindings allow interfacing to libcamera from other - programming languages. - - Those components live in the same source code repository and - all together constitute the libcamera framework. - -* The libcamera adaptation is an umbrella term designating the - components that interface to libcamera in other frameworks. - Notable examples are a V4L2 compatibility layer, a gstreamer - libcamera element, and an Android camera HAL implementation based - on libcamera. - - Those components can live in the libcamera project source code - in separate repositories, or move to their respective project's - repository (for instance the gstreamer libcamera element). - -* The applications and upper level frameworks are based on the - libcamera framework or libcamera adaptation, and are outside of - the scope of the libcamera project. - - -libcamera Architecture -====================== - -:: - - ---------------------------< libcamera Public API >--------------------------- - ^ ^ - | | - v v - +-------------+ +-------------------------------------------------+ - | Camera | | Camera Device | - | Devices | | +---------------------------------------------+ | - | Manager | | | Device-Agnostic | | - +-------------+ | | | | - ^ | | +------------------------+ | - | | | | ~~~~~~~~~~~~~~~~~~~~~ | - | | | | { +---------------+ } | - | | | | } | ////Image//// | { | - | | | | <-> | /Processing// | } | - | | | | } | /Algorithms// | { | - | | | | { +---------------+ } | - | | | | ~~~~~~~~~~~~~~~~~~~~~ | - | | | | ======================== | - | | | | +---------------+ | - | | | | | //Pipeline/// | | - | | | | <-> | ///Handler/// | | - | | | | | ///////////// | | - | | +--------------------+ +---------------+ | - | | Device-Specific | - | +-------------------------------------------------+ - | ^ ^ - | | | - v v v - +--------------------------------------------------------------------+ - | Helpers and Support Classes | - | +-------------+ +-------------+ +-------------+ +-------------+ | - | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | | - | | Support | | Allocator | | IPC | | Manager | | - | +-------------+ +-------------+ +-------------+ +-------------+ | - | +-------------+ +-------------+ | - | | Pipeline | | ... | | - | | Runner | | | | - | +-------------+ +-------------+ | - +--------------------------------------------------------------------+ - - /// Device-Specific Components - ~~~ Sandboxing - -While offering a unified API towards upper layers, and presenting -itself as a single library, libcamera isn't monolithic. It exposes -multiple components through its public API, is built around a set of -separate helpers internally, uses device-specific components and can -load dynamic plugins. - -Camera Devices Manager - The Camera Devices Manager provides a view of available cameras - in the system. It performs cold enumeration and runtime camera - management, and supports a hotplug notification mechanism in its - public API. - - To avoid the cost associated with cold enumeration of all devices - at application start, and to arbitrate concurrent access to camera - devices, the Camera Devices Manager could later be split to a - separate service, possibly with integration in platform-specific - device management. - -Camera Device - The Camera Device represents a camera device to upper layers. It - exposes full control of the device through the public API, and is - thus the highest level object exposed by libcamera. - - Camera Device instances are created by the Camera Devices - Manager. An optional function to create new instances could be exposed - through the public API to speed up initialization when the upper - layer knows how to directly address camera devices present in the - system. - -Pipeline Handler - The Pipeline Handler manages complex pipelines exposed by the kernel drivers - through the Media Controller and V4L2 APIs. It abstracts pipeline handling to - hide device-specific details to the rest of the library, and implements both - pipeline configuration based on stream configuration, and pipeline runtime - execution and scheduling when needed by the device. - - This component is device-specific and is part of the libcamera code base. As - such it is covered by the same free software license as the rest of libcamera - and needs to be contributed upstream by device vendors. The Pipeline Handler - lives in the same process as the rest of the library, and has access to all - helpers and kernel camera-related devices. - -Image Processing Algorithms - Together with the hardware image processing and hardware statistics - collection, the Image Processing Algorithms implement 3A (Auto-Exposure, - Auto-White Balance and Auto-Focus) and other algorithms. They run on the CPU - and interact with the kernel camera devices to control hardware image - processing based on the parameters supplied by upper layers, closing the - control loop of the ISP. - - This component is device-specific and is loaded as an external plugin. It can - be part of the libcamera code base, in which case it is covered by the same - license, or provided externally as an open-source or closed-source component. - - The component is sandboxed and can only interact with libcamera through - internal APIs specifically marked as such. In particular it will have no - direct access to kernel camera devices, and all its accesses to image and - metadata will be mediated by dmabuf instances explicitly passed to the - component. The component must be prepared to run in a process separate from - the main libcamera process, and to have a very restricted view of the system, - including no access to networking APIs and limited access to file systems. - - The sandboxing mechanism isn't defined by libcamera. One example - implementation will be provided as part of the project, and platforms vendors - will be able to provide their own sandboxing mechanism as a plugin. - - libcamera should provide a basic implementation of Image Processing - Algorithms, to serve as a reference for the internal API. Device vendors are - expected to provide a full-fledged implementation compatible with their - Pipeline Handler. One goal of the libcamera project is to create an - environment in which the community will be able to compete with the - closed-source vendor binaries and develop a high quality open source - implementation. - -Helpers and Support Classes - While Pipeline Handlers are device-specific, implementations are expected to - share code due to usage of identical APIs towards the kernel camera drivers - and the Image Processing Algorithms. This includes without limitation handling - of the MC and V4L2 APIs, buffer management through dmabuf, and pipeline - discovery, configuration and scheduling. Such code will be factored out to - helpers when applicable. - - Other parts of libcamera will also benefit from factoring code out to - self-contained support classes, even if such code is present only once in the - code base, in order to keep the source code clean and easy to read. This - should be the case for instance for plugin management. - - -V4L2 Compatibility Layer ------------------------- - -V4L2 compatibility is achieved through a shared library that traps all -accesses to camera devices and routes them to libcamera to emulate high-level -V4L2 camera devices. It is injected in a process address space through -`LD_PRELOAD` and is completely transparent for applications. - -The compatibility layer exposes camera device features on a best-effort basis, -and aims for the level of features traditionally available from a UVC camera -designed for video conferencing. - - -Android Camera HAL ------------------- - -Camera support for Android is achieved through a generic Android -camera HAL implementation on top of libcamera. The HAL will implement internally -features required by Android and missing from libcamera, such as JPEG encoding -support. - -The Android camera HAL implementation will initially target the -LIMITED hardware level, with support for the FULL level then being gradually -implemented. diff --git a/Documentation/documentation-contents.rst b/Documentation/documentation-contents.rst new file mode 100644 index 00000000..5c111849 --- /dev/null +++ b/Documentation/documentation-contents.rst @@ -0,0 +1,35 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. container:: documentation-nav + + * **Documentation for Users** + * :doc:`Introduction </introduction>` + * :doc:`/feature_requirements` + * :doc:`/guides/application-developer` + * :doc:`/python-bindings` + * :doc:`/environment_variables` + * :doc:`/api-html/index` + * :doc:`/code-of-conduct` + * | + * **Documentation for Developers** + * :doc:`/libcamera_architecture` + * :doc:`/guides/pipeline-handler` + * :doc:`/guides/ipa` + * :doc:`/camera-sensor-model` + * :doc:`/guides/tracing` + * :doc:`/software-isp-benchmarking` + * :doc:`/coding-style` + * :doc:`/internal-api-html/index` + * | + * **Documentation for System Integrators** + * :doc:`/lens_driver_requirements` + * :doc:`/sensor_driver_requirements` + +.. + The following directive adds the "documentation" class to all of the pages + generated by sphinx. This is not relevant in libcamera nor addressed in the + theme's CSS, since all of the pages here are documentation. It **is** used + to properly format the documentation pages on libcamera.org and so should not + be removed. + +.. rst-class:: documentation diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst index a9b230bc..6f123558 100644 --- a/Documentation/environment_variables.rst +++ b/Documentation/environment_variables.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + Environment variables ===================== @@ -37,11 +39,29 @@ LIBCAMERA_IPA_MODULE_PATH Example value: ``${HOME}/.libcamera/lib:/opt/libcamera/vendor/lib`` +LIBCAMERA_IPA_PROXY_PATH + Define custom full path for a proxy worker for a given executable name. + + Example value: ``${HOME}/.libcamera/proxy/worker:/opt/libcamera/vendor/proxy/worker`` + +LIBCAMERA_PIPELINES_MATCH_LIST + Define an ordered list of pipeline names to be used to match the media + devices in the system. The pipeline handler names used to populate the + variable are the ones passed to the REGISTER_PIPELINE_HANDLER() macro in the + source code. + + Example value: ``rkisp1,simple`` + LIBCAMERA_RPI_CONFIG_FILE Define a custom configuration file to use in the Raspberry Pi pipeline handler. Example value: ``/usr/local/share/libcamera/pipeline/rpi/vc4/minimal_mem.yaml`` +LIBCAMERA_<NAME>_TUNING_FILE + Define a custom IPA tuning file to use with the pipeline handler `NAME`. + + Example value: ``/usr/local/share/libcamera/ipa/rpi/vc4/custom_sensor.json`` + Further details --------------- diff --git a/Documentation/feature_requirements.rst b/Documentation/feature_requirements.rst new file mode 100644 index 00000000..e6b74a62 --- /dev/null +++ b/Documentation/feature_requirements.rst @@ -0,0 +1,150 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. include:: documentation-contents.rst + +Feature Requirements +==================== + +Device enumeration +------------------ + +The library shall support enumerating all camera devices available in the +system, including both fixed cameras and hotpluggable cameras. It shall +support cameras plugged and unplugged after the initialization of the +library, and shall offer a mechanism to notify applications of camera plug +and unplug. + +The following types of cameras shall be supported: + +* Internal cameras designed for point-and-shoot still image and video + capture usage, either controlled directly by the CPU, or exposed through + an internal USB bus as a UVC device. + +* External UVC cameras designed for video conferencing usage. + +Other types of camera, including analog cameras, depth cameras, thermal +cameras, external digital picture or movie cameras, are out of scope for +this project. + +A hardware device that includes independent camera sensors, such as front +and back sensors in a phone, shall be considered as multiple camera devices +for the purpose of this library. + +Independent Camera Devices +-------------------------- + +When multiple cameras are present in the system and are able to operate +independently from each other, the library shall expose them as multiple +camera devices and support parallel operation without any additional usage +restriction apart from the limitations inherent to the hardware (such as +memory bandwidth, CPU usage or number of CSI-2 receivers for instance). + +Independent processes shall be able to use independent cameras devices +without interfering with each other. A single camera device shall be +usable by a single process at a time. + +Multiple streams support +------------------------ + +The library shall support multiple video streams running in parallel +for each camera device, within the limits imposed by the system. + +Per frame controls +------------------ + +The library shall support controlling capture parameters for each stream +on a per-frame basis, on a best effort basis based on the capabilities of the +hardware and underlying software stack (including kernel drivers and +firmware). It shall apply capture parameters to the frame they target, and +report the value of the parameters that have effectively been used for each +captured frame. + +When a camera device supports multiple streams, the library shall allow both +control of each stream independently, and control of multiple streams +together. Streams that are controlled together shall be synchronized. No +synchronization is required for streams controlled independently. + +Capability Enumeration +---------------------- + +The library shall expose capabilities of each camera device in a way that +allows applications to discover those capabilities dynamically. Applications +shall be allowed to cache capabilities for as long as they are using the +library. If capabilities can change at runtime, the library shall offer a +mechanism to notify applications of such changes. Applications shall not +cache capabilities in long term storage between runs. + +Capabilities shall be discovered dynamically at runtime from the device when +possible, and may come, in part or in full, from platform configuration +data. + +Device Profiles +--------------- + +The library may define different camera device profiles, each with a minimum +set of required capabilities. Applications may use those profiles to quickly +determine the level of features exposed by a device without parsing the full +list of capabilities. Camera devices may implement additional capabilities +on top of the minimum required set for the profile they expose. + +3A and Image Enhancement Algorithms +----------------------------------- + +The library shall provide a basic implementation of Image Processing Algorithms +to serve as a reference for the internal API. This shall including auto exposure +and gain and auto white balance. Camera devices that include a focus lens shall +implement auto focus. Additional image enhancement algorithms, such as noise +reduction or video stabilization, may be implemented. Device vendors are +expected to provide a fully-fledged implementation compatible with their +Pipeline Handler. One goal of the libcamera project is to create an environment +in which the community will be able to compete with the closed-source vendor +biaries and develop a high quality open source implementation. + +All algorithms may be implemented in hardware or firmware outside of the +library, or in software in the library. They shall all be controllable by +applications. + +The library shall be architectured to isolate the 3A and image enhancement +algorithms in a component with a documented API, respectively called the 3A +component and the 3A API. The 3A API shall be stable, and shall allow both +open-source and closed-source implementations of the 3A component. + +The library may include statically-linked open-source 3A components, and +shall support dynamically-linked open-source and closed-source 3A +components. + +Closed-source 3A Component Sandboxing +------------------------------------- + +For security purposes, it may be desired to run closed-source 3A components +in a separate process. The 3A API would in such a case be transported over +IPC. The 3A API shall make it possible to use any IPC mechanism that +supports passing file descriptors. + +The library may implement an IPC mechanism, and shall support third-party +platform-specific IPC mechanisms through the implementation of a +platform-specific 3A API wrapper. No modification to the library shall be +needed to use such third-party IPC mechanisms. + +The 3A component shall not directly access any device node on the system. +Such accesses shall instead be performed through the 3A API. The library +shall validate all accesses and restrict them to what is absolutely required +by 3A components. + +V4L2 Compatibility Layer +------------------------ + +The project shall support traditional V4L2 application through an additional +libcamera wrapper library. The wrapper library shall trap all accesses to +camera devices through `LD_PRELOAD`, and route them through libcamera to +emulate a high-level V4L2 camera device. It shall expose camera device +features on a best-effort basis, and aim for the level of features +traditionally available from a UVC camera designed for video conferencing. + +Android Camera HAL v3 Compatibility +----------------------------------- + +The library API shall expose all the features required to implement an +Android Camera HAL v3 on top of libcamera. Some features of the HAL may be +omitted as long as they can be implemented separately in the HAL, such as +JPEG encoding, or YUV reprocessing. diff --git a/Documentation/gen-doxyfile.py b/Documentation/gen-doxyfile.py new file mode 100755 index 00000000..c265bc2f --- /dev/null +++ b/Documentation/gen-doxyfile.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2024, Google Inc. +# +# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# +# Generate Doxyfile from a template + +import argparse +import os +import string +import sys + + +def fill_template(template, data): + + template = open(template, 'rb').read() + template = template.decode('utf-8') + template = string.Template(template) + + return template.substitute(data) + + +def main(argv): + + parser = argparse.ArgumentParser() + parser.add_argument('-o', dest='output', metavar='file', + type=argparse.FileType('w', encoding='utf-8'), + default=sys.stdout, + help='Output file name (default: standard output)') + parser.add_argument('template', metavar='doxyfile.tmpl', type=str, + help='Doxyfile template') + parser.add_argument('inputs', type=str, nargs='*', + help='Input files') + + args = parser.parse_args(argv[1:]) + + inputs = [f'"{os.path.realpath(input)}"' for input in args.inputs] + data = fill_template(args.template, {'inputs': (' \\\n' + ' ' * 25).join(inputs)}) + args.output.write(data) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/Documentation/getting-started.rst b/Documentation/getting-started.rst index 987f43f7..63b050eb 100644 --- a/Documentation/getting-started.rst +++ b/Documentation/getting-started.rst @@ -1,4 +1,5 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 + .. Getting started information is defined in the project README file. .. include:: ../README.rst :start-after: .. section-begin-getting-started diff --git a/Documentation/guides/application-developer.rst b/Documentation/guides/application-developer.rst index c46d3362..6501345a 100644 --- a/Documentation/guides/application-developer.rst +++ b/Documentation/guides/application-developer.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: ../documentation-contents.rst + Using libcamera in a C++ application ==================================== @@ -116,19 +118,21 @@ available. .. code:: cpp - if (cm->cameras().empty()) { + auto cameras = cm->cameras(); + if (cameras.empty()) { std::cout << "No cameras were identified on the system." << std::endl; cm->stop(); return EXIT_FAILURE; } - std::string cameraId = cm->cameras()[0]->id(); - camera = cm->get(cameraId); + std::string cameraId = cameras[0]->id(); + camera = cm->get(cameraId); /* - * Note that is equivalent to: - * camera = cm->cameras()[0]; + * Note that `camera` may not compare equal to `cameras[0]`. + * In fact, it might simply be a `nullptr`, as the particular + * device might have disappeared (and reappeared) in the meantime. */ Once a camera has been selected an application needs to acquire an exclusive @@ -348,7 +352,7 @@ The libcamera library uses the concept of `signals and slots` (similar to `Qt Signals and Slots`_) to connect events with callbacks to handle them. .. _signals and slots: https://libcamera.org/api-html/classlibcamera_1_1Signal.html#details -.. _Qt Signals and Slots: https://doc.qt.io/qt-5/signalsandslots.html +.. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html The ``Camera`` device emits two signals that applications can connect to in order to execute callbacks on frame completion events. @@ -479,7 +483,7 @@ instance. An example of how to write image data to disk is available in the `FileSink class`_ which is a part of the ``cam`` utility application in the libcamera repository. -.. _FileSink class: https://git.libcamera.org/libcamera/libcamera.git/tree/src/cam/file_sink.cpp +.. _FileSink class: https://git.libcamera.org/libcamera/libcamera.git/tree/src/apps/cam/file_sink.cpp With the handling of this request completed, it is possible to re-use the request and the associated buffers and re-queue it to the camera @@ -614,7 +618,7 @@ accordingly. In this example, the application file has been named simple_cam = executable('simple-cam', 'simple-cam.cpp', - dependencies: dependency('libcamera', required : true)) + dependencies: dependency('libcamera')) The ``dependencies`` line instructs meson to ask ``pkgconfig`` (or ``cmake``) to locate the ``libcamera`` library, which the test application will be diff --git a/Documentation/guides/introduction.rst b/Documentation/guides/introduction.rst deleted file mode 100644 index 700ec2d3..00000000 --- a/Documentation/guides/introduction.rst +++ /dev/null @@ -1,319 +0,0 @@ -.. SPDX-License-Identifier: CC-BY-SA-4.0 - -Developers guide to libcamera -============================= - -The Linux kernel handles multimedia devices through the 'Linux media' subsystem -and provides a set of APIs (application programming interfaces) known -collectively as V4L2 (`Video for Linux 2`_) and the `Media Controller`_ API -which provide an interface to interact and control media devices. - -Included in this subsystem are drivers for camera sensors, CSI2 (Camera -Serial Interface) receivers, and ISPs (Image Signal Processors) - -The usage of these drivers to provide a functioning camera stack is a -responsibility that lies in userspace which is commonly implemented separately -by vendors without a common architecture or API for application developers. - -libcamera provides a complete camera stack for Linux based systems to abstract -functionality desired by camera application developers and process the -configuration of hardware and image control algorithms required to obtain -desirable results from the camera. - -.. _Video for Linux 2: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/v4l2.html -.. _Media Controller: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/mediactl/media-controller.html - - -In this developers guide, we will explore the `Camera Stack`_ and how it is -can be visualised at a high level, and explore the internal `Architecture`_ of -the libcamera library with its components. The current `Platform Support`_ is -detailed, as well as an overview of the `Licensing`_ requirements of the -project. - -This introduction is followed by a walkthrough tutorial to newcomers wishing to -support a new platform with the `Pipeline Handler Writers Guide`_ and for those -looking to make use of the libcamera native API an `Application Writers Guide`_ -provides a tutorial of the key APIs exposed by libcamera. - -.. _Pipeline Handler Writers Guide: pipeline-handler.html -.. _Application Writers Guide: application-developer.html - -.. TODO: Correctly link to the other articles of the guide - -Camera Stack ------------- - -The libcamera library is implemented in userspace, and makes use of underlying -kernel drivers that directly interact with hardware. - -Applications can make use of libcamera through the native `libcamera API`_'s or -through an adaptation layer integrating libcamera into a larger framework. - -.. _libcamera API: https://www.libcamera.org/api-html/index.html - -:: - - Application Layer - / +--------------+ +--------------+ +--------------+ +--------------+ - | | Native | | Framework | | Native | | Android | - | | V4L2 | | Application | | libcamera | | Camera | - | | Application | | (gstreamer) | | Application | | Framework | - \ +--------------+ +--------------+ +--------------+ +--------------+ - - ^ ^ ^ ^ - | | | | - | | | | - v v | v - Adaptation Layer | - / +--------------+ +--------------+ | +--------------+ - | | V4L2 | | gstreamer | | | Android | - | | Compatibility| | element | | | Camera | - | | (preload) | |(libcamerasrc)| | | HAL | - \ +--------------+ +--------------+ | +--------------+ - | - ^ ^ | ^ - | | | | - | | | | - v v v v - libcamera Framework - / +--------------------------------------------------------------------+ - | | | - | | libcamera | - | | | - \ +--------------------------------------------------------------------+ - - ^ ^ ^ - Userspace | | | - --------------------- | ---------------- | ---------------- | --------------- - Kernel | | | - v v v - - +-----------+ +-----------+ +-----------+ - | Media | <--> | Video | <--> | V4L2 | - | Device | | Device | | Subdev | - +-----------+ +-----------+ +-----------+ - -The camera stack comprises of four software layers. From bottom to top: - -* The kernel drivers control the camera hardware and expose a low-level - interface to userspace through the Linux kernel V4L2 family of APIs - (Media Controller API, V4L2 Video Device API and V4L2 Subdev API). - -* The libcamera framework is the core part of the stack. It handles all control - of the camera devices in its core component, libcamera, and exposes a native - C++ API to upper layers. - -* The libcamera adaptation layer is an umbrella term designating the components - that interface to libcamera in other frameworks. Notable examples are the V4L2 - compatibility layer, the gstreamer libcamera element, and the Android camera - HAL implementation based on libcamera which are provided as a part of the - libcamera project. - -* The applications and upper level frameworks are based on the libcamera - framework or libcamera adaptation, and are outside of the scope of the - libcamera project, however example native applications (cam, qcam) are - provided for testing. - - -V4L2 Compatibility Layer - V4L2 compatibility is achieved through a shared library that traps all - accesses to camera devices and routes them to libcamera to emulate high-level - V4L2 camera devices. It is injected in a process address space through - ``LD_PRELOAD`` and is completely transparent for applications. - - The compatibility layer exposes camera device features on a best-effort basis, - and aims for the level of features traditionally available from a UVC camera - designed for video conferencing. - -Android Camera HAL - Camera support for Android is achieved through a generic Android camera HAL - implementation on top of libcamera. The HAL implements features required by - Android and out of scope from libcamera, such as JPEG encoding support. - - This component is used to provide support for ChromeOS platforms - -GStreamer element (gstlibcamerasrc) - A `GStreamer element`_ is provided to allow capture from libcamera supported - devices through GStreamer pipelines, and connect to other elements for further - processing. - - Development of this element is ongoing and is limited to a single stream. - -Native libcamera API - Applications can make use of the libcamera API directly using the C++ - API. An example application and walkthrough using the libcamera API can be - followed in the `Application Writers Guide`_ - -.. _GStreamer element: https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html - -Architecture ------------- - -While offering a unified API towards upper layers, and presenting itself as a -single library, libcamera isn't monolithic. It exposes multiple components -through its public API and is built around a set of separate helpers internally. -Hardware abstractions are handled through the use of device-specific components -where required and dynamically loadable plugins are used to separate image -processing algorithms from the core libcamera codebase. - -:: - - --------------------------< libcamera Public API >--------------------------- - ^ ^ - | | - v v - +-------------+ +---------------------------------------------------+ - | Camera | | Camera Device | - | Manager | | +-----------------------------------------------+ | - +-------------+ | | Device-Agnostic | | - ^ | | | | - | | | +--------------------------+ | - | | | | ~~~~~~~~~~~~~~~~~~~~~~~ | - | | | | { +-----------------+ } | - | | | | } | //// Image //// | { | - | | | | <-> | / Processing // | } | - | | | | } | / Algorithms // | { | - | | | | { +-----------------+ } | - | | | | ~~~~~~~~~~~~~~~~~~~~~~~ | - | | | | ========================== | - | | | | +-----------------+ | - | | | | | // Pipeline /// | | - | | | | <-> | /// Handler /// | | - | | | | | /////////////// | | - | | +--------------------+ +-----------------+ | - | | Device-Specific | - | +---------------------------------------------------+ - | ^ ^ - | | | - v v v - +--------------------------------------------------------------------+ - | Helpers and Support Classes | - | +-------------+ +-------------+ +-------------+ +-------------+ | - | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | | - | | Support | | Allocator | | IPC | | Manager | | - | +-------------+ +-------------+ +-------------+ +-------------+ | - | +-------------+ +-------------+ | - | | Pipeline | | ... | | - | | Runner | | | | - | +-------------+ +-------------+ | - +--------------------------------------------------------------------+ - - /// Device-Specific Components - ~~~ Sandboxing - - -Camera Manager - The Camera Manager enumerates cameras and instantiates Pipeline Handlers to - manage each Camera that libcamera supports. The Camera Manager supports - hotplug detection and notification events when supported by the underlying - kernel devices. - - There is only ever one instance of the Camera Manager running per application. - Each application's instance of the Camera Manager ensures that only a single - application can take control of a camera device at once. - - Read the `Camera Manager API`_ documentation for more details. - -.. _Camera Manager API: https://libcamera.org/api-html/classlibcamera_1_1CameraManager.html - -Camera Device - The Camera class represents a single item of camera hardware that is capable - of producing one or more image streams, and provides the API to interact with - the underlying device. - - If a system has multiple instances of the same hardware attached, each has its - own instance of the camera class. - - The API exposes full control of the device to upper layers of libcamera through - the public API, making it the highest level object libcamera exposes, and the - object that all other API operations interact with from configuration to - capture. - - Read the `Camera API`_ documentation for more details. - -.. _Camera API: https://libcamera.org/api-html/classlibcamera_1_1Camera.html - -Pipeline Handler - The Pipeline Handler manages the complex pipelines exposed by the kernel - drivers through the Media Controller and V4L2 APIs. It abstracts pipeline - handling to hide device-specific details from the rest of the library, and - implements both pipeline configuration based on stream configuration, and - pipeline runtime execution and scheduling when needed by the device. - - The Pipeline Handler lives in the same process as the rest of the library, and - has access to all helpers and kernel camera-related devices. - - Hardware abstraction is handled by device specific Pipeline Handlers which are - derived from the Pipeline Handler base class allowing commonality to be shared - among the implementations. - - Derived pipeline handlers create Camera device instances based on the devices - they detect and support on the running system, and are responsible for - managing the interactions with a camera device. - - More details can be found in the `PipelineHandler API`_ documentation, and the - `Pipeline Handler Writers Guide`_. - -.. _PipelineHandler API: https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html - -Image Processing Algorithms - An image processing algorithm (IPA) component is a loadable plugin that - implements 3A (Auto-Exposure, Auto-White Balance, and Auto-Focus) and other - algorithms. - - The algorithms run on the CPU and interact with the camera devices through the - Pipeline Handler to control hardware image processing based on the parameters - supplied by upper layers, maintaining state and closing the control loop - of the ISP. - - The component is sandboxed and can only interact with libcamera through the - API provided by the Pipeline Handler and an IPA has no direct access to kernel - camera devices. - - Open source IPA modules built with libcamera can be run in the same process - space as libcamera, however external IPA modules are run in a separate process - from the main libcamera process. IPA modules have a restricted view of the - system, including no access to networking APIs and limited access to file - systems. - - IPA modules are only required for platforms and devices with an ISP controlled - by the host CPU. Camera sensors which have an integrated ISP are not - controlled through the IPA module. - -Platform Support ----------------- - -The library currently supports the following hardware platforms specifically -with dedicated pipeline handlers: - - - Intel IPU3 (ipu3) - - Rockchip RK3399 (rkisp1) - - RaspberryPi 3 and 4 (rpi/vc4) - -Furthermore, generic platform support is provided for the following: - - - USB video device class cameras (uvcvideo) - - iMX7, Allwinner Sun6i (simple) - - Virtual media controller driver for test use cases (vimc) - -Licensing ---------- - -The libcamera core, is covered by the `LGPL-2.1-or-later`_ license. Pipeline -Handlers are a part of the libcamera code base and need to be contributed -upstream by device vendors. IPA modules included in libcamera are covered by a -free software license, however third-parties may develop IPA modules outside of -libcamera and distribute them under a closed-source license, provided they do -not include source code from the libcamera project. - -The libcamera project itself contains multiple libraries, applications and -utilities. Licenses are expressed through SPDX tags in text-based files that -support comments, and through the .reuse/dep5 file otherwise. A copy of all -licenses are stored in the LICENSES directory, and a full summary of the -licensing used throughout the project can be found in the COPYING.rst document. - -Applications which link dynamically against libcamera and use only the public -API are an independent work of the authors and have no license restrictions -imposed upon them from libcamera. - -.. _LGPL-2.1-or-later: https://spdx.org/licenses/LGPL-2.1-or-later.html diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst index 25deadef..cd640563 100644 --- a/Documentation/guides/ipa.rst +++ b/Documentation/guides/ipa.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: ../documentation-contents.rst + IPA Writer's Guide ================== diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst index 10b9c75c..fe752975 100644 --- a/Documentation/guides/pipeline-handler.rst +++ b/Documentation/guides/pipeline-handler.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: ../documentation-contents.rst + Pipeline Handler Writers Guide ============================== @@ -151,13 +153,14 @@ integrates with the libcamera build system, and a *vivid.cpp* file that matches the name of the pipeline. In the *meson.build* file, add the *vivid.cpp* file as a build source for -libcamera by adding it to the global meson ``libcamera_sources`` variable: +libcamera by adding it to the global meson ``libcamera_internal_sources`` +variable: .. code-block:: none # SPDX-License-Identifier: CC0-1.0 - libcamera_sources += files([ + libcamera_internal_sources += files([ 'vivid.cpp', ]) @@ -183,7 +186,7 @@ to the libcamera build options in the top level ``meson_options.txt``. option('pipelines', type : 'array', - choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'uvcvideo', 'vimc', 'vivid'], + choices : ['ipu3', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple', 'uvcvideo', 'vimc', 'vivid'], description : 'Select which pipeline handlers to include') @@ -258,7 +261,7 @@ implementations for the overridden class members. return false; } - REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid) + REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid, "vivid") } /* namespace libcamera */ @@ -266,6 +269,8 @@ Note that you must register the ``PipelineHandler`` subclass with the pipeline handler factory using the `REGISTER_PIPELINE_HANDLER`_ macro which registers it and creates a global symbol to reference the class and make it available to try and match devices. +String "vivid" is the name assigned to the pipeline, matching the pipeline +subdirectory name in the source tree. .. _REGISTER_PIPELINE_HANDLER: https://libcamera.org/api-html/pipeline__handler_8h.html @@ -516,14 +521,14 @@ handler and camera manager using `registerCamera`_. Finally with a successful construction, we return 'true' indicating that the PipelineHandler successfully matched and constructed a device. -.. _Camera::create: https://libcamera.org/api-html/classlibcamera_1_1Camera.html#a453740e0d2a2f495048ae307a85a2574 +.. _Camera::create: https://libcamera.org/internal-api-html/classlibcamera_1_1Camera.html#adf5e6c22411f953bfaa1ae21155d6c31 .. _registerCamera: https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html#adf02a7f1bbd87aca73c0e8d8e0e6c98b .. code-block:: cpp std::set<Stream *> streams{ &data->stream_ }; - std::shared_ptr<Camera> camera = Camera::create(this, data->video_->deviceName(), streams); - registerCamera(std::move(camera), std::move(data)); + std::shared_ptr<Camera> camera = Camera::create(std::move(data), data->video_->deviceName(), streams); + registerCamera(std::move(camera)); return true; @@ -549,8 +554,7 @@ Our match function should now look like the following: /* Create and register the camera. */ std::set<Stream *> streams{ &data->stream_ }; - const std::string &id = data->video_->deviceName(); - std::shared_ptr<Camera> camera = Camera::create(data.release(), id, streams); + std::shared_ptr<Camera> camera = Camera::create(std::move(data), data->video_->deviceName(), streams); registerCamera(std::move(camera)); return true; @@ -587,12 +591,12 @@ immutable properties of the ``Camera`` device. The libcamera controls and properties are defined in YAML form which is processed to automatically generate documentation and interfaces. Controls are -defined by the src/libcamera/`control_ids.yaml`_ file and camera properties -are defined by src/libcamera/`properties_ids.yaml`_. +defined by the src/libcamera/`control_ids_core.yaml`_ file and camera properties +are defined by src/libcamera/`property_ids_core.yaml`_. .. _controls framework: https://libcamera.org/api-html/controls_8h.html -.. _control_ids.yaml: https://libcamera.org/api-html/control__ids_8h.html -.. _properties_ids.yaml: https://libcamera.org/api-html/property__ids_8h.html +.. _control_ids_core.yaml: https://libcamera.org/api-html/control__ids_8h.html +.. _property_ids_core.yaml: https://libcamera.org/api-html/property__ids_8h.html Pipeline handlers can optionally register the list of controls an application can set as well as a list of immutable camera properties. Being both @@ -651,7 +655,7 @@ inline in our VividCameraData init: ctrls.emplace(id, info); } - controlInfo_ = std::move(ctrls); + controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); The ``properties_`` field is a list of ``ControlId`` instances associated with immutable values, which represent static characteristics that can @@ -672,6 +676,58 @@ handling controls: #include <libcamera/controls.h> #include <libcamera/control_ids.h> +Vendor-specific controls and properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Vendor-specific controls and properties must be defined in a separate YAML file +and included in the build by defining the pipeline handler to file mapping in +include/libcamera/meson.build. These YAML files live in the src/libcamera +directory. + +For example, adding a Raspberry Pi vendor control file for the PiSP pipeline +handler is done with the following mapping: + +.. code-block:: meson + + controls_map = { + 'controls': { + 'draft': 'control_ids_draft.yaml', + 'libcamera': 'control_ids_core.yaml', + 'rpi/pisp': 'control_ids_rpi.yaml', + }, + + 'properties': { + 'draft': 'property_ids_draft.yaml', + 'libcamera': 'property_ids_core.yaml', + } + } + +The pipeline handler named above must match the pipeline handler option string +specified in the meson build configuration. + +Vendor-specific controls and properties must contain a `vendor: <vendor_string>` +tag in the YAML file. Every unique vendor tag must define a unique and +non-overlapping range of reserved control IDs in src/libcamera/control_ranges.yaml. + +For example, the following block defines a vendor-specific control with the +`rpi` vendor tag: + +.. code-block:: yaml + + vendor: rpi + controls: + - PispConfigDumpFile: + type: string + description: | + Triggers the Raspberry Pi PiSP pipeline handler to generate a JSON + formatted dump of the Backend configuration to the filename given by the + value of the control. + +The controls will be generated in the vendor-specific namespace +`libcamera::controls::rpi`. Additionally a `#define +LIBCAMERA_HAS_RPI_VENDOR_CONTROLS` will be available to allow applications to +test for the availability of these controls. + Generating a default configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -743,8 +799,7 @@ derived class, and assign it to a base class pointer. .. code-block:: cpp - VividCameraData *data = cameraData(camera); - CameraConfiguration *config = new VividCameraConfiguration(); + auto config = std::make_unique<VividCameraConfiguration>(); A ``CameraConfiguration`` is specific to each pipeline, so you can only create it from the pipeline handler code path. Applications can also generate an empty @@ -772,9 +827,7 @@ To generate a ``StreamConfiguration``, you need a list of pixel formats and frame sizes which are supported as outputs of the stream. You can fetch a map of the ``V4LPixelFormat`` and ``SizeRange`` supported by the underlying output device, but the pipeline handler needs to convert this to a -``libcamera::PixelFormat`` type to pass to applications. We do this here using -``std::transform`` to convert the formats and populate a new ``PixelFormat`` map -as shown below. +``libcamera::PixelFormat`` type to pass to applications. Continue adding the following code example to our ``generateConfiguration`` implementation. @@ -784,14 +837,12 @@ implementation. std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats = data->video_->formats(); std::map<PixelFormat, std::vector<SizeRange>> deviceFormats; - std::transform(v4l2Formats.begin(), v4l2Formats.end(), - std::inserter(deviceFormats, deviceFormats.begin()), - [&](const decltype(v4l2Formats)::value_type &format) { - return decltype(deviceFormats)::value_type{ - format.first.toPixelFormat(), - format.second - }; - }); + + for (auto &[v4l2PixelFormat, sizes] : v4l2Formats) { + PixelFormat pixelFormat = v4l2PixelFormat.toPixelFormat(); + if (pixelFormat.isValid()) + deviceFormats.try_emplace(pixelFormat, std::move(sizes)); + } The `StreamFormats`_ class holds information about the pixel formats and frame sizes that a stream can support. The class groups size information by the pixel @@ -881,9 +932,9 @@ Add the following function implementation to your file: StreamConfiguration &cfg = config_[0]; - const std::vector<libcamera::PixelFormat> formats = cfg.formats().pixelformats(); + const std::vector<libcamera::PixelFormat> &formats = cfg.formats().pixelformats(); if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) == formats.end()) { - cfg.pixelFormat = cfg.formats().pixelformats()[0]; + cfg.pixelFormat = formats[0]; LOG(VIVID, Debug) << "Adjusting format to " << cfg.pixelFormat.toString(); status = Adjusted; } @@ -1293,7 +1344,7 @@ before being set. continue; } - int32_t value = lroundf(it.second.get<float>() * 128 + offset); + int32_t value = std::lround(it.second.get<float>() * 128 + offset); controls.set(cid, std::clamp(value, 0, 255)); } @@ -1357,7 +1408,7 @@ value translation operations: .. code-block:: cpp - #include <math.h> + #include <cmath> Frame completion and event handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1370,7 +1421,7 @@ emitted triggers the execution of the connected slots. A detailed description of the libcamera implementation is available in the `libcamera Signal and Slot`_ classes documentation. -.. _Qt Signals and Slots: https://doc.qt.io/qt-5/signalsandslots.html +.. _Qt Signals and Slots: https://doc.qt.io/qt-6/signalsandslots.html .. _libcamera Signal and Slot: https://libcamera.org/api-html/classlibcamera_1_1Signal.html#details In order to notify applications about the availability of new frames and data, diff --git a/Documentation/guides/tracing.rst b/Documentation/guides/tracing.rst index ae960d85..537dce50 100644 --- a/Documentation/guides/tracing.rst +++ b/Documentation/guides/tracing.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: ../documentation-contents.rst + Tracing Guide ============= diff --git a/Documentation/index.rst b/Documentation/index.rst index 63fac72d..251112fb 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -1,26 +1,31 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 -.. Front page matter is defined in the project README file. -.. include:: ../README.rst - :start-after: .. section-begin-libcamera - :end-before: .. section-end-libcamera +.. include:: introduction.rst .. toctree:: :maxdepth: 1 :caption: Contents: Home <self> - Docs <docs> Contribute <contributing> Getting Started <getting-started> - Developer Guide <guides/introduction> Application Writer's Guide <guides/application-developer> - Pipeline Handler Writer's Guide <guides/pipeline-handler> - IPA Writer's guide <guides/ipa> - Tracing guide <guides/tracing> + Camera Sensor Model <camera-sensor-model> Environment variables <environment_variables> - Sensor driver requirements <sensor_driver_requirements> + Feature Requirements <feature_requirements> + IPA Writer's guide <guides/ipa> Lens driver requirements <lens_driver_requirements> + libcamera Architecture <libcamera_architecture> + Pipeline Handler Writer's Guide <guides/pipeline-handler> Python Bindings <python-bindings> - Camera Sensor Model <camera-sensor-model> + Sensor driver requirements <sensor_driver_requirements> + SoftwareISP Benchmarking <software-isp-benchmarking> + Tracing guide <guides/tracing> + + Design document: AE <design/ae> + +.. toctree:: + :hidden: + + introduction diff --git a/Documentation/internal-api-html/index.rst b/Documentation/internal-api-html/index.rst new file mode 100644 index 00000000..43768648 --- /dev/null +++ b/Documentation/internal-api-html/index.rst @@ -0,0 +1,8 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. _internal-api: + +Internal API Reference +====================== + +:: Placeholder for Doxygen documentation diff --git a/Documentation/introduction.rst b/Documentation/introduction.rst new file mode 100644 index 00000000..82aa11a3 --- /dev/null +++ b/Documentation/introduction.rst @@ -0,0 +1,224 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. include:: documentation-contents.rst + +************ +Introduction +************ + +.. toctree:: + :hidden: + + API <api-html/index> + Internal API <internal-api-html/index> + +What is libcamera? +================== + +libcamera is an open source complex camera support library for Linux, Android +and ChromeOS. The library interfaces with Linux kernel device drivers and +provides an intuitive API to developers in order to simplify the complexity +involved in capturing images from complex cameras on Linux systems. + +What is a "complex camera"? +=========================== + +A modern "camera" tends to infact be several different pieces of hardware which +must all be controlled together in order to produce and capture images of +appropriate quality. A hardware pipeline typically consists of a camera sensor +that captures raw frames and transmits them on a bus, a receiver that decodes +the bus signals, and an image signal processor that processes raw frames to +produce usable images in a standard format. The Linux kernel handles these +multimedia devices through the 'Linux media' subsystem and provides a set of +application programming interfaces known collectively as the +V4L2 (`Video for Linux 2`_) and the `Media Controller`_ APIs, which provide an +interface to interact and control media devices. + +.. _Video for Linux 2: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/v4l2.html +.. _Media Controller: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/mediactl/media-controller.html + +Included in this subsystem are drivers for camera sensors, CSI2 (Camera +Serial Interface) receivers, and ISPs (Image Signal Processors). + +The usage of these drivers to provide a functioning camera stack is a +responsibility that lies in userspace, and is commonly implemented separately +by vendors without a common architecture or API for application developers. This +adds a lot of complexity to the task, particularly when considering that the +differences in hardware pipelines and their representation in the kernel's APIs +often necessitate bespoke handling. + +What is libcamera for? +====================== + +libcamera provides a complete camera stack for Linux-based systems to abstract +the configuration of hardware and image control algorithms required to obtain +desirable results from the camera through the kernel's APIs, reducing those +operations to a simple and consistent method for developers. In short instead of +having to deal with this: + +.. graphviz:: mali-c55.dot + +you can instead simply deal with: + +.. code-block:: python + + >>> import libcamera as lc + >>> camera_manager = lc.CameraManager.singleton() + [0:15:59.582029920] [504] INFO Camera camera_manager.cpp:313 libcamera v0.3.0+182-01e57380 + >>> for camera in camera_manager.cameras: + ... print(f' - {camera.id}') + ... + - mali-c55 tpg + - imx415 1-001a + +The library handles the rest for you. These documentary pages give more +information on the internal workings of libcamera (and the kernel camera stack +that lies behind it) as well as guidance on using libcamera in an application or +extending the library with support for your hardware (through the pipeline +handler and IPA module writer's guides). + +How should I use it? +==================== + +There are a few ways you might want to use libcamera, depending on your +application. It's always possible to use the library directly, and you can find +detailed information on how to do so in the +:doc:`application writer's guide <guides/application-developer>`. + +It is often more appropriate to use one of the frameworks with libcamera +support. For example an application powering an embedded media device +incorporating capture, encoding and streaming of both video and audio would +benefit from using `GStreamer`_, for which libcamera provides a plugin. +Similarly an application for user-facing devices like a laptop would likely +benefit accessing cameras through the XDG camera portal and `pipewire`_, which +brings the advantages of resource sharing (multiple applications accessing the +stream at the same time) and access control. + +.. _GStreamer: https://gstreamer.freedesktop.org/ +.. _pipewire: https://pipewire.org/ + +Camera Stack +============ + +:: + + a c / +-------------+ +-------------+ +-------------+ +-------------+ + p a | | Native | | Framework | | Native | | Android | + p t | | V4L2 | | Application | | libcamera | | Camera | + l i | | Application | | (gstreamer) | | Application | | Framework | + i o \ +-------------+ +-------------+ +-------------+ +-------------+ + n ^ ^ ^ ^ + | | | | + l a | | | | + i d v v | v + b a / +-------------+ +-------------+ | +-------------+ + c p | | V4L2 | | Camera | | | Android | + a t | | Compat. | | Framework | | | Camera | + m a | | | | (gstreamer) | | | HAL | + e t \ +-------------+ +-------------+ | +-------------+ + r i ^ ^ | ^ + a o | | | | + n | | | | + / | ,................................................ + | | ! : Language : ! + l f | | ! : Bindings : ! + i r | | ! : (optional) : ! + b a | | \...............................................' + c m | | | | | + a e | | | | | + m w | v v v v + e o | +----------------------------------------------------------------+ + r r | | | + a k | | libcamera | + | | | + \ +----------------------------------------------------------------+ + ^ ^ ^ + Userspace | | | + ------------------------ | ---------------- | ---------------- | --------------- + Kernel | | | + v v v + +-----------+ +-----------+ +-----------+ + | Media | <--> | Video | <--> | V4L2 | + | Device | | Device | | Subdev | + +-----------+ +-----------+ +-----------+ + +The camera stack comprises four software layers. From bottom to top: + +* The kernel drivers control the camera hardware and expose a + low-level interface to userspace through the Linux kernel V4L2 + family of APIs (Media Controller API, V4L2 Video Device API and + V4L2 Subdev API). + +* The libcamera framework is the core part of the stack. It + handles all control of the camera devices in its core component, + libcamera, and exposes a native C++ API to upper layers. Optional + language bindings allow interfacing to libcamera from other + programming languages. + + Those components live in the same source code repository and + all together constitute the libcamera framework. + +* The libcamera adaptation is an umbrella term designating the + components that interface to libcamera in other frameworks. + Notable examples are a V4L2 compatibility layer, a gstreamer + libcamera element, and an Android camera HAL implementation based + on libcamera. + + Those components can live in the libcamera project source code + in separate repositories, or move to their respective project's + repository (for instance the gstreamer libcamera element). + +* The applications and upper level frameworks are based on the + libcamera framework or libcamera adaptation, and are outside of + the scope of the libcamera project. + +V4L2 Compatibility Layer + V4L2 compatibility is achieved through a shared library that traps all + accesses to camera devices and routes them to libcamera to emulate high-level + V4L2 camera devices. It is injected in a process address space through + ``LD_PRELOAD`` and is completely transparent for applications. + + The compatibility layer exposes camera device features on a best-effort basis, + and aims for the level of features traditionally available from a UVC camera + designed for video conferencing. + +Android Camera HAL + Camera support for Android is achieved through a generic Android camera HAL + implementation on top of libcamera. The HAL implements features required by + Android and out of scope from libcamera, such as JPEG encoding support. + + This component is used to provide support for ChromeOS platforms. + +GStreamer element (gstlibcamerasrc) + A `GStreamer element`_ is provided to allow capture from libcamera supported + devices through GStreamer pipelines, and connect to other elements for further + processing. + +Native libcamera API + Applications can make use of the libcamera API directly using the C++ + API. An example application and walkthrough using the libcamera API can be + followed in the :doc:`Application writer's guide </guides/application-developer>` + +.. _GStreamer element: https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html + +Licensing +========= + +The libcamera core is covered by the `LGPL-2.1-or-later`_ license. Pipeline +Handlers are a part of the libcamera code base and need to be contributed +upstream by device vendors. IPA modules included in libcamera are covered by a +free software license, however third-parties may develop IPA modules outside of +libcamera and distribute them under a closed-source license, provided they do +not include source code from the libcamera project. + +The libcamera project itself contains multiple libraries, applications and +utilities. Licenses are expressed through SPDX tags in text-based files that +support comments, and through the .reuse/dep5 file otherwise. A copy of all +licenses are stored in the LICENSES directory, and a full summary of the +licensing used throughout the project can be found in the COPYING.rst document. + +Applications which link dynamically against libcamera and use only the public +API are an independent work of the authors and have no license restrictions +imposed upon them from libcamera. + +.. _LGPL-2.1-or-later: https://spdx.org/licenses/LGPL-2.1-or-later.html diff --git a/Documentation/lens_driver_requirements.rst b/Documentation/lens_driver_requirements.rst index b96e502d..85fef76f 100644 --- a/Documentation/lens_driver_requirements.rst +++ b/Documentation/lens_driver_requirements.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + .. _lens-driver-requirements: Lens Driver Requirements diff --git a/Documentation/libcamera_architecture.rst b/Documentation/libcamera_architecture.rst new file mode 100644 index 00000000..abbb0d17 --- /dev/null +++ b/Documentation/libcamera_architecture.rst @@ -0,0 +1,168 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. include:: documentation-contents.rst + +libcamera Architecture +====================== + +While offering a unified API towards upper layers, and presenting itself as a +single library, libcamera isn't monolithic. It exposes multiple components +through its public API and is built around a set of separate helpers internally. +Hardware abstractions are handled through the use of device-specific components +where required and dynamically loadable plugins are used to separate image +processing algorithms from the core libcamera codebase. + +:: + + --------------------------< libcamera Public API >--------------------------- + ^ ^ + | | + v v + +-------------+ +---------------------------------------------------+ + | Camera | | Camera Device | + | Manager | | +-----------------------------------------------+ | + +-------------+ | | Device-Agnostic | | + ^ | | | | + | | | +--------------------------+ | + | | | | ~~~~~~~~~~~~~~~~~~~~~~~ | + | | | | { +-----------------+ } | + | | | | } | //// Image //// | { | + | | | | <-> | / Processing // | } | + | | | | } | / Algorithms // | { | + | | | | { +-----------------+ } | + | | | | ~~~~~~~~~~~~~~~~~~~~~~~ | + | | | | ========================== | + | | | | +-----------------+ | + | | | | | // Pipeline /// | | + | | | | <-> | /// Handler /// | | + | | | | | /////////////// | | + | | +--------------------+ +-----------------+ | + | | Device-Specific | + | +---------------------------------------------------+ + | ^ ^ + | | | + v v v + +--------------------------------------------------------------------+ + | Helpers and Support Classes | + | +-------------+ +-------------+ +-------------+ +-------------+ | + | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | | + | | Support | | Allocator | | IPC | | Manager | | + | +-------------+ +-------------+ +-------------+ +-------------+ | + | +-------------+ +-------------+ | + | | Pipeline | | ... | | + | | Runner | | | | + | +-------------+ +-------------+ | + +--------------------------------------------------------------------+ + + /// Device-Specific Components + ~~~ Sandboxing + + +Camera Manager + The Camera Manager enumerates cameras and instantiates Pipeline Handlers to + manage each Camera that libcamera supports. The Camera Manager supports + hotplug detection and notification events when supported by the underlying + kernel devices. + + There is only ever one instance of the Camera Manager running per application. + Each application's instance of the Camera Manager ensures that only a single + application can take control of a camera device at once. + + Read the `Camera Manager API`_ documentation for more details. + +.. _Camera Manager API: https://libcamera.org/api-html/classlibcamera_1_1CameraManager.html + +Camera Device + The Camera class represents a single item of camera hardware that is capable + of producing one or more image streams, and provides the API to interact with + the underlying device. + + If a system has multiple instances of the same hardware attached, each has its + own instance of the camera class. + + The API exposes full control of the device to upper layers of libcamera through + the public API, making it the highest level object libcamera exposes, and the + object that all other API operations interact with from configuration to + capture. + + Read the `Camera API`_ documentation for more details. + +.. _Camera API: https://libcamera.org/api-html/classlibcamera_1_1Camera.html + +Pipeline Handler + The Pipeline Handler manages the complex pipelines exposed by the kernel + drivers through the Media Controller and V4L2 APIs. It abstracts pipeline + handling to hide device-specific details from the rest of the library, and + implements both pipeline configuration based on stream configuration, and + pipeline runtime execution and scheduling when needed by the device. + + The Pipeline Handler lives in the same process as the rest of the library, and + has access to all helpers and kernel camera-related devices. + + Hardware abstraction is handled by device specific Pipeline Handlers which are + derived from the Pipeline Handler base class allowing commonality to be shared + among the implementations. + + Derived pipeline handlers create Camera device instances based on the devices + they detect and support on the running system, and are responsible for + managing the interactions with a camera device. + + More details can be found in the `PipelineHandler API`_ documentation, and the + :doc:`Pipeline Handler Writers Guide <guides/pipeline-handler>`. + +.. _PipelineHandler API: https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html + +Image Processing Algorithms + Together with the hardware image processing and hardware statistics + collection, the Image Processing Algorithms (IPA) implement 3A (Auto-Exposure, + Auto-White Balance and Auto-Focus) and other algorithms. They run on the CPU + and control hardware image processing based on the parameters supplied by + upper layers, closing the control loop of the ISP. + + IPAs are loaded as external plugins named IPA Modules. IPA Modules can be part + of the libcamera code base or provided externally by camera vendors as + open-source or closed-source components. + + Open source IPA Modules built with libcamera are run in the same process space + as libcamera. External IPA Modules are run in a separate sandboxed process. In + either case, they can only interact with libcamera through the API provided by + the Pipeline Handler. They have a restricted view of the system, with no direct + access to kernel camera devices, no access to networking APIs, and limited + access to file systems. All their accesses to image and metadata are mediated + by dmabuf instances explicitly passed by the Pipeline Handler to the IPA + Module. + + IPA Modules are only required for platforms and devices with an ISP controlled + by the host CPU. Camera sensors which have an integrated ISP are not + controlled through the IPA Module. + +Helpers and Support Classes + While Pipeline Handlers are device-specific, implementations are expected to + share code due to usage of identical APIs towards the kernel camera drivers + and the Image Processing Algorithms. This includes without limitation handling + of the MC and V4L2 APIs, buffer management through dmabuf, and pipeline + discovery, configuration and scheduling. Such code will be factored out to + helpers when applicable. + + Other parts of libcamera will also benefit from factoring code out to + self-contained support classes, even if such code is present only once in the + code base, in order to keep the source code clean and easy to read. This + should be the case for instance for plugin management. + +Platform Support +---------------- + +The library currently supports the following hardware platforms specifically +with dedicated pipeline handlers: + + - Arm Mali-C55 + - Intel IPU3 (ipu3) + - NXP i.MX8MP (imx8-isi and rkisp1) + - RaspberryPi 3, 4 and zero (rpi/vc4) + - Rockchip RK3399 (rkisp1) + +Furthermore, generic platform support is provided for the following: + + - USB video device class cameras (uvcvideo) + - iMX7, IPU6, Allwinner Sun6i (simple) + - Virtual media controller driver for test use cases (vimc) diff --git a/Documentation/mainpage.dox b/Documentation/mainpage.dox new file mode 100644 index 00000000..cbee9bab --- /dev/null +++ b/Documentation/mainpage.dox @@ -0,0 +1,33 @@ +/** +\mainpage libcamera API reference + +Welcome to the API reference for <a href="https://libcamera.org/">libcamera</a>, +a complex camera support library for Linux, Android and ChromeOS. These pages +are automatically generated from the libcamera source code and describe the API +in detail - if this is your first interaction with libcamera then you may find +it useful to visit the [documentation](../introduction.html) in +the first instance, which can provide a more generic introduction to the +library's concepts. + +\if internal + +As a follow-on to the developer's guide, to assist you in adding support for +your platform the [pipeline handler writer's guide](../guides/pipeline-handler.html) +and the [ipa module writer's guide](../guides/ipa.html) should be helpful. + +The full libcamera API is documented here. If you wish to see only the public +part of the API you can use [these pages](../api-html/index.html) instead. + +\else + +As a follow-on to the developer's guide, to assist you in using libcamera within +your project the [application developer's guide](../guides/application-developer.html) +gives an overview on how to achieve that. + +Only the public part of the libcamera API is documented here; if you are a +developer seeking to add support for your hardware to the library or make other +improvements, you should switch to the internal API +[reference pages](../internal-api-html/index.html) instead. + +\endif +*/ diff --git a/Documentation/mali-c55.dot b/Documentation/mali-c55.dot new file mode 100644 index 00000000..7bfc44c0 --- /dev/null +++ b/Documentation/mali-c55.dot @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: CC-BY-SA-4.0 */ + +digraph board { + rankdir=TB + n00000001 [label="{{} | mali-c55 tpg\n/dev/v4l-subdev0 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green] + n00000001:port0 -> n00000003:port0 [style=dashed] + n00000003 [label="{{<port0> 0 | <port4> 4} | mali-c55 isp\n/dev/v4l-subdev1 | {<port1> 1 | <port2> 2 | <port3> 3}}", shape=Mrecord, style=filled, fillcolor=green] + n00000003:port1 -> n00000009:port0 [style=bold] + n00000003:port2 -> n00000009:port2 [style=bold] + n00000003:port1 -> n0000000d:port0 [style=bold] + n00000003:port3 -> n0000001c + n00000009 [label="{{<port0> 0 | <port2> 2} | mali-c55 resizer fr\n/dev/v4l-subdev2 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000009:port1 -> n00000010 + n0000000d [label="{{<port0> 0} | mali-c55 resizer ds\n/dev/v4l-subdev3 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green] + n0000000d:port1 -> n00000014 + n00000010 [label="mali-c55 fr\n/dev/video0", shape=box, style=filled, fillcolor=yellow] + n00000014 [label="mali-c55 ds\n/dev/video1", shape=box, style=filled, fillcolor=yellow] + n00000018 [label="mali-c55 3a params\n/dev/video2", shape=box, style=filled, fillcolor=yellow] + n00000018 -> n00000003:port4 + n0000001c [label="mali-c55 3a stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow] + n00000030 [label="{{<port0> 0} | lte-csi2-rx\n/dev/v4l-subdev4 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green] + n00000030:port1 -> n00000003:port0 + n00000035 [label="{{} | imx415 1-001a\n/dev/v4l-subdev5 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green] + n00000035:port0 -> n00000030:port0 [style=bold] +} diff --git a/Documentation/meson.build b/Documentation/meson.build index 7c150259..0fc5909d 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -15,6 +15,7 @@ if doxygen.found() and dot.found() cdata.set('TOP_SRCDIR', meson.project_source_root()) cdata.set('TOP_BUILDDIR', meson.project_build_root()) cdata.set('OUTPUT_DIR', meson.current_build_dir()) + cdata.set('WARN_AS_ERROR', get_option('doc_werror') ? 'YES' : 'NO') doxygen_predefined = [] foreach key : config_h.keys() @@ -23,61 +24,124 @@ if doxygen.found() and dot.found() cdata.set('PREDEFINED', ' \\\n\t\t\t '.join(doxygen_predefined)) - doxyfile = configure_file(input : 'Doxyfile.in', - output : 'Doxyfile', - configuration : cdata) + doxyfile_common = configure_file(input : 'Doxyfile-common.in', + output : 'Doxyfile-common', + configuration : cdata) + + doxygen_public_input = [ + libcamera_base_public_headers, + libcamera_base_public_sources, + libcamera_public_headers, + libcamera_public_sources, + ] - doxygen_input = [ - doxyfile, - libcamera_base_headers, - libcamera_base_sources, + doxygen_internal_input = [ + libcamera_base_private_headers, + libcamera_base_internal_sources, libcamera_internal_headers, + libcamera_internal_sources, libcamera_ipa_headers, libcamera_ipa_interfaces, - libcamera_public_headers, - libcamera_sources, libipa_headers, libipa_sources, ] if is_variable('ipu3_ipa_sources') - doxygen_input += [ipu3_ipa_sources] + doxygen_internal_input += [ipu3_ipa_sources] endif - custom_target('doxygen', - input : doxygen_input, + # We run doxygen twice - the first run excludes internal API objects as it + # is intended to document the public API only. A second run covers all of + # the library's objects for libcamera developers. Common configuration is + # set in an initially generated Doxyfile, which is then included by the two + # final Doxyfiles. + + # This is the "public" run of doxygen generating an abridged version of the + # API's documentation. + + doxyfile_tmpl = configure_file(input : 'Doxyfile-public.in', + output : 'Doxyfile-public.tmpl', + configuration : cdata) + + # The set of public input files stored in the doxygen_public_input array + # needs to be set in Doxyfile public. We can't pass them through cdata + # cdata, as some of the array members are custom_tgt instances, which + # configuration_data.set() doesn't support. Using a separate script invoked + # through custom_target(), which supports custom_tgt instances as inputs. + + doxyfile = custom_target('doxyfile-public', + input : [ + doxygen_public_input, + ], + output : 'Doxyfile-public', + command : [ + 'gen-doxyfile.py', + '-o', '@OUTPUT@', + doxyfile_tmpl, + '@INPUT@', + ]) + + custom_target('doxygen-public', + input : [ + doxyfile, + doxyfile_common, + ], output : 'api-html', command : [doxygen, doxyfile], install : true, - install_dir : doc_install_dir) + install_dir : doc_install_dir, + install_tag : 'doc') + + # This is the internal documentation, which hard-codes a list of directories + # to parse in its doxyfile. + + doxyfile = configure_file(input : 'Doxyfile-internal.in', + output : 'Doxyfile-internal', + configuration : cdata) + + custom_target('doxygen-internal', + input : [ + doxyfile, + doxyfile_common, + doxygen_internal_input, + ], + output : 'internal-api-html', + command : [doxygen, doxyfile], + install : true, + install_dir : doc_install_dir, + install_tag : 'doc-internal') endif # # Sphinx # -sphinx = find_program('sphinx-build-3', required : false) -if not sphinx.found() - sphinx = find_program('sphinx-build', required : get_option('documentation')) -endif +sphinx = find_program('sphinx-build-3', 'sphinx-build', + required : get_option('documentation')) if sphinx.found() docs_sources = [ 'camera-sensor-model.rst', + 'code-of-conduct.rst', 'coding-style.rst', 'conf.py', 'contributing.rst', - 'docs.rst', + 'design/ae.rst', + 'documentation-contents.rst', 'environment_variables.rst', + 'feature_requirements.rst', 'guides/application-developer.rst', - 'guides/introduction.rst', 'guides/ipa.rst', 'guides/pipeline-handler.rst', 'guides/tracing.rst', 'index.rst', + 'introduction.rst', 'lens_driver_requirements.rst', + 'libcamera_architecture.rst', + 'mali-c55.dot', 'python-bindings.rst', 'sensor_driver_requirements.rst', + 'software-isp-benchmarking.rst', '../README.rst', ] @@ -90,7 +154,8 @@ if sphinx.found() output : 'html', build_by_default : true, install : true, - install_dir : doc_install_dir) + install_dir : doc_install_dir, + install_tag : 'doc') custom_target('documentation-linkcheck', command : [sphinx, '-W', '-b', 'linkcheck', meson.current_source_dir(), '@OUTPUT@'], diff --git a/Documentation/python-bindings.rst b/Documentation/python-bindings.rst index bac5cd34..94712238 100644 --- a/Documentation/python-bindings.rst +++ b/Documentation/python-bindings.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + .. _python-bindings: Python Bindings for libcamera @@ -17,13 +19,13 @@ 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. + 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. + 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 ------------ diff --git a/Documentation/sensor_driver_requirements.rst b/Documentation/sensor_driver_requirements.rst index 0e516b34..fb4269d0 100644 --- a/Documentation/sensor_driver_requirements.rst +++ b/Documentation/sensor_driver_requirements.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 +.. include:: documentation-contents.rst + .. _sensor-driver-requirements: Sensor Driver Requirements diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst new file mode 100644 index 00000000..9c2a409b --- /dev/null +++ b/Documentation/software-isp-benchmarking.rst @@ -0,0 +1,79 @@ +.. SPDX-License-Identifier: CC-BY-SA-4.0 + +.. include:: documentation-contents.rst + +.. _software-isp-benchmarking: + +Software ISP benchmarking +========================= + +The Software ISP is particularly sensitive to performance regressions therefore +it is a good idea to always benchmark the Software ISP before and after making +changes to it and ensure that there are no performance regressions. + +DebayerCpu class builtin benchmark +---------------------------------- + +The DebayerCpu class has a builtin benchmark. This benchmark measures the time +spent on processing (collecting statistics and debayering) only, it does not +measure the time spent on capturing or outputting the frames. + +The builtin benchmark always runs. So this can be used by simply running "cam" +or "qcam" with a pipeline using the Software ISP. + +When it runs it will skip measuring the first 30 frames to allow the caches and +the CPU temperature (turbo-ing) to warm-up and then it measures 30 fps and shows +the total and per frame processing time using an info level log message: + +.. code-block:: text + + INFO Debayer debayer_cpu.cpp:907 Processed 30 frames in 244317us, 8143 us/frame + +To get stable measurements it is advised to disable any other processes which +may cause significant CPU usage (e.g. disable wifi, bluetooth and browsers). +When possible it is also advisable to disable CPU turbo-ing and +frequency-scaling. + +For example when benchmarking on a Lenovo ThinkPad X1 Yoga Gen 8, with the +charger plugged in, the CPU can be fixed to run at 2 GHz using: + +.. code-block:: shell + + sudo x86_energy_perf_policy --turbo-enable 0 + sudo cpupower frequency-set -d 2GHz -u 2GHz + +with these settings the builtin bench reports a processing time of ~7.8ms/frame +on this laptop for FHD SGRBG10 (unpacked) bayer data. + +Measuring power consumption +--------------------------- + +Since the Software ISP is often used on mobile devices it is also important to +measure power consumption and ensure that that does not regress. + +For example to measure power consumption on a Lenovo ThinkPad X1 Yoga Gen 8 it +needs to be running on battery and it should be configured with its +platform-profile (/sys/firmware/acpi/platform_profile) set to balanced and with +its default turbo and frequency-scaling behavior to match real world usage. + +Then start qcam to capture a FHD picture at 30 fps and position the qcam window +so that it is fully visible. After this run the following command to monitor the +power consumption: + +.. code-block:: shell + + watch -n 10 cat /sys/class/power_supply/BAT0/power_now /sys/class/hwmon/hwmon6/fan?_input + +Note this not only measures the power consumption in µW it also monitors the +speed of this laptop's 2 fans. This is important because depending on the +ambient temperature the 2 fans may spin up while testing and this will cause an +additional power consumption of approx. 0.5 W messing up the measurement. + +After starting qcam + the watch command let the laptop sit without using it for +2 minutes for the readings to stabilize. Then check that the fans have not +turned on and manually take a couple of consecutive power readings and average +these. + +On the example Lenovo ThinkPad X1 Yoga Gen 8 laptop this results in a measured +power consumption of approx. 13 W while running qcam versus approx. 4-5 W while +setting idle with its OLED panel on. diff --git a/Documentation/theme/static/css/theme.css b/Documentation/theme/static/css/theme.css index d4274ea6..a6d43195 100644 --- a/Documentation/theme/static/css/theme.css +++ b/Documentation/theme/static/css/theme.css @@ -283,9 +283,13 @@ div#signature { font-size: 12px; } -#libcamera div.toctree-wrapper { +#licensing div.toctree-wrapper { height: 0px; margin: 0px; padding: 0px; visibility: hidden; } + +.documentation-nav { + display: none; +} diff --git a/Documentation/thread-safety.dox b/Documentation/thread-safety.dox new file mode 100644 index 00000000..df4c457c --- /dev/null +++ b/Documentation/thread-safety.dox @@ -0,0 +1,44 @@ +/** + * \page thread-safety Reentrancy and Thread-Safety + * + * Through the documentation, several terms are used to define how classes and + * their member functions can be used from multiple threads. + * + * - A **reentrant** function may be called simultaneously from multiple + * threads if and only if each invocation uses a different instance of the + * class. This is the default for all member functions not explictly marked + * otherwise. + * + * - \anchor thread-safe A **thread-safe** function may be called + * simultaneously from multiple threads on the same instance of a class. A + * thread-safe function is thus reentrant. Thread-safe functions may also be + * called simultaneously with any other reentrant function of the same class + * on the same instance. + * + * \internal + * - \anchor thread-bound A **thread-bound** function may be called only from + * the thread that the class instances lives in (see section \ref + * thread-objects). For instances of classes that do not derive from the + * Object class, this is the thread in which the instance was created. A + * thread-bound function is not thread-safe, and may or may not be reentrant. + * \endinternal + * + * Neither reentrancy nor thread-safety, in this context, mean that a function + * may be called simultaneously from the same thread, for instance from a + * callback invoked by the function. This may deadlock and isn't allowed unless + * separately documented. + * + * \if internal + * A class is defined as reentrant, thread-safe or thread-bound if all its + * member functions are reentrant, thread-safe or thread-bound respectively. + * \else + * A class is defined as reentrant or thread-safe if all its member functions + * are reentrant or thread-safe respectively. + * \endif + * Some member functions may additionally be documented as having additional + * thread-related attributes. + * + * Most classes are reentrant but not thread-safe, as making them fully + * thread-safe would incur locking costs considered prohibitive for the + * expected use cases. + */ @@ -1,7 +1,5 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 -.. section-begin-libcamera - =========== libcamera =========== @@ -22,7 +20,6 @@ open-source-friendly while still protecting vendor core IP. libcamera was born out of that collaboration and will offer modern camera support to Linux-based systems, including traditional Linux distributions, ChromeOS and Android. -.. section-end-libcamera .. section-begin-getting-started Getting Started @@ -30,7 +27,7 @@ Getting Started To fetch the sources, build and install: -:: +.. code:: git clone https://git.libcamera.org/libcamera/libcamera.git cd libcamera @@ -47,7 +44,7 @@ A C++ toolchain: [required] Either {g++, clang} Meson Build system: [required] - meson (>= 0.57) ninja-build pkg-config + meson (>= 0.63) ninja-build pkg-config for the libcamera core: [required] libyaml-dev python3-yaml python3-ply python3-jinja2 @@ -75,6 +72,9 @@ for documentation: [optional] for gstreamer: [optional] libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev +for Python bindings: [optional] + libpython3-dev pybind11-dev + for cam: [optional] libevent-dev is required to support cam, however the following optional dependencies bring more functionality to the cam test @@ -83,9 +83,10 @@ for cam: [optional] - libdrm-dev: Enables the KMS sink - libjpeg-dev: Enables MJPEG on the SDL sink - libsdl2-dev: Enables the SDL sink + - libtiff-dev: Enables writing DNG for qcam: [optional] - qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5 qttools5-dev-tools libtiff-dev + libtiff-dev qt6-base-dev for tracing with lttng: [optional] liblttng-ust-dev python3-jinja2 lttng-tools @@ -94,7 +95,7 @@ for android: [optional] libexif-dev libjpeg-dev for lc-compliance: [optional] - libevent-dev + libevent-dev libgtest-dev for abi-compat.sh: [optional] abi-compliance-checker @@ -117,10 +118,13 @@ setting the ``LIBCAMERA_LOG_LEVELS`` environment variable: Using GStreamer plugin ~~~~~~~~~~~~~~~~~~~~~~ -To use GStreamer plugin from source tree, set the following environment so that -GStreamer can find it. This isn't necessary when libcamera is installed. +To use the GStreamer plugin from the source tree, use the meson ``devenv`` +command. This will create a new shell instance with the ``GST_PLUGIN_PATH`` +environment set accordingly. + +.. code:: - export GST_PLUGIN_PATH=$(pwd)/build/src/gstreamer + meson devenv -C build The debugging tool ``gst-launch-1.0`` can be used to construct a pipeline and test it. The following pipeline will stream from the camera named "Camera 1" @@ -128,7 +132,7 @@ onto the OpenGL accelerated display element on your system. .. code:: - gst-launch-1.0 libcamerasrc camera-name="Camera 1" ! glimagesink + gst-launch-1.0 libcamerasrc camera-name="Camera 1" ! queue ! glimagesink To show the first camera found you can omit the camera-name property, or you can list the cameras and their capabilities using: @@ -143,7 +147,7 @@ if desired with a pipeline such as: .. code:: gst-launch-1.0 libcamerasrc ! 'video/x-raw,width=1280,height=720' ! \ - glimagesink + queue ! glimagesink The libcamerasrc element has two log categories, named libcamera-provider (for the video device provider) and libcamerasrc (for the operation of the camera). @@ -159,7 +163,7 @@ the following example could be used as a starting point: gst-launch-1.0 libcamerasrc ! \ video/x-raw,colorimetry=bt709,format=NV12,width=1280,height=720,framerate=30/1 ! \ - jpegenc ! multipartmux ! \ + queue ! jpegenc ! multipartmux ! \ tcpserversink host=0.0.0.0 port=5000 Which can be received on another device over the network with: @@ -169,6 +173,22 @@ Which can be received on another device over the network with: gst-launch-1.0 tcpclientsrc host=$DEVICE_IP port=5000 ! \ multipartdemux ! jpegdec ! autovideosink +The GStreamer element also supports multiple streams. This is achieved by +requesting additional source pads. Downstream caps filters can be used +to choose specific parameters like resolution and pixel format. The pad +property ``stream-role`` can be used to select a role. + +The following example displays a 640x480 view finder while streaming JPEG +encoded 800x600 video. You can use the receiver pipeline above to view the +remote stream from another device. + +.. code:: + + gst-launch-1.0 libcamerasrc name=cs src::stream-role=view-finder src_0::stream-role=video-recording \ + cs.src ! queue ! video/x-raw,width=640,height=480 ! videoconvert ! autovideosink \ + cs.src_0 ! queue ! video/x-raw,width=800,height=600 ! videoconvert ! \ + jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=5000 + .. section-end-getting-started Troubleshooting @@ -186,8 +206,8 @@ the build.ninja module. This is a snippet of the error message. This can be solved in two ways: -1) Don't install meson again if it is already installed system-wide. +1. Don't install meson again if it is already installed system-wide. -2) If a version of meson which is different from the system-wide version is -already installed, uninstall that meson using pip3, and install again without -the --user argument. +2. If a version of meson which is different from the system-wide version is + already installed, uninstall that meson using pip3, and install again without + the --user argument. diff --git a/include/libcamera/base/backtrace.h b/include/libcamera/base/backtrace.h index 752034d1..699ddd9e 100644 --- a/include/libcamera/base/backtrace.h +++ b/include/libcamera/base/backtrace.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * backtrace.h - Call stack backtraces + * Call stack backtraces */ #pragma once diff --git a/include/libcamera/base/bound_method.h b/include/libcamera/base/bound_method.h index c0275249..507c320d 100644 --- a/include/libcamera/base/bound_method.h +++ b/include/libcamera/base/bound_method.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * bound_method.h - Method bind and invocation + * Method bind and invocation */ #pragma once @@ -98,21 +98,15 @@ public: using PackType = BoundMethodPack<R, Args...>; private: - template<std::size_t... I, typename T = R> - std::enable_if_t<!std::is_void<T>::value, void> - invokePack(BoundMethodPackBase *pack, std::index_sequence<I...>) + template<std::size_t... I> + void invokePack(BoundMethodPackBase *pack, std::index_sequence<I...>) { - PackType *args = static_cast<PackType *>(pack); - args->ret_ = invoke(std::get<I>(args->args_)...); - } + [[maybe_unused]] auto *args = static_cast<PackType *>(pack); - template<std::size_t... I, typename T = R> - std::enable_if_t<std::is_void<T>::value, void> - invokePack(BoundMethodPackBase *pack, std::index_sequence<I...>) - { - /* args is effectively unused when the sequence I is empty. */ - PackType *args [[gnu::unused]] = static_cast<PackType *>(pack); - invoke(std::get<I>(args->args_)...); + if constexpr (!std::is_void_v<R>) + args->ret_ = invoke(std::get<I>(args->args_)...); + else + invoke(std::get<I>(args->args_)...); } public: diff --git a/include/libcamera/base/class.h b/include/libcamera/base/class.h index 571eecf4..a808422e 100644 --- a/include/libcamera/base/class.h +++ b/include/libcamera/base/class.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * class.h - Utilities and helpers for classes + * Utilities and helpers for classes */ #pragma once diff --git a/include/libcamera/base/compiler.h b/include/libcamera/base/compiler.h deleted file mode 100644 index 02564f2f..00000000 --- a/include/libcamera/base/compiler.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2021, Google Inc. - * - * compiler.h - Compiler support - */ - -#pragma once - -#if __cplusplus >= 201703L -#define __nodiscard [[nodiscard]] -#else -#define __nodiscard -#endif diff --git a/include/libcamera/base/event_dispatcher.h b/include/libcamera/base/event_dispatcher.h index 184f1b12..408f8da6 100644 --- a/include/libcamera/base/event_dispatcher.h +++ b/include/libcamera/base/event_dispatcher.h @@ -2,13 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * event_dispatcher.h - Event dispatcher + * Event dispatcher */ #pragma once -#include <vector> - #include <libcamera/base/private.h> namespace libcamera { diff --git a/include/libcamera/base/event_dispatcher_poll.h b/include/libcamera/base/event_dispatcher_poll.h index b7840309..1f7e05cf 100644 --- a/include/libcamera/base/event_dispatcher_poll.h +++ b/include/libcamera/base/event_dispatcher_poll.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event_dispatcher_poll.h - Poll-based event dispatcher + * Poll-based event dispatcher */ #pragma once diff --git a/include/libcamera/base/event_notifier.h b/include/libcamera/base/event_notifier.h index e5c0594d..158f2d44 100644 --- a/include/libcamera/base/event_notifier.h +++ b/include/libcamera/base/event_notifier.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event_notifier.h - File descriptor event notifier + * File descriptor event notifier */ #pragma once diff --git a/include/libcamera/base/file.h b/include/libcamera/base/file.h index 0cdc2ed0..6d3f106d 100644 --- a/include/libcamera/base/file.h +++ b/include/libcamera/base/file.h @@ -2,15 +2,15 @@ /* * Copyright (C) 2020, Google Inc. * - * file.h - File I/O operations + * File I/O operations */ #pragma once -#include <sys/types.h> - #include <map> +#include <stdint.h> #include <string> +#include <sys/types.h> #include <libcamera/base/private.h> diff --git a/include/libcamera/base/flags.h b/include/libcamera/base/flags.h index a1b404bd..af4f6e35 100644 --- a/include/libcamera/base/flags.h +++ b/include/libcamera/base/flags.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * flags.h - Type-safe enum-based bitfields + * Type-safe enum-based bitfields */ #pragma once diff --git a/include/libcamera/base/log.h b/include/libcamera/base/log.h index dcaacbe0..8af74b59 100644 --- a/include/libcamera/base/log.h +++ b/include/libcamera/base/log.h @@ -2,13 +2,14 @@ /* * Copyright (C) 2018, Google Inc. * - * log.h - Logging infrastructure + * Logging infrastructure */ #pragma once -#include <chrono> +#include <atomic> #include <sstream> +#include <string_view> #include <libcamera/base/private.h> @@ -29,25 +30,29 @@ enum LogSeverity { class LogCategory { public: - static LogCategory *create(const char *name); + static LogCategory *create(std::string_view name); const std::string &name() const { return name_; } - LogSeverity severity() const { return severity_; } - void setSeverity(LogSeverity severity); + LogSeverity severity() const { return severity_.load(std::memory_order_relaxed); } + void setSeverity(LogSeverity severity) { severity_.store(severity, std::memory_order_relaxed); } static const LogCategory &defaultCategory(); private: - explicit LogCategory(const char *name); + friend class Logger; + explicit LogCategory(std::string_view name); const std::string name_; - LogSeverity severity_; + + std::atomic<LogSeverity> severity_; + static_assert(decltype(severity_)::is_always_lock_free); }; #define LOG_DECLARE_CATEGORY(name) \ extern const LogCategory &_LOG_CATEGORY(name)(); #define LOG_DEFINE_CATEGORY(name) \ +LOG_DECLARE_CATEGORY(name) \ const LogCategory &_LOG_CATEGORY(name)() \ { \ /* The instance will be deleted by the Logger destructor. */ \ @@ -60,9 +65,7 @@ class LogMessage public: LogMessage(const char *fileName, unsigned int line, const LogCategory &category, LogSeverity severity, - const std::string &prefix = std::string()); - - LogMessage(LogMessage &&); + std::string prefix = {}); ~LogMessage(); std::ostream &stream() { return msgStream_; } @@ -75,9 +78,7 @@ public: const std::string msg() const { return msgStream_.str(); } private: - LIBCAMERA_DISABLE_COPY(LogMessage) - - void init(const char *fileName, unsigned int line); + LIBCAMERA_DISABLE_COPY_AND_MOVE(LogMessage) std::ostringstream msgStream_; const LogCategory &category_; diff --git a/include/libcamera/base/memfd.h b/include/libcamera/base/memfd.h new file mode 100644 index 00000000..705d9929 --- /dev/null +++ b/include/libcamera/base/memfd.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Anonymous file creation + */ + +#pragma once + +#include <libcamera/base/flags.h> +#include <libcamera/base/unique_fd.h> + +namespace libcamera { + +class MemFd +{ +public: + enum class Seal { + None = 0, + Shrink = (1 << 0), + Grow = (1 << 1), + }; + + using Seals = Flags<Seal>; + + static UniqueFD create(const char *name, std::size_t size, + Seals seals = Seal::None); +}; + +LIBCAMERA_FLAGS_ENABLE_OPERATORS(MemFd::Seal) + +} /* namespace libcamera */ diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build index bace25d5..f28ae4d4 100644 --- a/include/libcamera/base/meson.build +++ b/include/libcamera/base/meson.build @@ -5,7 +5,6 @@ libcamera_base_include_dir = libcamera_include_dir / 'base' libcamera_base_public_headers = files([ 'bound_method.h', 'class.h', - 'compiler.h', 'flags.h', 'object.h', 'shared_fd.h', @@ -21,6 +20,7 @@ libcamera_base_private_headers = files([ 'event_notifier.h', 'file.h', 'log.h', + 'memfd.h', 'message.h', 'mutex.h', 'private.h', diff --git a/include/libcamera/base/message.h b/include/libcamera/base/message.h index b939af6f..4b232031 100644 --- a/include/libcamera/base/message.h +++ b/include/libcamera/base/message.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * message.h - Message queue support + * Message queue support */ #pragma once diff --git a/include/libcamera/base/mutex.h b/include/libcamera/base/mutex.h index 52441c55..20ebe6fe 100644 --- a/include/libcamera/base/mutex.h +++ b/include/libcamera/base/mutex.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * mutex.h - Mutex classes with clang thread safety annotation + * Mutex classes with clang thread safety annotation */ #pragma once @@ -23,10 +23,6 @@ namespace libcamera { class LIBCAMERA_TSA_CAPABILITY("mutex") Mutex final { public: - constexpr Mutex() - { - } - void lock() LIBCAMERA_TSA_ACQUIRE() { mutex_.lock(); @@ -84,10 +80,6 @@ private: class ConditionVariable final { public: - ConditionVariable() - { - } - void notify_one() noexcept { cv_.notify_one(); diff --git a/include/libcamera/base/object.h b/include/libcamera/base/object.h index 93333636..a24f84ff 100644 --- a/include/libcamera/base/object.h +++ b/include/libcamera/base/object.h @@ -2,16 +2,18 @@ /* * Copyright (C) 2019, Google Inc. * - * object.h - Base object + * Base object */ #pragma once #include <list> #include <memory> +#include <utility> #include <vector> #include <libcamera/base/bound_method.h> +#include <libcamera/base/class.h> namespace libcamera { @@ -38,7 +40,7 @@ public: { T *obj = static_cast<T *>(this); auto *method = new BoundMethodMember<T, R, FuncArgs...>(obj, this, func, type); - return method->activate(args..., true); + return method->activate(std::forward<Args>(args)..., true); } Thread *thread() const { return thread_; } @@ -49,7 +51,11 @@ public: protected: virtual void message(Message *msg); + bool assertThreadBound(const char *message); + private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(Object) + friend class SignalBase; friend class Thread; diff --git a/include/libcamera/base/private.h b/include/libcamera/base/private.h index 163012bf..8670c40b 100644 --- a/include/libcamera/base/private.h +++ b/include/libcamera/base/private.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * private.h - Private Header Validation + * Private Header Validation * * A selection of internal libcamera headers are installed as part * of the libcamera package to allow sharing of a select subset of diff --git a/include/libcamera/base/semaphore.h b/include/libcamera/base/semaphore.h index f1052317..59d4aa44 100644 --- a/include/libcamera/base/semaphore.h +++ b/include/libcamera/base/semaphore.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * semaphore.h - General-purpose counting semaphore + * General-purpose counting semaphore */ #pragma once diff --git a/include/libcamera/base/shared_fd.h b/include/libcamera/base/shared_fd.h index e53a8b88..61fe11c1 100644 --- a/include/libcamera/base/shared_fd.h +++ b/include/libcamera/base/shared_fd.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * shared_fd.h - File descriptor wrapper with shared ownership + * File descriptor wrapper with shared ownership */ #pragma once diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h index 841e4b4c..ed1d81ea 100644 --- a/include/libcamera/base/signal.h +++ b/include/libcamera/base/signal.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal.h - Signal & slot implementation + * Signal & slot implementation */ #pragma once @@ -10,13 +10,13 @@ #include <functional> #include <list> #include <type_traits> -#include <vector> #include <libcamera/base/bound_method.h> -#include <libcamera/base/object.h> namespace libcamera { +class Object; + class SignalBase { public: @@ -63,11 +63,8 @@ public: #ifndef __DOXYGEN__ template<typename T, typename Func, - std::enable_if_t<std::is_base_of<Object, T>::value -#if __cplusplus >= 201703L - && std::is_invocable_v<Func, Args...> -#endif - > * = nullptr> + std::enable_if_t<std::is_base_of<Object, T>::value && + std::is_invocable_v<Func, Args...>> * = nullptr> void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) { Object *object = static_cast<Object *>(obj); @@ -75,11 +72,8 @@ public: } template<typename T, typename Func, - std::enable_if_t<!std::is_base_of<Object, T>::value -#if __cplusplus >= 201703L - && std::is_invocable_v<Func, Args...> -#endif - > * = nullptr> + std::enable_if_t<!std::is_base_of<Object, T>::value && + std::is_invocable_v<Func, Args...>> * = nullptr> #else template<typename T, typename Func> #endif diff --git a/include/libcamera/base/span.h b/include/libcamera/base/span.h index 88d2e3de..806db106 100644 --- a/include/libcamera/base/span.h +++ b/include/libcamera/base/span.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * span.h - C++20 std::span<> implementation for C++11 + * C++20 std::span<> implementation for C++11 */ #pragma once @@ -10,7 +10,6 @@ #include <array> #include <iterator> #include <limits> -#include <stddef.h> #include <type_traits> namespace libcamera { @@ -347,13 +346,7 @@ public: } constexpr Span(const Span &other) noexcept = default; - - constexpr Span &operator=(const Span &other) noexcept - { - data_ = other.data_; - size_ = other.size_; - return *this; - } + constexpr Span &operator=(const Span &other) noexcept = default; constexpr iterator begin() const { return data(); } constexpr const_iterator cbegin() const { return begin(); } diff --git a/include/libcamera/base/thread.h b/include/libcamera/base/thread.h index 9d00f102..b9284c2c 100644 --- a/include/libcamera/base/thread.h +++ b/include/libcamera/base/thread.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * thread.h - Thread support + * Thread support */ #pragma once @@ -13,8 +13,10 @@ #include <libcamera/base/private.h> +#include <libcamera/base/class.h> #include <libcamera/base/message.h> #include <libcamera/base/signal.h> +#include <libcamera/base/span.h> #include <libcamera/base/utils.h> namespace libcamera { @@ -35,6 +37,8 @@ public: void exit(int code = 0); bool wait(utils::duration duration = utils::duration::max()); + int setThreadAffinity(const Span<const unsigned int> &cpus); + bool isRunning(); Signal<> finished; @@ -44,16 +48,21 @@ public: EventDispatcher *eventDispatcher(); - void dispatchMessages(Message::Type type = Message::Type::None); + void dispatchMessages(Message::Type type = Message::Type::None, + Object *receiver = nullptr); protected: int exec(); virtual void run(); private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(Thread) + void startThread(); void finishThread(); + void setThreadAffinityInternal(); + void postMessage(std::unique_ptr<Message> msg, Object *receiver); void removeMessages(Object *receiver); diff --git a/include/libcamera/base/thread_annotations.h b/include/libcamera/base/thread_annotations.h index 25b3c7b6..81930f08 100644 --- a/include/libcamera/base/thread_annotations.h +++ b/include/libcamera/base/thread_annotations.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * thread_annotation.h - Macro of Clang thread safety analysis + * Macro of Clang thread safety analysis */ #pragma once diff --git a/include/libcamera/base/timer.h b/include/libcamera/base/timer.h index 759b68ad..9646a0fe 100644 --- a/include/libcamera/base/timer.h +++ b/include/libcamera/base/timer.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * timer.h - Generic timer + * Generic timer */ #pragma once #include <chrono> -#include <stdint.h> #include <libcamera/base/private.h> diff --git a/include/libcamera/base/unique_fd.h b/include/libcamera/base/unique_fd.h index ae4d96b7..3066bf28 100644 --- a/include/libcamera/base/unique_fd.h +++ b/include/libcamera/base/unique_fd.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * unique_fd.h - File descriptor wrapper that owns a file descriptor. + * File descriptor wrapper that owns a file descriptor. */ #pragma once @@ -10,7 +10,6 @@ #include <utility> #include <libcamera/base/class.h> -#include <libcamera/base/compiler.h> namespace libcamera { @@ -43,7 +42,7 @@ public: return *this; } - __nodiscard int release() + [[nodiscard]] int release() { int fd = fd_; fd_ = -1; diff --git a/include/libcamera/base/utils.h b/include/libcamera/base/utils.h index 37d9af60..8d5c3578 100644 --- a/include/libcamera/base/utils.h +++ b/include/libcamera/base/utils.h @@ -2,19 +2,20 @@ /* * Copyright (C) 2018, Google Inc. * - * utils.h - Miscellaneous utility functions + * Miscellaneous utility functions */ #pragma once #include <algorithm> #include <chrono> +#include <functional> #include <iterator> -#include <memory> #include <ostream> #include <sstream> -#include <string> +#include <stdint.h> #include <string.h> +#include <string> #include <sys/time.h> #include <type_traits> #include <utility> @@ -91,6 +92,30 @@ _hex hex(T value, unsigned int width = 0); #ifndef __DOXYGEN__ template<> +inline _hex hex<int8_t>(int8_t value, unsigned int width) +{ + return { static_cast<uint64_t>(value), width ? width : 2 }; +} + +template<> +inline _hex hex<uint8_t>(uint8_t value, unsigned int width) +{ + return { static_cast<uint64_t>(value), width ? width : 2 }; +} + +template<> +inline _hex hex<int16_t>(int16_t value, unsigned int width) +{ + return { static_cast<uint64_t>(value), width ? width : 4 }; +} + +template<> +inline _hex hex<uint16_t>(uint16_t value, unsigned int width) +{ + return { static_cast<uint64_t>(value), width ? width : 4 }; +} + +template<> inline _hex hex<int32_t>(int32_t value, unsigned int width) { return { static_cast<uint64_t>(value), width ? width : 8 }; @@ -180,7 +205,16 @@ public: iterator &operator++(); std::string operator*() const; - bool operator!=(const iterator &other) const; + + bool operator==(const iterator &other) const + { + return pos_ == other.pos_; + } + + bool operator!=(const iterator &other) const + { + return !(*this == other); + } private: const StringSplitter *ss_; @@ -188,8 +222,15 @@ public: std::string::size_type next_; }; - iterator begin() const; - iterator end() const; + iterator begin() const + { + return { this, 0 }; + } + + iterator end() const + { + return { this, std::string::npos }; + } private: std::string str_; @@ -369,6 +410,24 @@ decltype(auto) abs_diff(const T &a, const T &b) double strtod(const char *__restrict nptr, char **__restrict endptr); +template<class Enum> +constexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept +{ + return static_cast<std::underlying_type_t<Enum>>(e); +} + +class ScopeExitActions +{ +public: + ~ScopeExitActions(); + + void operator+=(std::function<void()> &&action); + void release(); + +private: + std::vector<std::function<void()>> actions_; +}; + } /* namespace utils */ #ifndef __DOXYGEN__ diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index ae35792d..94cee7bd 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * camera.h - Camera object interface + * Camera object interface */ #pragma once diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h index 1a891cac..27835500 100644 --- a/include/libcamera/camera_manager.h +++ b/include/libcamera/camera_manager.h @@ -2,13 +2,14 @@ /* * Copyright (C) 2018, Google Inc. * - * camera_manager.h - Camera management + * Camera management */ #pragma once #include <memory> #include <string> +#include <string_view> #include <sys/types.h> #include <vector> @@ -31,7 +32,7 @@ public: void stop(); std::vector<std::shared_ptr<Camera>> cameras() const; - std::shared_ptr<Camera> get(const std::string &id); + std::shared_ptr<Camera> get(std::string_view id); static const std::string &version() { return version_; } diff --git a/include/libcamera/color_space.h b/include/libcamera/color_space.h index 6d6c2829..7b483cd1 100644 --- a/include/libcamera/color_space.h +++ b/include/libcamera/color_space.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * color_space.h - color space definitions + * color space definitions */ #pragma once diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in index 0718a888..5d0594c6 100644 --- a/include/libcamera/control_ids.h.in +++ b/include/libcamera/control_ids.h.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_ids.h - Control ID list + * {{mode|capitalize}} ID list * * This file is auto-generated. Do not edit. */ @@ -10,28 +10,52 @@ #pragma once #include <array> +#include <map> #include <stdint.h> +#include <string> #include <libcamera/controls.h> namespace libcamera { -namespace controls { +namespace {{mode}} { -enum { -${ids} -}; - -${controls} - -extern const ControlIdMap controls; +extern const ControlIdMap {{mode}}; -namespace draft { +{%- for vendor, ctrls in controls -%} -${draft_controls} +{% if vendor != 'libcamera' %} +namespace {{vendor}} { -} /* namespace draft */ +#define LIBCAMERA_HAS_{{vendor|upper}}_VENDOR_{{mode|upper}} +{%- endif %} -} /* namespace controls */ +{% if ctrls %} +enum { +{%- for ctrl in ctrls %} + {{ctrl.name|snake_case|upper}} = {{ctrl.id}}, +{%- endfor %} +}; +{% endif %} + +{% for ctrl in ctrls -%} +{% if ctrl.is_enum -%} +enum {{ctrl.name}}Enum { +{%- for enum in ctrl.enum_values %} + {{enum.name}} = {{enum.value}}, +{%- endfor %} +}; +extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values; +extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap; +{% endif -%} +extern const Control<{{ctrl.type}}> {{ctrl.name}}; +{% endfor -%} + +{% if vendor != 'libcamera' %} +} /* namespace {{vendor}} */ +{% endif -%} + +{% endfor %} +} /* namespace {{mode}} */ } /* namespace libcamera */ diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index cf942055..2ae4ec3d 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -2,12 +2,13 @@ /* * Copyright (C) 2019, Google Inc. * - * controls.h - Control handling + * Control handling */ #pragma once #include <assert.h> +#include <map> #include <optional> #include <set> #include <stdint.h> @@ -16,6 +17,7 @@ #include <vector> #include <libcamera/base/class.h> +#include <libcamera/base/flags.h> #include <libcamera/base/span.h> #include <libcamera/geometry.h> @@ -28,67 +30,102 @@ enum ControlType { ControlTypeNone, ControlTypeBool, ControlTypeByte, + ControlTypeUnsigned16, + ControlTypeUnsigned32, ControlTypeInteger32, ControlTypeInteger64, ControlTypeFloat, ControlTypeString, ControlTypeRectangle, ControlTypeSize, + ControlTypePoint, }; namespace details { -template<typename T> +template<typename T, typename = std::void_t<>> struct control_type { }; template<> struct control_type<void> { static constexpr ControlType value = ControlTypeNone; + static constexpr std::size_t size = 0; }; template<> struct control_type<bool> { static constexpr ControlType value = ControlTypeBool; + static constexpr std::size_t size = 0; }; template<> struct control_type<uint8_t> { static constexpr ControlType value = ControlTypeByte; + static constexpr std::size_t size = 0; +}; + +template<> +struct control_type<uint16_t> { + static constexpr ControlType value = ControlTypeUnsigned16; + static constexpr std::size_t size = 0; +}; + +template<> +struct control_type<uint32_t> { + static constexpr ControlType value = ControlTypeUnsigned32; + static constexpr std::size_t size = 0; }; template<> struct control_type<int32_t> { static constexpr ControlType value = ControlTypeInteger32; + static constexpr std::size_t size = 0; }; template<> struct control_type<int64_t> { static constexpr ControlType value = ControlTypeInteger64; + static constexpr std::size_t size = 0; }; template<> struct control_type<float> { static constexpr ControlType value = ControlTypeFloat; + static constexpr std::size_t size = 0; }; template<> struct control_type<std::string> { static constexpr ControlType value = ControlTypeString; + static constexpr std::size_t size = 0; }; template<> struct control_type<Rectangle> { static constexpr ControlType value = ControlTypeRectangle; + static constexpr std::size_t size = 0; }; template<> struct control_type<Size> { static constexpr ControlType value = ControlTypeSize; + static constexpr std::size_t size = 0; +}; + +template<> +struct control_type<Point> { + static constexpr ControlType value = ControlTypePoint; + static constexpr std::size_t size = 0; }; template<typename T, std::size_t N> -struct control_type<Span<T, N>> : public control_type<std::remove_cv_t<T>> { +struct control_type<Span<T, N>, std::enable_if_t<control_type<std::remove_cv_t<T>>::size == 0>> : public control_type<std::remove_cv_t<T>> { + static constexpr std::size_t size = N; +}; + +template<typename T> +struct control_type<T, std::enable_if_t<std::is_enum_v<T> && sizeof(T) == sizeof(int32_t)>> : public control_type<int32_t> { }; } /* namespace details */ @@ -213,23 +250,44 @@ private: class ControlId { public: - ControlId(unsigned int id, const std::string &name, ControlType type) - : id_(id), name_(name), type_(type) - { - } + enum class Direction { + In = (1 << 0), + Out = (1 << 1), + }; + + using DirectionFlags = Flags<Direction>; + + ControlId(unsigned int id, const std::string &name, const std::string &vendor, + ControlType type, DirectionFlags direction, + std::size_t size = 0, + const std::map<std::string, int32_t> &enumStrMap = {}); unsigned int id() const { return id_; } const std::string &name() const { return name_; } + const std::string &vendor() const { return vendor_; } ControlType type() const { return type_; } + DirectionFlags direction() const { return direction_; } + bool isInput() const { return !!(direction_ & Direction::In); } + bool isOutput() const { return !!(direction_ & Direction::Out); } + bool isArray() const { return size_ > 0; } + std::size_t size() const { return size_; } + const std::map<int32_t, std::string> &enumerators() const { return reverseMap_; } private: LIBCAMERA_DISABLE_COPY_AND_MOVE(ControlId) unsigned int id_; std::string name_; + std::string vendor_; ControlType type_; + DirectionFlags direction_; + std::size_t size_; + std::map<std::string, int32_t> enumStrMap_; + std::map<int32_t, std::string> reverseMap_; }; +LIBCAMERA_FLAGS_ENABLE_OPERATORS(ControlId::Direction) + static inline bool operator==(unsigned int lhs, const ControlId &rhs) { return lhs == rhs.id(); @@ -256,8 +314,11 @@ class Control : public ControlId public: using type = T; - Control(unsigned int id, const char *name) - : ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value) + Control(unsigned int id, const char *name, const char *vendor, + ControlId::DirectionFlags direction, + const std::map<std::string, int32_t> &enumStrMap = {}) + : ControlId(id, name, vendor, details::control_type<std::remove_cv_t<T>>::value, + direction, details::control_type<std::remove_cv_t<T>>::size, enumStrMap) { } @@ -352,6 +413,11 @@ private: using ControlListMap = std::unordered_map<unsigned int, ControlValue>; public: + enum class MergePolicy { + KeepExisting = 0, + OverwriteExisting, + }; + ControlList(); ControlList(const ControlIdMap &idmap, const ControlValidator *validator = nullptr); ControlList(const ControlInfoMap &infoMap, const ControlValidator *validator = nullptr); @@ -368,7 +434,7 @@ public: std::size_t size() const { return controls_.size(); } void clear() { controls_.clear(); } - void merge(const ControlList &source); + void merge(const ControlList &source, MergePolicy policy = MergePolicy::KeepExisting); bool contains(unsigned int id) const; diff --git a/include/libcamera/fence.h b/include/libcamera/fence.h index c0c916c2..598336cb 100644 --- a/include/libcamera/fence.h +++ b/include/libcamera/fence.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * internal/fence.h - Synchronization fence + * Synchronization fence */ #pragma once diff --git a/include/libcamera/formats.h.in b/include/libcamera/formats.h.in index ead5287d..6ae7634f 100644 --- a/include/libcamera/formats.h.in +++ b/include/libcamera/formats.h.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * formats.h - Formats + * Formats * * This file is auto-generated. Do not edit. */ diff --git a/include/libcamera/framebuffer.h b/include/libcamera/framebuffer.h index 61244829..ff839243 100644 --- a/include/libcamera/framebuffer.h +++ b/include/libcamera/framebuffer.h @@ -2,12 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * framebuffer.h - Frame buffer handling + * Frame buffer handling */ #pragma once -#include <assert.h> #include <limits> #include <memory> #include <stdint.h> diff --git a/include/libcamera/framebuffer_allocator.h b/include/libcamera/framebuffer_allocator.h index 45ff232b..f3896bf2 100644 --- a/include/libcamera/framebuffer_allocator.h +++ b/include/libcamera/framebuffer_allocator.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * framebuffer_allocator.h - FrameBuffer allocator + * FrameBuffer allocator */ #pragma once diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h index d7fdbe70..f322e3d5 100644 --- a/include/libcamera/geometry.h +++ b/include/libcamera/geometry.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * geometry.h - Geometry-related classes + * Geometry-related classes */ #pragma once @@ -11,8 +11,6 @@ #include <ostream> #include <string> -#include <libcamera/base/compiler.h> - namespace libcamera { class Rectangle; @@ -110,8 +108,8 @@ public: return *this; } - __nodiscard constexpr Size alignedDownTo(unsigned int hAlignment, - unsigned int vAlignment) const + [[nodiscard]] constexpr Size alignedDownTo(unsigned int hAlignment, + unsigned int vAlignment) const { return { width / hAlignment * hAlignment, @@ -119,8 +117,8 @@ public: }; } - __nodiscard constexpr Size alignedUpTo(unsigned int hAlignment, - unsigned int vAlignment) const + [[nodiscard]] constexpr Size alignedUpTo(unsigned int hAlignment, + unsigned int vAlignment) const { return { (width + hAlignment - 1) / hAlignment * hAlignment, @@ -128,7 +126,7 @@ public: }; } - __nodiscard constexpr Size boundedTo(const Size &bound) const + [[nodiscard]] constexpr Size boundedTo(const Size &bound) const { return { std::min(width, bound.width), @@ -136,7 +134,7 @@ public: }; } - __nodiscard constexpr Size expandedTo(const Size &expand) const + [[nodiscard]] constexpr Size expandedTo(const Size &expand) const { return { std::max(width, expand.width), @@ -144,7 +142,7 @@ public: }; } - __nodiscard constexpr Size grownBy(const Size &margins) const + [[nodiscard]] constexpr Size grownBy(const Size &margins) const { return { width + margins.width, @@ -152,7 +150,7 @@ public: }; } - __nodiscard constexpr Size shrunkBy(const Size &margins) const + [[nodiscard]] constexpr Size shrunkBy(const Size &margins) const { return { width > margins.width ? width - margins.width : 0, @@ -160,10 +158,10 @@ public: }; } - __nodiscard Size boundedToAspectRatio(const Size &ratio) const; - __nodiscard Size expandedToAspectRatio(const Size &ratio) const; + [[nodiscard]] Size boundedToAspectRatio(const Size &ratio) const; + [[nodiscard]] Size expandedToAspectRatio(const Size &ratio) const; - __nodiscard Rectangle centeredTo(const Point ¢er) const; + [[nodiscard]] Rectangle centeredTo(const Point ¢er) const; Size operator*(float factor) const; Size operator/(float factor) const; @@ -262,6 +260,15 @@ public: { } + constexpr Rectangle(const Point &point1, const Point &point2) + : Rectangle(std::min(point1.x, point2.x), std::min(point1.y, point2.y), + static_cast<unsigned int>(std::max(point1.x, point2.x)) - + static_cast<unsigned int>(std::min(point1.x, point2.x)), + static_cast<unsigned int>(std::max(point1.y, point2.y)) - + static_cast<unsigned int>(std::min(point1.y, point2.y))) + { + } + int x; int y; unsigned int width; @@ -285,11 +292,14 @@ public: Rectangle &scaleBy(const Size &numerator, const Size &denominator); Rectangle &translateBy(const Point &point); - __nodiscard Rectangle boundedTo(const Rectangle &bound) const; - __nodiscard Rectangle enclosedIn(const Rectangle &boundary) const; - __nodiscard Rectangle scaledBy(const Size &numerator, - const Size &denominator) const; - __nodiscard Rectangle translatedBy(const Point &point) const; + [[nodiscard]] Rectangle boundedTo(const Rectangle &bound) const; + [[nodiscard]] Rectangle enclosedIn(const Rectangle &boundary) const; + [[nodiscard]] Rectangle scaledBy(const Size &numerator, + const Size &denominator) const; + [[nodiscard]] Rectangle translatedBy(const Point &point) const; + + Rectangle transformedBetween(const Rectangle &source, + const Rectangle &target) const; }; bool operator==(const Rectangle &lhs, const Rectangle &rhs); diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h index 78ba3969..5c14bb5f 100644 --- a/include/libcamera/internal/bayer_format.h +++ b/include/libcamera/internal/bayer_format.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * bayer_format.h - Bayer Pixel Format + * Bayer Pixel Format */ #pragma once @@ -34,6 +34,8 @@ public: None = 0, CSI2 = 1, IPU3 = 2, + PISP1 = 3, + PISP2 = 4, }; constexpr BayerFormat() diff --git a/include/libcamera/internal/byte_stream_buffer.h b/include/libcamera/internal/byte_stream_buffer.h index 0f4fce6f..5b1c10ab 100644 --- a/include/libcamera/internal/byte_stream_buffer.h +++ b/include/libcamera/internal/byte_stream_buffer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * byte_stream_buffer.h - Byte stream buffer + * Byte stream buffer */ #pragma once diff --git a/include/libcamera/internal/camera.h b/include/libcamera/internal/camera.h index 38dd94ff..18f5c32a 100644 --- a/include/libcamera/internal/camera.h +++ b/include/libcamera/internal/camera.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera.h - Camera private data + * Camera private data */ #pragma once @@ -11,6 +11,7 @@ #include <list> #include <memory> #include <set> +#include <stdint.h> #include <string> #include <libcamera/base/class.h> @@ -32,6 +33,7 @@ public: ~Private(); PipelineHandler *pipe() { return pipe_.get(); } + const PipelineHandler *pipe() const { return pipe_.get(); } std::list<Request *> queuedRequests_; ControlInfoMap controlInfo_; diff --git a/include/libcamera/internal/camera_controls.h b/include/libcamera/internal/camera_controls.h index ee6d382f..4a5a3ebc 100644 --- a/include/libcamera/internal/camera_controls.h +++ b/include/libcamera/internal/camera_controls.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_controls.h - Camera controls + * Camera controls */ #pragma once diff --git a/include/libcamera/internal/camera_lens.h b/include/libcamera/internal/camera_lens.h index 277417da..f347c5e0 100644 --- a/include/libcamera/internal/camera_lens.h +++ b/include/libcamera/internal/camera_lens.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_lens.h - A camera lens controller + * A camera lens controller */ #pragma once #include <memory> +#include <stdint.h> #include <string> #include <libcamera/base/class.h> diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h index 33ebe069..0150ca61 100644 --- a/include/libcamera/internal/camera_manager.h +++ b/include/libcamera/internal/camera_manager.h @@ -2,14 +2,13 @@ /* * Copyright (C) 2023, Ideas on Board Oy. * - * camera_manager.h - Camera manager private data + * Camera manager private data */ #pragma once #include <libcamera/camera_manager.h> -#include <map> #include <memory> #include <sys/types.h> #include <vector> @@ -19,13 +18,14 @@ #include <libcamera/base/thread.h> #include <libcamera/base/thread_annotations.h> -#include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/process.h" namespace libcamera { class Camera; class DeviceEnumerator; +class IPAManager; +class PipelineHandlerFactoryBase; class CameraManager::Private : public Extensible::Private, public Thread { @@ -38,12 +38,15 @@ public: void addCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_); void removeCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_); + IPAManager *ipaManager() const { return ipaManager_.get(); } + protected: void run() override; private: int init(); void createPipelineHandlers(); + void pipelineFactoryMatch(const PipelineHandlerFactoryBase *factory); void cleanup() LIBCAMERA_TSA_EXCLUDES(mutex_); /* @@ -61,7 +64,7 @@ private: std::unique_ptr<DeviceEnumerator> enumerator_; - IPAManager ipaManager_; + std::unique_ptr<IPAManager> ipaManager_; ProcessManager processManager_; }; diff --git a/include/libcamera/internal/camera_sensor.h b/include/libcamera/internal/camera_sensor.h index 60a8b106..13048f32 100644 --- a/include/libcamera/internal/camera_sensor.h +++ b/include/libcamera/internal/camera_sensor.h @@ -2,17 +2,18 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_sensor.h - A camera sensor + * A camera sensor */ #pragma once #include <memory> +#include <stdint.h> #include <string> +#include <variant> #include <vector> #include <libcamera/base/class.h> -#include <libcamera/base/log.h> #include <libcamera/control_ids.h> #include <libcamera/controls.h> @@ -20,106 +21,111 @@ #include <libcamera/orientation.h> #include <libcamera/transform.h> -#include <libcamera/ipa/core_ipa_interface.h> - -#include "libcamera/internal/formats.h" +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/v4l2_subdevice.h" namespace libcamera { -class BayerFormat; class CameraLens; class MediaEntity; class SensorConfiguration; -struct CameraSensorProperties; - enum class Orientation; -class CameraSensor : protected Loggable +struct IPACameraSensorInfo; + +class CameraSensor { public: - explicit CameraSensor(const MediaEntity *entity); - ~CameraSensor(); - - int init(); - - const std::string &model() const { return model_; } - const std::string &id() const { return id_; } - const MediaEntity *entity() const { return entity_; } - const std::vector<unsigned int> &mbusCodes() const { return mbusCodes_; } - std::vector<Size> sizes(unsigned int mbusCode) const; - Size resolution() const; - const std::vector<controls::draft::TestPatternModeEnum> &testPatternModes() const - { - return testPatternModes_; - } - int setTestPatternMode(controls::draft::TestPatternModeEnum mode); + virtual ~CameraSensor(); - V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes, - const Size &size) const; - int setFormat(V4L2SubdeviceFormat *format, - Transform transform = Transform::Identity); - int tryFormat(V4L2SubdeviceFormat *format) const; + virtual const std::string &model() const = 0; + virtual const std::string &id() const = 0; - int applyConfiguration(const SensorConfiguration &config, - Transform transform = Transform::Identity, - V4L2SubdeviceFormat *sensorFormat = nullptr); + virtual const MediaEntity *entity() const = 0; + virtual V4L2Subdevice *device() = 0; - const ControlInfoMap &controls() const; - ControlList getControls(const std::vector<uint32_t> &ids); - int setControls(ControlList *ctrls); + virtual CameraLens *focusLens() = 0; - V4L2Subdevice *device() { return subdev_.get(); } + virtual const std::vector<unsigned int> &mbusCodes() const = 0; + virtual std::vector<Size> sizes(unsigned int mbusCode) const = 0; + virtual Size resolution() const = 0; - const ControlList &properties() const { return properties_; } - int sensorInfo(IPACameraSensorInfo *info) const; + virtual V4L2SubdeviceFormat + getFormat(const std::vector<unsigned int> &mbusCodes, + const Size &size, const Size maxSize = Size()) const = 0; + virtual int setFormat(V4L2SubdeviceFormat *format, + Transform transform = Transform::Identity) = 0; + virtual int tryFormat(V4L2SubdeviceFormat *format) const = 0; - void updateControlInfo(); + virtual int applyConfiguration(const SensorConfiguration &config, + Transform transform = Transform::Identity, + V4L2SubdeviceFormat *sensorFormat = nullptr) = 0; - CameraLens *focusLens() { return focusLens_.get(); } + virtual V4L2Subdevice::Stream imageStream() const; + virtual std::optional<V4L2Subdevice::Stream> embeddedDataStream() const; + virtual V4L2SubdeviceFormat embeddedDataFormat() const; + virtual int setEmbeddedDataEnabled(bool enable); - Transform computeTransform(Orientation *orientation) const; + virtual const ControlList &properties() const = 0; + virtual int sensorInfo(IPACameraSensorInfo *info) const = 0; + virtual Transform computeTransform(Orientation *orientation) const = 0; + virtual BayerFormat::Order bayerOrder(Transform t) const = 0; -protected: - std::string logPrefix() const override; + virtual const ControlInfoMap &controls() const = 0; + virtual ControlList getControls(const std::vector<uint32_t> &ids) = 0; + virtual int setControls(ControlList *ctrls) = 0; -private: - LIBCAMERA_DISABLE_COPY(CameraSensor) + virtual const std::vector<controls::draft::TestPatternModeEnum> & + testPatternModes() const = 0; + virtual int setTestPatternMode(controls::draft::TestPatternModeEnum mode) = 0; + virtual const CameraSensorProperties::SensorDelays &sensorDelays() = 0; +}; - int generateId(); - int validateSensorDriver(); - void initVimcDefaultProperties(); - void initStaticProperties(); - void initTestPatternModes(); - int initProperties(); - int applyTestPatternMode(controls::draft::TestPatternModeEnum mode); - int discoverAncillaryDevices(); +class CameraSensorFactoryBase +{ +public: + CameraSensorFactoryBase(const char *name, int priority); + virtual ~CameraSensorFactoryBase() = default; - const MediaEntity *entity_; - std::unique_ptr<V4L2Subdevice> subdev_; - unsigned int pad_; + static std::unique_ptr<CameraSensor> create(MediaEntity *entity); - const CameraSensorProperties *staticProps_; + const std::string &name() const { return name_; } + int priority() const { return priority_; } - std::string model_; - std::string id_; +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorFactoryBase) - V4L2Subdevice::Formats formats_; - std::vector<unsigned int> mbusCodes_; - std::vector<Size> sizes_; - std::vector<controls::draft::TestPatternModeEnum> testPatternModes_; - controls::draft::TestPatternModeEnum testPatternMode_; + static std::vector<CameraSensorFactoryBase *> &factories(); - Size pixelArraySize_; - Rectangle activeArea_; - const BayerFormat *bayerFormat_; - bool supportFlips_; - Orientation mountingOrientation_; + static void registerFactory(CameraSensorFactoryBase *factory); - ControlList properties_; + virtual std::variant<std::unique_ptr<CameraSensor>, int> + match(MediaEntity *entity) const = 0; - std::unique_ptr<CameraLens> focusLens_; + std::string name_; + int priority_; }; +template<typename _CameraSensor> +class CameraSensorFactory final : public CameraSensorFactoryBase +{ +public: + CameraSensorFactory(const char *name, int priority) + : CameraSensorFactoryBase(name, priority) + { + } + +private: + std::variant<std::unique_ptr<CameraSensor>, int> + match(MediaEntity *entity) const override + { + return _CameraSensor::match(entity); + } +}; + +#define REGISTER_CAMERA_SENSOR(sensor, priority) \ +static CameraSensorFactory<sensor> global_##sensor##Factory{ #sensor, priority }; + } /* namespace libcamera */ diff --git a/include/libcamera/internal/camera_sensor_properties.h b/include/libcamera/internal/camera_sensor_properties.h index 1ee3cb99..d7d4dab6 100644 --- a/include/libcamera/internal/camera_sensor_properties.h +++ b/include/libcamera/internal/camera_sensor_properties.h @@ -2,12 +2,13 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_sensor_properties.h - Database of camera sensor properties + * Database of camera sensor properties */ #pragma once #include <map> +#include <stdint.h> #include <string> #include <libcamera/control_ids.h> @@ -16,10 +17,18 @@ namespace libcamera { struct CameraSensorProperties { + struct SensorDelays { + uint8_t exposureDelay; + uint8_t gainDelay; + uint8_t vblankDelay; + uint8_t hblankDelay; + }; + static const CameraSensorProperties *get(const std::string &sensor); Size unitCellSize; std::map<controls::draft::TestPatternModeEnum, int32_t> testPatternModes; + SensorDelays sensorDelays; }; } /* namespace libcamera */ diff --git a/include/libcamera/internal/control_serializer.h b/include/libcamera/internal/control_serializer.h index a38ca6b0..8a63ae44 100644 --- a/include/libcamera/internal/control_serializer.h +++ b/include/libcamera/internal/control_serializer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_serializer.h - Control (de)serializer + * Control (de)serializer */ #pragma once diff --git a/include/libcamera/internal/control_validator.h b/include/libcamera/internal/control_validator.h index 26412d8b..260602f2 100644 --- a/include/libcamera/internal/control_validator.h +++ b/include/libcamera/internal/control_validator.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_validator.h - Control validator + * Control validator */ #pragma once diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h index 834ec5ab..644ec429 100644 --- a/include/libcamera/internal/converter.h +++ b/include/libcamera/internal/converter.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Laurent Pinchart * Copyright 2022 NXP * - * converter.h - Generic format converter interface + * Generic format converter interface */ #pragma once @@ -14,9 +14,11 @@ #include <memory> #include <string> #include <tuple> +#include <utility> #include <vector> #include <libcamera/base/class.h> +#include <libcamera/base/flags.h> #include <libcamera/base/signal.h> #include <libcamera/geometry.h> @@ -26,12 +28,25 @@ namespace libcamera { class FrameBuffer; class MediaDevice; class PixelFormat; +class Stream; struct StreamConfiguration; class Converter { public: - Converter(MediaDevice *media); + enum class Feature { + None = 0, + InputCrop = (1 << 0), + }; + + using Features = Flags<Feature>; + + enum class Alignment { + Down = 0, + Up, + }; + + Converter(MediaDevice *media, Features features = Feature::None); virtual ~Converter(); virtual int loadConfiguration(const std::string &filename) = 0; @@ -41,25 +56,45 @@ public: virtual std::vector<PixelFormat> formats(PixelFormat input) = 0; virtual SizeRange sizes(const Size &input) = 0; + virtual Size adjustInputSize(const PixelFormat &pixFmt, + const Size &size, + Alignment align = Alignment::Down) = 0; + virtual Size adjustOutputSize(const PixelFormat &pixFmt, + const Size &size, + Alignment align = Alignment::Down) = 0; + virtual std::tuple<unsigned int, unsigned int> strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0; + virtual int validateOutput(StreamConfiguration *cfg, bool *adjusted, + Alignment align = Alignment::Down) = 0; + virtual int configure(const StreamConfiguration &inputCfg, const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0; - virtual int exportBuffers(unsigned int output, unsigned int count, + virtual bool isConfigured(const Stream *stream) const = 0; + virtual int exportBuffers(const Stream *stream, unsigned int count, std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0; virtual int start() = 0; virtual void stop() = 0; virtual int queueBuffers(FrameBuffer *input, - const std::map<unsigned int, FrameBuffer *> &outputs) = 0; + const std::map<const Stream *, FrameBuffer *> &outputs) = 0; + + virtual int setInputCrop(const Stream *stream, Rectangle *rect) = 0; + virtual std::pair<Rectangle, Rectangle> inputCropBounds() = 0; + virtual std::pair<Rectangle, Rectangle> inputCropBounds(const Stream *stream) = 0; Signal<FrameBuffer *> inputBufferReady; Signal<FrameBuffer *> outputBufferReady; const std::string &deviceNode() const { return deviceNode_; } + Features features() const { return features_; } + +protected: + Features features_; + private: std::string deviceNode_; }; diff --git a/include/libcamera/internal/converter/converter_v4l2_m2m.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h index 815916d0..0ad7bf7f 100644 --- a/include/libcamera/internal/converter/converter_v4l2_m2m.h +++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Laurent Pinchart * Copyright 2022 NXP * - * converter_v4l2_m2m.h - V4l2 M2M Format converter interface + * V4l2 M2M Format converter interface */ #pragma once @@ -28,7 +28,9 @@ class FrameBuffer; class MediaDevice; class Size; class SizeRange; +class Stream; struct StreamConfiguration; +class Rectangle; class V4L2M2MDevice; class V4L2M2MConverter : public Converter @@ -36,31 +38,45 @@ class V4L2M2MConverter : public Converter public: V4L2M2MConverter(MediaDevice *media); - int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } - bool isValid() const { return m2m_ != nullptr; } + int loadConfiguration([[maybe_unused]] const std::string &filename) override { return 0; } + bool isValid() const override { return m2m_ != nullptr; } - std::vector<PixelFormat> formats(PixelFormat input); - SizeRange sizes(const Size &input); + std::vector<PixelFormat> formats(PixelFormat input) override; + SizeRange sizes(const Size &input) override; std::tuple<unsigned int, unsigned int> - strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size); + strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) override; + + Size adjustInputSize(const PixelFormat &pixFmt, + const Size &size, Alignment align = Alignment::Down) override; + Size adjustOutputSize(const PixelFormat &pixFmt, + const Size &size, Alignment align = Alignment::Down) override; int configure(const StreamConfiguration &inputCfg, - const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg); - int exportBuffers(unsigned int ouput, unsigned int count, - std::vector<std::unique_ptr<FrameBuffer>> *buffers); + const std::vector<std::reference_wrapper<StreamConfiguration>> + &outputCfg) override; + bool isConfigured(const Stream *stream) const override; + int exportBuffers(const Stream *stream, unsigned int count, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; + + int start() override; + void stop() override; - int start(); - void stop(); + int validateOutput(StreamConfiguration *cfg, bool *adjusted, + Alignment align = Alignment::Down) override; int queueBuffers(FrameBuffer *input, - const std::map<unsigned int, FrameBuffer *> &outputs); + const std::map<const Stream *, FrameBuffer *> &outputs) override; + + int setInputCrop(const Stream *stream, Rectangle *rect) override; + std::pair<Rectangle, Rectangle> inputCropBounds() override { return inputCropBounds_; } + std::pair<Rectangle, Rectangle> inputCropBounds(const Stream *stream) override; private: - class Stream : protected Loggable + class V4L2M2MStream : protected Loggable { public: - Stream(V4L2M2MConverter *converter, unsigned int index); + V4L2M2MStream(V4L2M2MConverter *converter, const Stream *stream); bool isValid() const { return m2m_ != nullptr; } @@ -74,6 +90,11 @@ private: int queueBuffers(FrameBuffer *input, FrameBuffer *output); + int setInputSelection(unsigned int target, Rectangle *rect); + int getInputSelection(unsigned int target, Rectangle *rect); + + std::pair<Rectangle, Rectangle> inputCropBounds(); + protected: std::string logPrefix() const override; @@ -82,17 +103,23 @@ private: void outputBufferReady(FrameBuffer *buffer); V4L2M2MConverter *converter_; - unsigned int index_; + const Stream *stream_; std::unique_ptr<V4L2M2MDevice> m2m_; unsigned int inputBufferCount_; unsigned int outputBufferCount_; + + std::pair<Rectangle, Rectangle> inputCropBounds_; }; + Size adjustSizes(const Size &size, const std::vector<SizeRange> &ranges, + Alignment align); + std::unique_ptr<V4L2M2MDevice> m2m_; - std::vector<Stream> streams_; + std::map<const Stream *, std::unique_ptr<V4L2M2MStream>> streams_; std::map<FrameBuffer *, unsigned int> queue_; + std::pair<Rectangle, Rectangle> inputCropBounds_; }; } /* namespace libcamera */ diff --git a/include/libcamera/internal/debug_controls.h b/include/libcamera/internal/debug_controls.h new file mode 100644 index 00000000..0b049f48 --- /dev/null +++ b/include/libcamera/internal/debug_controls.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Debug metadata helpers + */ + +#pragma once + +#include <libcamera/control_ids.h> + +namespace libcamera { + +class DebugMetadata +{ +public: + DebugMetadata() = default; + + void enableByControl(const ControlList &controls); + void enable(bool enable = true); + void setParent(DebugMetadata *parent); + void moveEntries(ControlList &list); + + template<typename T, typename V> + void set(const Control<T> &ctrl, const V &value) + { + if (parent_) { + parent_->set(ctrl, value); + return; + } + + if (!enabled_) + return; + + cache_.set(ctrl, value); + } + + void set(unsigned int id, const ControlValue &value); + +private: + bool enabled_ = false; + DebugMetadata *parent_ = nullptr; + ControlList cache_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/delayed_controls.h b/include/libcamera/internal/delayed_controls.h index aef37077..e8d3014d 100644 --- a/include/libcamera/internal/delayed_controls.h +++ b/include/libcamera/internal/delayed_controls.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * delayed_controls.h - Helper to deal with controls that take effect with a delay + * Helper to deal with controls that take effect with a delay */ #pragma once diff --git a/include/libcamera/internal/device_enumerator.h b/include/libcamera/internal/device_enumerator.h index 72ec9a60..db3532a9 100644 --- a/include/libcamera/internal/device_enumerator.h +++ b/include/libcamera/internal/device_enumerator.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * device_enumerator.h - API to enumerate and find media devices + * API to enumerate and find media devices */ #pragma once diff --git a/include/libcamera/internal/device_enumerator_sysfs.h b/include/libcamera/internal/device_enumerator_sysfs.h index 3e84b83f..4ccc9845 100644 --- a/include/libcamera/internal/device_enumerator_sysfs.h +++ b/include/libcamera/internal/device_enumerator_sysfs.h @@ -2,12 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * device_enumerator_sysfs.h - sysfs-based device enumerator + * sysfs-based device enumerator */ #pragma once -#include <memory> #include <string> #include "libcamera/internal/device_enumerator.h" diff --git a/include/libcamera/internal/device_enumerator_udev.h b/include/libcamera/internal/device_enumerator_udev.h index 1b3360df..1378c190 100644 --- a/include/libcamera/internal/device_enumerator_udev.h +++ b/include/libcamera/internal/device_enumerator_udev.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018-2019, Google Inc. * - * device_enumerator_udev.h - udev-based device enumerator + * udev-based device enumerator */ #pragma once diff --git a/include/libcamera/internal/dma_buf_allocator.h b/include/libcamera/internal/dma_buf_allocator.h new file mode 100644 index 00000000..13600915 --- /dev/null +++ b/include/libcamera/internal/dma_buf_allocator.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi Ltd + * + * Helper class for dma-buf allocations. + */ + +#pragma once + +#include <memory> +#include <stdint.h> +#include <string> +#include <vector> + +#include <libcamera/base/flags.h> +#include <libcamera/base/shared_fd.h> +#include <libcamera/base/unique_fd.h> + +namespace libcamera { + +class FrameBuffer; + +class DmaBufAllocator +{ +public: + enum class DmaBufAllocatorFlag { + CmaHeap = 1 << 0, + SystemHeap = 1 << 1, + UDmaBuf = 1 << 2, + }; + + using DmaBufAllocatorFlags = Flags<DmaBufAllocatorFlag>; + + DmaBufAllocator(DmaBufAllocatorFlags flags = DmaBufAllocatorFlag::CmaHeap); + ~DmaBufAllocator(); + bool isValid() const { return providerHandle_.isValid(); } + UniqueFD alloc(const char *name, std::size_t size); + + int exportBuffers(unsigned int count, + const std::vector<unsigned int> &planeSizes, + std::vector<std::unique_ptr<FrameBuffer>> *buffers); + +private: + std::unique_ptr<FrameBuffer> createBuffer( + std::string name, const std::vector<unsigned int> &planeSizes); + + UniqueFD allocFromHeap(const char *name, std::size_t size); + UniqueFD allocFromUDmaBuf(const char *name, std::size_t size); + UniqueFD providerHandle_; + DmaBufAllocatorFlag type_; +}; + +class DmaSyncer final +{ +public: + enum class SyncType { + Read = 0, + Write, + ReadWrite, + }; + + explicit DmaSyncer(SharedFD fd, SyncType type = SyncType::ReadWrite); + + DmaSyncer(DmaSyncer &&other) = default; + DmaSyncer &operator=(DmaSyncer &&other) = default; + + ~DmaSyncer(); + +private: + LIBCAMERA_DISABLE_COPY(DmaSyncer) + + void sync(uint64_t step); + + SharedFD fd_; + uint64_t flags_ = 0; +}; + +LIBCAMERA_FLAGS_ENABLE_OPERATORS(DmaBufAllocator::DmaBufAllocatorFlag) + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/formats.h b/include/libcamera/internal/formats.h index 5b16c0a8..6a3e9c16 100644 --- a/include/libcamera/internal/formats.h +++ b/include/libcamera/internal/formats.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * formats.h - libcamera image formats + * libcamera image formats */ #pragma once #include <array> -#include <map> #include <vector> #include <libcamera/geometry.h> diff --git a/include/libcamera/internal/framebuffer.h b/include/libcamera/internal/framebuffer.h index 1f42a4fc..97b49d42 100644 --- a/include/libcamera/internal/framebuffer.h +++ b/include/libcamera/internal/framebuffer.h @@ -2,12 +2,13 @@ /* * Copyright (C) 2020, Google Inc. * - * framebuffer.h - Internal frame buffer handling + * Internal frame buffer handling */ #pragma once #include <memory> +#include <stdint.h> #include <utility> #include <libcamera/base/class.h> diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h index 085f1fed..b1fefba5 100644 --- a/include/libcamera/internal/ipa_data_serializer.h +++ b/include/libcamera/internal/ipa_data_serializer.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2020, Google Inc. * - * ipa_data_serializer.h - Image Processing Algorithm data serializer + * Image Processing Algorithm data serializer */ #pragma once -#include <deque> -#include <iostream> +#include <stdint.h> #include <string.h> #include <tuple> #include <type_traits> @@ -20,10 +19,9 @@ #include <libcamera/control_ids.h> #include <libcamera/framebuffer.h> #include <libcamera/geometry.h> + #include <libcamera/ipa/ipa_interface.h> -#include "libcamera/internal/byte_stream_buffer.h" -#include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/control_serializer.h" namespace libcamera { @@ -165,7 +163,7 @@ public: std::vector<SharedFD>::const_iterator fdIter = fdsBegin; for (uint32_t i = 0; i < vecLen; i++) { uint32_t sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd); - uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); + uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); dataIter += 8; ret[i] = IPADataSerializer<V>::deserialize(dataIter, @@ -272,7 +270,7 @@ public: std::vector<SharedFD>::const_iterator fdIter = fdsBegin; for (uint32_t i = 0; i < mapLen; i++) { uint32_t sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd); - uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); + uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); dataIter += 8; K key = IPADataSerializer<K>::deserialize(dataIter, @@ -284,7 +282,7 @@ public: dataIter += sizeofData; fdIter += sizeofFds; sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd); - sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); + sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd); dataIter += 8; const V value = IPADataSerializer<V>::deserialize(dataIter, @@ -311,7 +309,6 @@ public: serialize(const Flags<E> &data, [[maybe_unused]] ControlSerializer *cs = nullptr) { std::vector<uint8_t> dataVec; - dataVec.reserve(sizeof(Flags<E>)); appendPOD<uint32_t>(dataVec, static_cast<typename Flags<E>::Type>(data)); return { dataVec, {} }; diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index bf823563..a0d448cf 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_manager.h - Image Processing Algorithm module manager + * Image Processing Algorithm module manager */ #pragma once +#include <memory> #include <stdint.h> #include <vector> @@ -15,6 +16,7 @@ #include <libcamera/ipa/ipa_interface.h> #include <libcamera/ipa/ipa_module_info.h> +#include "libcamera/internal/camera_manager.h" #include "libcamera/internal/ipa_module.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/pub_key.h" @@ -34,11 +36,13 @@ public: uint32_t minVersion, uint32_t maxVersion) { - IPAModule *m = self_->module(pipe, minVersion, maxVersion); + CameraManager *cm = pipe->cameraManager(); + IPAManager *self = cm->_d()->ipaManager(); + IPAModule *m = self->module(pipe, minVersion, maxVersion); if (!m) return nullptr; - std::unique_ptr<T> proxy = std::make_unique<T>(m, !self_->isSignatureValid(m)); + std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m)); if (!proxy->isValid()) { LOG(IPAManager, Error) << "Failed to load proxy"; return nullptr; @@ -55,8 +59,6 @@ public: #endif private: - static IPAManager *self_; - void parseDir(const char *libDir, unsigned int maxDepth, std::vector<std::string> &files); unsigned int addDir(const char *libDir, unsigned int maxDepth = 0); @@ -66,7 +68,7 @@ private: bool isSignatureValid(IPAModule *ipa) const; - std::vector<IPAModule *> modules_; + std::vector<std::unique_ptr<IPAModule>> modules_; #if HAVE_IPA_PUBKEY static const uint8_t publicKeyData_[]; diff --git a/include/libcamera/internal/ipa_module.h b/include/libcamera/internal/ipa_module.h index 8038bdee..15f19492 100644 --- a/include/libcamera/internal/ipa_module.h +++ b/include/libcamera/internal/ipa_module.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_module.h - Image Processing Algorithm module + * Image Processing Algorithm module */ #pragma once @@ -29,7 +29,7 @@ public: bool isValid() const; const struct IPAModuleInfo &info() const; - const std::vector<uint8_t> signature() const; + const std::vector<uint8_t> &signature() const; const std::string &path() const; bool load(); diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h index 781c8b62..983bcc5f 100644 --- a/include/libcamera/internal/ipa_proxy.h +++ b/include/libcamera/internal/ipa_proxy.h @@ -2,14 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_proxy.h - Image Processing Algorithm proxy + * Image Processing Algorithm proxy */ #pragma once -#include <memory> #include <string> -#include <vector> #include <libcamera/ipa/ipa_interface.h> @@ -31,7 +29,8 @@ public: bool isValid() const { return valid_; } - std::string configurationFile(const std::string &file) const; + std::string configurationFile(const std::string &name, + const std::string &fallbackName = std::string()) const; protected: std::string resolvePath(const std::string &file) const; diff --git a/include/libcamera/internal/ipc_pipe.h b/include/libcamera/internal/ipc_pipe.h index ab5dd67c..418c4622 100644 --- a/include/libcamera/internal/ipc_pipe.h +++ b/include/libcamera/internal/ipc_pipe.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2020, Google Inc. * - * ipc_pipe.h - Image Processing Algorithm IPC module for IPA proxies + * Image Processing Algorithm IPC module for IPA proxies */ #pragma once +#include <stdint.h> #include <vector> #include <libcamera/base/shared_fd.h> diff --git a/include/libcamera/internal/ipc_pipe_unixsocket.h b/include/libcamera/internal/ipc_pipe_unixsocket.h index 004d9539..84512809 100644 --- a/include/libcamera/internal/ipc_pipe_unixsocket.h +++ b/include/libcamera/internal/ipc_pipe_unixsocket.h @@ -2,14 +2,14 @@ /* * Copyright (C) 2020, Google Inc. * - * ipc_pipe_unixsocket.h - Image Processing Algorithm IPC module using unix socket + * Image Processing Algorithm IPC module using unix socket */ #pragma once #include <map> #include <memory> -#include <vector> +#include <stdint.h> #include "libcamera/internal/ipc_pipe.h" #include "libcamera/internal/ipc_unixsocket.h" diff --git a/include/libcamera/internal/ipc_unixsocket.h b/include/libcamera/internal/ipc_unixsocket.h index 3963d182..48bb7a94 100644 --- a/include/libcamera/internal/ipc_unixsocket.h +++ b/include/libcamera/internal/ipc_unixsocket.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipc_unixsocket.h - IPC mechanism based on Unix sockets + * IPC mechanism based on Unix sockets */ #pragma once diff --git a/include/libcamera/internal/mapped_framebuffer.h b/include/libcamera/internal/mapped_framebuffer.h index fb39adbf..6aaabf50 100644 --- a/include/libcamera/internal/mapped_framebuffer.h +++ b/include/libcamera/internal/mapped_framebuffer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * mapped_framebuffer.h - Frame buffer memory mapping support + * Frame buffer memory mapping support */ #pragma once diff --git a/include/libcamera/internal/matrix.h b/include/libcamera/internal/matrix.h new file mode 100644 index 00000000..47513b99 --- /dev/null +++ b/include/libcamera/internal/matrix.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Matrix and related operations + */ +#pragma once + +#include <algorithm> +#include <sstream> +#include <type_traits> +#include <vector> + +#include <libcamera/base/log.h> +#include <libcamera/base/span.h> + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Matrix) + +#ifndef __DOXYGEN__ +template<typename T> +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim, + Span<T> scratchBuffer, Span<unsigned int> swapBuffer); +#endif /* __DOXYGEN__ */ + +template<typename T, unsigned int Rows, unsigned int Cols> +class Matrix +{ + static_assert(std::is_arithmetic_v<T>, "Matrix type must be arithmetic"); + +public: + constexpr Matrix() + { + } + + Matrix(const std::array<T, Rows * Cols> &data) + { + std::copy(data.begin(), data.end(), data_.begin()); + } + + Matrix(const Span<const T, Rows * Cols> data) + { + std::copy(data.begin(), data.end(), data_.begin()); + } + + static constexpr Matrix identity() + { + Matrix ret; + for (size_t i = 0; i < std::min(Rows, Cols); i++) + ret[i][i] = static_cast<T>(1); + return ret; + } + + ~Matrix() = default; + + const std::string toString() const + { + std::stringstream out; + + out << "Matrix { "; + for (unsigned int i = 0; i < Rows; i++) { + out << "[ "; + for (unsigned int j = 0; j < Cols; j++) { + out << (*this)[i][j]; + out << ((j + 1 < Cols) ? ", " : " "); + } + out << ((i + 1 < Rows) ? "], " : "]"); + } + out << " }"; + + return out.str(); + } + + constexpr Span<const T, Rows * Cols> data() const { return data_; } + + constexpr Span<const T, Cols> operator[](size_t i) const + { + return Span<const T, Cols>{ &data_.data()[i * Cols], Cols }; + } + + constexpr Span<T, Cols> operator[](size_t i) + { + return Span<T, Cols>{ &data_.data()[i * Cols], Cols }; + } + +#ifndef __DOXYGEN__ + template<typename U, std::enable_if_t<std::is_arithmetic_v<U>>> +#else + template<typename U> +#endif /* __DOXYGEN__ */ + Matrix<T, Rows, Cols> &operator*=(U d) + { + for (unsigned int i = 0; i < Rows * Cols; i++) + data_[i] *= d; + return *this; + } + + Matrix<T, Rows, Cols> inverse(bool *ok = nullptr) const + { + static_assert(Rows == Cols, "Matrix must be square"); + + Matrix<T, Rows, Cols> inverse; + std::array<T, Rows * Cols * 2> scratchBuffer; + std::array<unsigned int, Rows> swapBuffer; + bool res = matrixInvert(Span<const T>(data_), + Span<T>(inverse.data_), + Rows, + Span<T>(scratchBuffer), + Span<unsigned int>(swapBuffer)); + if (ok) + *ok = res; + return inverse; + } + +private: + /* + * \todo The initializer is only necessary for the constructor to be + * constexpr in C++17. Remove the initializer as soon as we are on + * C++20. + */ + std::array<T, Rows * Cols> data_ = {}; +}; + +#ifndef __DOXYGEN__ +template<typename T, typename U, unsigned int Rows, unsigned int Cols, + std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr> +#else +template<typename T, typename U, unsigned int Rows, unsigned int Cols> +#endif /* __DOXYGEN__ */ +Matrix<U, Rows, Cols> operator*(T d, const Matrix<U, Rows, Cols> &m) +{ + Matrix<U, Rows, Cols> result; + + for (unsigned int i = 0; i < Rows; i++) { + for (unsigned int j = 0; j < Cols; j++) + result[i][j] = d * m[i][j]; + } + + return result; +} + +#ifndef __DOXYGEN__ +template<typename T, typename U, unsigned int Rows, unsigned int Cols, + std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr> +#else +template<typename T, typename U, unsigned int Rows, unsigned int Cols> +#endif /* __DOXYGEN__ */ +Matrix<U, Rows, Cols> operator*(const Matrix<U, Rows, Cols> &m, T d) +{ + return d * m; +} + +template<typename T1, unsigned int R1, unsigned int C1, typename T2, unsigned int R2, unsigned int C2> +constexpr Matrix<std::common_type_t<T1, T2>, R1, C2> operator*(const Matrix<T1, R1, C1> &m1, + const Matrix<T2, R2, C2> &m2) +{ + static_assert(C1 == R2, "Matrix dimensions must match for multiplication"); + Matrix<std::common_type_t<T1, T2>, R1, C2> result; + + for (unsigned int i = 0; i < R1; i++) { + for (unsigned int j = 0; j < C2; j++) { + std::common_type_t<T1, T2> sum = 0; + + for (unsigned int k = 0; k < C1; k++) + sum += m1[i][k] * m2[k][j]; + + result[i][j] = sum; + } + } + + return result; +} + +template<typename T, unsigned int Rows, unsigned int Cols> +constexpr Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const Matrix<T, Rows, Cols> &m2) +{ + Matrix<T, Rows, Cols> result; + + for (unsigned int i = 0; i < Rows; i++) { + for (unsigned int j = 0; j < Cols; j++) + result[i][j] = m1[i][j] + m2[i][j]; + } + + return result; +} + +#ifndef __DOXYGEN__ +bool matrixValidateYaml(const YamlObject &obj, unsigned int size); +#endif /* __DOXYGEN__ */ + +#ifndef __DOXYGEN__ +template<typename T, unsigned int Rows, unsigned int Cols> +std::ostream &operator<<(std::ostream &out, const Matrix<T, Rows, Cols> &m) +{ + out << m.toString(); + return out; +} + +template<typename T, unsigned int Rows, unsigned int Cols> +struct YamlObject::Getter<Matrix<T, Rows, Cols>> { + std::optional<Matrix<T, Rows, Cols>> get(const YamlObject &obj) const + { + if (!matrixValidateYaml(obj, Rows * Cols)) + return std::nullopt; + + Matrix<T, Rows, Cols> matrix; + T *data = &matrix[0][0]; + + unsigned int i = 0; + for (const YamlObject &entry : obj.asList()) { + const auto value = entry.get<T>(); + if (!value) + return std::nullopt; + + data[i++] = *value; + } + + return matrix; + } +}; +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h index eb8cfde4..b3a48b98 100644 --- a/include/libcamera/internal/media_device.h +++ b/include/libcamera/internal/media_device.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2018, Google Inc. * - * media_device.h - Media device handler + * Media device handler */ #pragma once #include <map> -#include <sstream> #include <string> #include <vector> @@ -56,6 +55,8 @@ public: Signal<> disconnected; + std::vector<MediaEntity *> locateEntities(unsigned int function); + protected: std::string logPrefix() const override; diff --git a/include/libcamera/internal/media_object.h b/include/libcamera/internal/media_object.h index b1572968..54e2e5ce 100644 --- a/include/libcamera/internal/media_object.h +++ b/include/libcamera/internal/media_object.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * media_object.h - Media Device objects: entities, pads and links. + * Media Device objects: entities, pads and links. */ #pragma once @@ -48,6 +48,8 @@ public: unsigned int flags() const { return flags_; } int setEnabled(bool enable); + std::string toString() const; + private: LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaLink) @@ -61,6 +63,8 @@ private: unsigned int flags_; }; +std::ostream &operator<<(std::ostream &out, const MediaLink &link); + class MediaPad : public MediaObject { public: @@ -71,6 +75,8 @@ public: void addLink(MediaLink *link); + std::string toString() const; + private: LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaPad) @@ -85,6 +91,8 @@ private: std::vector<MediaLink *> links_; }; +std::ostream &operator<<(std::ostream &out, const MediaPad &pad); + class MediaEntity : public MediaObject { public: @@ -104,7 +112,7 @@ public: unsigned int deviceMinor() const { return minor_; } const std::vector<MediaPad *> &pads() const { return pads_; } - const std::vector<MediaEntity *> ancillaryEntities() const { return ancillaryEntities_; } + const std::vector<MediaEntity *> &ancillaryEntities() const { return ancillaryEntities_; } const MediaPad *getPadByIndex(unsigned int index) const; const MediaPad *getPadById(unsigned int id) const; diff --git a/include/libcamera/internal/media_pipeline.h b/include/libcamera/internal/media_pipeline.h new file mode 100644 index 00000000..a7a4b8c5 --- /dev/null +++ b/include/libcamera/internal/media_pipeline.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Media pipeline support + */ + +#pragma once + +#include <list> +#include <string> + +#include <libcamera/base/log.h> + +namespace libcamera { + +class CameraSensor; +class MediaEntity; +class MediaLink; +class MediaPad; +struct V4L2SubdeviceFormat; + +class MediaPipeline +{ +public: + int init(MediaEntity *source, std::string_view sink); + int initLinks(); + int configure(CameraSensor *sensor, V4L2SubdeviceFormat *); + +private: + struct Entity { + /* The media entity, always valid. */ + MediaEntity *entity; + /* + * Whether or not the entity is a subdev that supports the + * routing API. + */ + bool supportsRouting; + /* + * The local sink pad connected to the upstream entity, null for + * the camera sensor at the beginning of the pipeline. + */ + const MediaPad *sink; + /* + * The local source pad connected to the downstream entity, null + * for the video node at the end of the pipeline. + */ + const MediaPad *source; + /* + * The link on the source pad, to the downstream entity, null + * for the video node at the end of the pipeline. + */ + MediaLink *sourceLink; + }; + + std::list<Entity> entities_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 7f1f3440..33f318b2 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -2,13 +2,6 @@ subdir('tracepoints') -libcamera_tracepoint_header = custom_target( - 'tp_header', - input : ['tracepoints.h.in', tracepoint_files], - output : 'tracepoints.h', - command : [gen_tracepoints_header, include_build_dir, '@OUTPUT@', '@INPUT@'], -) - libcamera_internal_headers = files([ 'bayer_format.h', 'byte_stream_buffer.h', @@ -21,30 +14,48 @@ libcamera_internal_headers = files([ 'control_serializer.h', 'control_validator.h', 'converter.h', + 'debug_controls.h', 'delayed_controls.h', 'device_enumerator.h', 'device_enumerator_sysfs.h', 'device_enumerator_udev.h', + 'dma_buf_allocator.h', 'formats.h', 'framebuffer.h', + 'ipa_data_serializer.h', 'ipa_manager.h', 'ipa_module.h', 'ipa_proxy.h', + 'ipc_pipe.h', 'ipc_unixsocket.h', 'mapped_framebuffer.h', + 'matrix.h', 'media_device.h', 'media_object.h', + 'media_pipeline.h', 'pipeline_handler.h', 'process.h', 'pub_key.h', 'request.h', + 'shared_mem_object.h', 'source_paths.h', 'sysfs.h', 'v4l2_device.h', 'v4l2_pixelformat.h', 'v4l2_subdevice.h', 'v4l2_videodevice.h', + 'vector.h', 'yaml_parser.h', ]) +tracepoints_h = custom_target( + 'tp_header', + input : ['tracepoints.h.in', tracepoint_files], + output : 'tracepoints.h', + command : [gen_tracepoints, include_build_dir, '@OUTPUT@', '@INPUT@'], +) + +libcamera_internal_headers += tracepoints_h + subdir('converter') +subdir('software_isp') diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h index c96944f4..972a2fa6 100644 --- a/include/libcamera/internal/pipeline_handler.h +++ b/include/libcamera/internal/pipeline_handler.h @@ -2,26 +2,22 @@ /* * Copyright (C) 2018, Google Inc. * - * pipeline_handler.h - Pipeline handler infrastructure + * Pipeline handler infrastructure */ #pragma once #include <memory> #include <queue> -#include <set> #include <string> #include <sys/types.h> #include <vector> -#include <libcamera/base/mutex.h> #include <libcamera/base/object.h> #include <libcamera/controls.h> #include <libcamera/stream.h> -#include "libcamera/internal/ipa_proxy.h" - namespace libcamera { class Camera; @@ -45,7 +41,7 @@ public: MediaDevice *acquireMediaDevice(DeviceEnumerator *enumerator, const DeviceMatch &dm); - bool acquire(); + bool acquire(Camera *camera); void release(Camera *camera); virtual std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera, @@ -64,12 +60,16 @@ public: bool completeBuffer(Request *request, FrameBuffer *buffer); void completeRequest(Request *request); + void cancelRequest(Request *request); std::string configurationFile(const std::string &subdir, - const std::string &name) const; + const std::string &name, + bool silent = false) const; const char *name() const { return name_; } + CameraManager *cameraManager() const { return manager_; } + protected: void registerCamera(std::shared_ptr<Camera> camera); void hotplugMediaDevice(MediaDevice *media); @@ -77,6 +77,7 @@ protected: virtual int queueRequestDevice(Camera *camera, Request *request) = 0; virtual void stopDevice(Camera *camera) = 0; + virtual bool acquireDevice(Camera *camera); virtual void releaseDevice(Camera *camera); CameraManager *manager_; @@ -96,9 +97,7 @@ private: std::queue<Request *> waitingRequests_; const char *name_; - - Mutex lock_; - unsigned int useCount_ LIBCAMERA_TSA_GUARDED_BY(lock_); + unsigned int useCount_; friend class PipelineHandlerFactoryBase; }; @@ -114,6 +113,7 @@ public: const std::string &name() const { return name_; } static std::vector<PipelineHandlerFactoryBase *> &factories(); + static const PipelineHandlerFactoryBase *getFactoryByName(const std::string &name); private: static void registerType(PipelineHandlerFactoryBase *factory); @@ -140,7 +140,7 @@ public: } }; -#define REGISTER_PIPELINE_HANDLER(handler) \ -static PipelineHandlerFactory<handler> global_##handler##Factory(#handler); +#define REGISTER_PIPELINE_HANDLER(handler, name) \ + static PipelineHandlerFactory<handler> global_##handler##Factory(name); } /* namespace libcamera */ diff --git a/include/libcamera/internal/process.h b/include/libcamera/internal/process.h index 95e67e10..b1d07a5a 100644 --- a/include/libcamera/internal/process.h +++ b/include/libcamera/internal/process.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * process.h - Process object + * Process object */ #pragma once diff --git a/include/libcamera/internal/pub_key.h b/include/libcamera/internal/pub_key.h index 8653a912..c8cc04cb 100644 --- a/include/libcamera/internal/pub_key.h +++ b/include/libcamera/internal/pub_key.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * pub_key.h - Public key signature verification + * Public key signature verification */ #pragma once diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h index 3454cf5a..73e9bb5c 100644 --- a/include/libcamera/internal/request.h +++ b/include/libcamera/internal/request.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * request.h - Request class private data + * Request class private data */ #pragma once @@ -10,6 +10,8 @@ #include <chrono> #include <map> #include <memory> +#include <stdint.h> +#include <unordered_set> #include <libcamera/base/event_notifier.h> #include <libcamera/base/timer.h> diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h new file mode 100644 index 00000000..e9f1dacd --- /dev/null +++ b/include/libcamera/internal/shared_mem_object.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023 Raspberry Pi Ltd + * Copyright (C) 2024 Andrei Konovalov + * Copyright (C) 2024 Dennis Bonke + * + * Helpers for shared memory allocations + */ +#pragma once + +#include <stdint.h> +#include <string> +#include <sys/mman.h> +#include <type_traits> +#include <utility> + +#include <libcamera/base/class.h> +#include <libcamera/base/shared_fd.h> +#include <libcamera/base/span.h> + +namespace libcamera { + +class SharedMem +{ +public: + SharedMem(); + + SharedMem(const std::string &name, std::size_t size); + SharedMem(SharedMem &&rhs); + + virtual ~SharedMem(); + + SharedMem &operator=(SharedMem &&rhs); + + const SharedFD &fd() const + { + return fd_; + } + + Span<uint8_t> mem() const + { + return mem_; + } + + explicit operator bool() const + { + return !mem_.empty(); + } + +private: + LIBCAMERA_DISABLE_COPY(SharedMem) + + SharedFD fd_; + + Span<uint8_t> mem_; +}; + +template<class T, typename = std::enable_if_t<std::is_standard_layout<T>::value>> +class SharedMemObject : public SharedMem +{ +public: + static constexpr std::size_t kSize = sizeof(T); + + SharedMemObject() + : SharedMem(), obj_(nullptr) + { + } + + template<class... Args> + SharedMemObject(const std::string &name, Args &&...args) + : SharedMem(name, kSize), obj_(nullptr) + { + if (mem().empty()) + return; + + obj_ = new (mem().data()) T(std::forward<Args>(args)...); + } + + SharedMemObject(SharedMemObject<T> &&rhs) + : SharedMem(std::move(rhs)) + { + this->obj_ = rhs.obj_; + rhs.obj_ = nullptr; + } + + ~SharedMemObject() + { + if (obj_) + obj_->~T(); + } + + SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs) + { + SharedMem::operator=(std::move(rhs)); + this->obj_ = rhs.obj_; + rhs.obj_ = nullptr; + return *this; + } + + T *operator->() + { + return obj_; + } + + const T *operator->() const + { + return obj_; + } + + T &operator*() + { + return *obj_; + } + + const T &operator*() const + { + return *obj_; + } + +private: + LIBCAMERA_DISABLE_COPY(SharedMemObject) + + T *obj_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h new file mode 100644 index 00000000..217cd5d9 --- /dev/null +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023-2025 Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * DebayerParams header + */ + +#pragma once + +#include <array> +#include <stdint.h> + +namespace libcamera { + +struct DebayerParams { + static constexpr unsigned int kRGBLookupSize = 256; + + struct CcmColumn { + int16_t r; + int16_t g; + int16_t b; + }; + + using LookupTable = std::array<uint8_t, kRGBLookupSize>; + using CcmLookupTable = std::array<CcmColumn, kRGBLookupSize>; + + /* + * Color lookup tables when CCM is not used. + * + * Each color of a debayered pixel is amended by the corresponding + * value in the given table. + */ + LookupTable red; + LookupTable green; + LookupTable blue; + + /* + * Color and gamma lookup tables when CCM is used. + * + * Each of the CcmLookupTable's corresponds to a CCM column; together they + * make a complete 3x3 CCM lookup table. The CCM is applied on debayered + * pixels and then the gamma lookup table is used to set the resulting + * values of all the three colors. + */ + CcmLookupTable redCcm; + CcmLookupTable greenCcm; + CcmLookupTable blueCcm; + LookupTable gammaLut; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build new file mode 100644 index 00000000..508ddddc --- /dev/null +++ b/include/libcamera/internal/software_isp/meson.build @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_headers += files([ + 'debayer_params.h', + 'software_isp.h', + 'swisp_stats.h', +]) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h new file mode 100644 index 00000000..78624659 --- /dev/null +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * Simple software ISP implementation + */ + +#pragma once + +#include <deque> +#include <functional> +#include <initializer_list> +#include <map> +#include <memory> +#include <stdint.h> +#include <string> +#include <tuple> +#include <vector> + +#include <libcamera/base/class.h> +#include <libcamera/base/log.h> +#include <libcamera/base/object.h> +#include <libcamera/base/signal.h> +#include <libcamera/base/thread.h> + +#include <libcamera/geometry.h> +#include <libcamera/pixel_format.h> + +#include <libcamera/ipa/soft_ipa_interface.h> +#include <libcamera/ipa/soft_ipa_proxy.h> + +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/shared_mem_object.h" +#include "libcamera/internal/software_isp/debayer_params.h" + +namespace libcamera { + +class DebayerCpu; +class FrameBuffer; +class PixelFormat; +class Stream; +struct StreamConfiguration; + +LOG_DECLARE_CATEGORY(SoftwareIsp) + +class SoftwareIsp : public Object +{ +public: + SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, + ControlInfoMap *ipaControls); + ~SoftwareIsp(); + + int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } + + bool isValid() const; + + std::vector<PixelFormat> formats(PixelFormat input); + + SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); + + std::tuple<unsigned int, unsigned int> + strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); + + int configure(const StreamConfiguration &inputCfg, + const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, + const ipa::soft::IPAConfigInfo &configInfo); + + int exportBuffers(const Stream *stream, unsigned int count, + std::vector<std::unique_ptr<FrameBuffer>> *buffers); + + void processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls); + + int start(); + void stop(); + + void queueRequest(const uint32_t frame, const ControlList &controls); + int queueBuffers(uint32_t frame, FrameBuffer *input, + const std::map<const Stream *, FrameBuffer *> &outputs); + + void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output); + + Signal<FrameBuffer *> inputBufferReady; + Signal<FrameBuffer *> outputBufferReady; + Signal<uint32_t, uint32_t> ispStatsReady; + Signal<uint32_t, const ControlList &> metadataReady; + Signal<const ControlList &> setSensorControls; + +private: + void saveIspParams(); + void setSensorCtrls(const ControlList &sensorControls); + void statsReady(uint32_t frame, uint32_t bufferId); + void inputReady(FrameBuffer *input); + void outputReady(FrameBuffer *output); + + std::unique_ptr<DebayerCpu> debayer_; + Thread ispWorkerThread_; + SharedMemObject<DebayerParams> sharedParams_; + DebayerParams debayerParams_; + DmaBufAllocator dmaHeap_; + bool ccmEnabled_; + + std::unique_ptr<ipa::soft::IPAProxySoft> ipa_; + std::deque<FrameBuffer *> queuedInputBuffers_; + std::deque<FrameBuffer *> queuedOutputBuffers_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h new file mode 100644 index 00000000..ae11f112 --- /dev/null +++ b/include/libcamera/internal/software_isp/swisp_stats.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * Statistics data format used by the software ISP and software IPA + */ + +#pragma once + +#include <array> +#include <stdint.h> + +namespace libcamera { + +/** + * \brief Struct that holds the statistics for the Software ISP + * + * The struct value types are large enough to not overflow. + * Should they still overflow for some reason, no check is performed and they + * wrap around. + */ +struct SwIspStats { + /** + * \brief Holds the sum of all sampled red pixels + */ + uint64_t sumR_; + /** + * \brief Holds the sum of all sampled green pixels + */ + uint64_t sumG_; + /** + * \brief Holds the sum of all sampled blue pixels + */ + uint64_t sumB_; + /** + * \brief Number of bins in the yHistogram + */ + static constexpr unsigned int kYHistogramSize = 64; + /** + * \brief Type of the histogram. + */ + using Histogram = std::array<uint32_t, kYHistogramSize>; + /** + * \brief A histogram of luminance values + */ + Histogram yHistogram; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/source_paths.h b/include/libcamera/internal/source_paths.h index be6f153b..14e64717 100644 --- a/include/libcamera/internal/source_paths.h +++ b/include/libcamera/internal/source_paths.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * source_paths.h - Identify libcamera source and build paths + * Identify libcamera source and build paths */ #pragma once diff --git a/include/libcamera/internal/sysfs.h b/include/libcamera/internal/sysfs.h index 917457be..aca60fb6 100644 --- a/include/libcamera/internal/sysfs.h +++ b/include/libcamera/internal/sysfs.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * sysfs.h - Miscellaneous utility functions to access sysfs + * Miscellaneous utility functions to access sysfs */ #pragma once diff --git a/include/libcamera/internal/tracepoints.h.in b/include/libcamera/internal/tracepoints.h.in index d0fc1365..385f9f54 100644 --- a/include/libcamera/internal/tracepoints.h.in +++ b/include/libcamera/internal/tracepoints.h.in @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) {{year}}, Google Inc. + * Copyright (C) 2020, Google Inc. * - * tracepoints.h - Tracepoints with lttng + * Tracepoints with lttng * * This file is auto-generated. Do not edit. */ diff --git a/include/libcamera/internal/tracepoints/request.tp b/include/libcamera/internal/tracepoints/request.tp index 4f367e91..42c59685 100644 --- a/include/libcamera/internal/tracepoints/request.tp +++ b/include/libcamera/internal/tracepoints/request.tp @@ -5,6 +5,8 @@ * request.tp - Tracepoints for the request object */ +#include <stdint.h> + #include <libcamera/framebuffer.h> #include "libcamera/internal/request.h" diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 50d4adbc..a647c96a 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_device.h - Common base for V4L2 video devices and subdevices + * Common base for V4L2 video devices and subdevices */ #pragma once @@ -10,6 +10,7 @@ #include <map> #include <memory> #include <optional> +#include <stdint.h> #include <vector> #include <linux/videodev2.h> @@ -44,6 +45,7 @@ public: const std::string &deviceNode() const { return deviceNode_; } std::string devicePath() const; + bool supportsFrameStartEvent(); int setFrameStartEnabled(bool enable); Signal<uint32_t> frameStart; diff --git a/include/libcamera/internal/v4l2_pixelformat.h b/include/libcamera/internal/v4l2_pixelformat.h index 44439fff..543eb21b 100644 --- a/include/libcamera/internal/v4l2_pixelformat.h +++ b/include/libcamera/internal/v4l2_pixelformat.h @@ -3,7 +3,7 @@ * Copyright (C) 2019, Google Inc. * Copyright (C) 2020, Raspberry Pi Ltd * - * v4l2_pixelformat.h - V4L2 Pixel Format + * V4L2 Pixel Format */ #pragma once @@ -49,6 +49,8 @@ public: static const std::vector<V4L2PixelFormat> & fromPixelFormat(const PixelFormat &pixelFormat); + bool isGenericLineBasedMetadata() const; + private: uint32_t fourcc_; }; diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h index 69862de0..fa2a4a21 100644 --- a/include/libcamera/internal/v4l2_subdevice.h +++ b/include/libcamera/internal/v4l2_subdevice.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice.h - V4L2 Subdevice + * V4L2 Subdevice */ #pragma once @@ -10,6 +10,7 @@ #include <memory> #include <optional> #include <ostream> +#include <stdint.h> #include <string> #include <vector> @@ -29,6 +30,26 @@ namespace libcamera { class MediaDevice; +class MediaBusFormatInfo +{ +public: + enum class Type { + Image, + Metadata, + EmbeddedData, + }; + + bool isValid() const { return code != 0; } + + static const MediaBusFormatInfo &info(uint32_t code); + + const char *name; + uint32_t code; + Type type; + unsigned int bitsPerPixel; + PixelFormatInfo::ColourEncoding colourEncoding; +}; + struct V4L2SubdeviceCapability final : v4l2_subdev_capability { bool isReadOnly() const { @@ -36,17 +57,16 @@ struct V4L2SubdeviceCapability final : v4l2_subdev_capability { } bool hasStreams() const { - return capabilities & V4L2_SUBDEV_CAP_MPLEXED; + return capabilities & V4L2_SUBDEV_CAP_STREAMS; } }; struct V4L2SubdeviceFormat { - uint32_t mbus_code; + uint32_t code; Size size; std::optional<ColorSpace> colorSpace; const std::string toString() const; - uint8_t bitsPerPixel() const; }; std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f); @@ -61,12 +81,39 @@ public: ActiveFormat = V4L2_SUBDEV_FORMAT_ACTIVE, }; - class Routing : public std::vector<struct v4l2_subdev_route> - { - public: - std::string toString() const; + struct Stream { + Stream() + : pad(0), stream(0) + { + } + + Stream(unsigned int p, unsigned int s) + : pad(p), stream(s) + { + } + + unsigned int pad; + unsigned int stream; + }; + + struct Route { + Route() + : flags(0) + { + } + + Route(const Stream &snk, const Stream &src, uint32_t f) + : sink(snk), source(src), flags(f) + { + } + + Stream sink; + Stream source; + uint32_t flags; }; + using Routing = std::vector<Route>; + explicit V4L2Subdevice(const MediaEntity *entity); ~V4L2Subdevice(); @@ -74,17 +121,39 @@ public: const MediaEntity *entity() const { return entity_; } - int getSelection(unsigned int pad, unsigned int target, + int getSelection(const Stream &stream, unsigned int target, Rectangle *rect); - int setSelection(unsigned int pad, unsigned int target, + int getSelection(unsigned int pad, unsigned int target, Rectangle *rect) + { + return getSelection({ pad, 0 }, target, rect); + } + int setSelection(const Stream &stream, unsigned int target, Rectangle *rect); + int setSelection(unsigned int pad, unsigned int target, Rectangle *rect) + { + return setSelection({ pad, 0 }, target, rect); + } - Formats formats(unsigned int pad); + Formats formats(const Stream &stream); + Formats formats(unsigned int pad) + { + return formats({ pad, 0 }); + } + int getFormat(const Stream &stream, V4L2SubdeviceFormat *format, + Whence whence = ActiveFormat); int getFormat(unsigned int pad, V4L2SubdeviceFormat *format, + Whence whence = ActiveFormat) + { + return getFormat({ pad, 0 }, format, whence); + } + int setFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat); int setFormat(unsigned int pad, V4L2SubdeviceFormat *format, - Whence whence = ActiveFormat); + Whence whence = ActiveFormat) + { + return setFormat({ pad, 0 }, format, whence); + } int getRouting(Routing *routing, Whence whence = ActiveFormat); int setRouting(Routing *routing, Whence whence = ActiveFormat); @@ -104,14 +173,28 @@ private: std::optional<ColorSpace> toColorSpace(const v4l2_mbus_framefmt &format) const; - std::vector<unsigned int> enumPadCodes(unsigned int pad); - std::vector<SizeRange> enumPadSizes(unsigned int pad, + std::vector<unsigned int> enumPadCodes(const Stream &stream); + std::vector<SizeRange> enumPadSizes(const Stream &stream, unsigned int code); + int getRoutingLegacy(Routing *routing, Whence whence); + int setRoutingLegacy(Routing *routing, Whence whence); + const MediaEntity *entity_; std::string model_; struct V4L2SubdeviceCapability caps_; }; +bool operator==(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs); +static inline bool operator!=(const V4L2Subdevice::Stream &lhs, + const V4L2Subdevice::Stream &rhs) +{ + return !(lhs == rhs); +} + +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Stream &stream); +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route); +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing); + } /* namespace libcamera */ diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h index d157a447..ae6a76cb 100644 --- a/include/libcamera/internal/v4l2_videodevice.h +++ b/include/libcamera/internal/v4l2_videodevice.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_videodevice.h - V4L2 Video Device + * V4L2 Video Device */ #pragma once #include <array> -#include <atomic> #include <memory> #include <optional> #include <ostream> @@ -158,7 +157,7 @@ private: std::vector<Plane> planes_; }; - std::atomic<uint64_t> lastUsedCounter_; + uint64_t lastUsedCounter_; std::vector<Entry> cache_; /* \todo Expose the miss counter through an instrumentation API. */ unsigned int missCounter_; @@ -208,6 +207,7 @@ public: int setFormat(V4L2DeviceFormat *format); Formats formats(uint32_t code = 0); + int getSelection(unsigned int target, Rectangle *rect); int setSelection(unsigned int target, Rectangle *rect); int allocateBuffers(unsigned int count, diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h new file mode 100644 index 00000000..16b6aef0 --- /dev/null +++ b/include/libcamera/internal/vector.h @@ -0,0 +1,371 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Vector and related operations + */ +#pragma once + +#include <algorithm> +#include <array> +#include <cmath> +#include <functional> +#include <numeric> +#include <optional> +#include <ostream> +#include <type_traits> + +#include <libcamera/base/log.h> +#include <libcamera/base/span.h> + +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Vector) + +#ifndef __DOXYGEN__ +template<typename T, unsigned int Rows, + std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr> +#else +template<typename T, unsigned int Rows> +#endif /* __DOXYGEN__ */ +class Vector +{ +public: + constexpr Vector() = default; + + constexpr explicit Vector(T scalar) + { + data_.fill(scalar); + } + + constexpr Vector(const std::array<T, Rows> &data) + { + std::copy(data.begin(), data.end(), data_.begin()); + } + + constexpr Vector(const Span<const T, Rows> data) + { + std::copy(data.begin(), data.end(), data_.begin()); + } + + const T &operator[](size_t i) const + { + ASSERT(i < data_.size()); + return data_[i]; + } + + T &operator[](size_t i) + { + ASSERT(i < data_.size()); + return data_[i]; + } + + constexpr Vector<T, Rows> operator-() const + { + Vector<T, Rows> ret; + for (unsigned int i = 0; i < Rows; i++) + ret[i] = -data_[i]; + return ret; + } + + constexpr Vector operator+(const Vector &other) const + { + return apply(*this, other, std::plus<>{}); + } + + constexpr Vector operator+(T scalar) const + { + return apply(*this, scalar, std::plus<>{}); + } + + constexpr Vector operator-(const Vector &other) const + { + return apply(*this, other, std::minus<>{}); + } + + constexpr Vector operator-(T scalar) const + { + return apply(*this, scalar, std::minus<>{}); + } + + constexpr Vector operator*(const Vector &other) const + { + return apply(*this, other, std::multiplies<>{}); + } + + constexpr Vector operator*(T scalar) const + { + return apply(*this, scalar, std::multiplies<>{}); + } + + constexpr Vector operator/(const Vector &other) const + { + return apply(*this, other, std::divides<>{}); + } + + constexpr Vector operator/(T scalar) const + { + return apply(*this, scalar, std::divides<>{}); + } + + Vector &operator+=(const Vector &other) + { + return apply(other, [](T a, T b) { return a + b; }); + } + + Vector &operator+=(T scalar) + { + return apply(scalar, [](T a, T b) { return a + b; }); + } + + Vector &operator-=(const Vector &other) + { + return apply(other, [](T a, T b) { return a - b; }); + } + + Vector &operator-=(T scalar) + { + return apply(scalar, [](T a, T b) { return a - b; }); + } + + Vector &operator*=(const Vector &other) + { + return apply(other, [](T a, T b) { return a * b; }); + } + + Vector &operator*=(T scalar) + { + return apply(scalar, [](T a, T b) { return a * b; }); + } + + Vector &operator/=(const Vector &other) + { + return apply(other, [](T a, T b) { return a / b; }); + } + + Vector &operator/=(T scalar) + { + return apply(scalar, [](T a, T b) { return a / b; }); + } + + constexpr Vector min(const Vector &other) const + { + return apply(*this, other, [](T a, T b) { return std::min(a, b); }); + } + + constexpr Vector min(T scalar) const + { + return apply(*this, scalar, [](T a, T b) { return std::min(a, b); }); + } + + constexpr Vector max(const Vector &other) const + { + return apply(*this, other, [](T a, T b) { return std::max(a, b); }); + } + + constexpr Vector max(T scalar) const + { + return apply(*this, scalar, [](T a, T b) -> T { return std::max(a, b); }); + } + + constexpr T dot(const Vector<T, Rows> &other) const + { + T ret = 0; + for (unsigned int i = 0; i < Rows; i++) + ret += data_[i] * other[i]; + return ret; + } + +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>> +#endif /* __DOXYGEN__ */ + constexpr const T &x() const { return data_[0]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>> +#endif /* __DOXYGEN__ */ + constexpr const T &y() const { return data_[1]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>> +#endif /* __DOXYGEN__ */ + constexpr const T &z() const { return data_[2]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>> +#endif /* __DOXYGEN__ */ + constexpr T &x() { return data_[0]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>> +#endif /* __DOXYGEN__ */ + constexpr T &y() { return data_[1]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>> +#endif /* __DOXYGEN__ */ + constexpr T &z() { return data_[2]; } + +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>> +#endif /* __DOXYGEN__ */ + constexpr const T &r() const { return data_[0]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>> +#endif /* __DOXYGEN__ */ + constexpr const T &g() const { return data_[1]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>> +#endif /* __DOXYGEN__ */ + constexpr const T &b() const { return data_[2]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>> +#endif /* __DOXYGEN__ */ + constexpr T &r() { return data_[0]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>> +#endif /* __DOXYGEN__ */ + constexpr T &g() { return data_[1]; } +#ifndef __DOXYGEN__ + template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>> +#endif /* __DOXYGEN__ */ + constexpr T &b() { return data_[2]; } + + constexpr double length2() const + { + double ret = 0; + for (unsigned int i = 0; i < Rows; i++) + ret += data_[i] * data_[i]; + return ret; + } + + constexpr double length() const + { + return std::sqrt(length2()); + } + + template<typename R = T> + constexpr R sum() const + { + return std::accumulate(data_.begin(), data_.end(), R{}); + } + +private: + template<class BinaryOp> + static constexpr Vector apply(const Vector &lhs, const Vector &rhs, BinaryOp op) + { + Vector result; + std::transform(lhs.data_.begin(), lhs.data_.end(), + rhs.data_.begin(), result.data_.begin(), + op); + + return result; + } + + template<class BinaryOp> + static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op) + { + Vector result; + std::transform(lhs.data_.begin(), lhs.data_.end(), + result.data_.begin(), + [&op, rhs](T v) { return op(v, rhs); }); + + return result; + } + + template<class BinaryOp> + Vector &apply(const Vector &other, BinaryOp op) + { + auto itOther = other.data_.begin(); + std::for_each(data_.begin(), data_.end(), + [&op, &itOther](T &v) { v = op(v, *itOther++); }); + + return *this; + } + + template<class BinaryOp> + Vector &apply(T scalar, BinaryOp op) + { + std::for_each(data_.begin(), data_.end(), + [&op, scalar](T &v) { v = op(v, scalar); }); + + return *this; + } + + std::array<T, Rows> data_; +}; + +template<typename T> +using RGB = Vector<T, 3>; + +template<typename T, typename U, unsigned int Rows, unsigned int Cols> +Vector<std::common_type_t<T, U>, Rows> operator*(const Matrix<T, Rows, Cols> &m, const Vector<U, Cols> &v) +{ + Vector<std::common_type_t<T, U>, Rows> result; + + for (unsigned int i = 0; i < Rows; i++) { + std::common_type_t<T, U> sum = 0; + for (unsigned int j = 0; j < Cols; j++) + sum += m[i][j] * v[j]; + result[i] = sum; + } + + return result; +} + +template<typename T, unsigned int Rows> +bool operator==(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs) +{ + for (unsigned int i = 0; i < Rows; i++) { + if (lhs[i] != rhs[i]) + return false; + } + + return true; +} + +template<typename T, unsigned int Rows> +bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs) +{ + return !(lhs == rhs); +} + +#ifndef __DOXYGEN__ +bool vectorValidateYaml(const YamlObject &obj, unsigned int size); +#endif /* __DOXYGEN__ */ + +#ifndef __DOXYGEN__ +template<typename T, unsigned int Rows> +std::ostream &operator<<(std::ostream &out, const Vector<T, Rows> &v) +{ + out << "Vector { "; + for (unsigned int i = 0; i < Rows; i++) { + out << v[i]; + out << ((i + 1 < Rows) ? ", " : " "); + } + out << " }"; + + return out; +} + +template<typename T, unsigned int Rows> +struct YamlObject::Getter<Vector<T, Rows>> { + std::optional<Vector<T, Rows>> get(const YamlObject &obj) const + { + if (!vectorValidateYaml(obj, Rows)) + return std::nullopt; + + Vector<T, Rows> vector; + + unsigned int i = 0; + for (const YamlObject &entry : obj.asList()) { + const auto value = entry.get<T>(); + if (!value) + return std::nullopt; + vector[i++] = *value; + } + + return vector; + } +}; +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h index 8ca71df8..8c791656 100644 --- a/include/libcamera/internal/yaml_parser.h +++ b/include/libcamera/internal/yaml_parser.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * yaml_parser.h - libcamera YAML parsing helper + * libcamera YAML parsing helper */ #pragma once @@ -10,7 +10,9 @@ #include <iterator> #include <map> #include <optional> +#include <stdint.h> #include <string> +#include <string_view> #include <vector> #include <libcamera/base/class.h> @@ -158,37 +160,34 @@ public: { return type_ == Type::Dictionary; } + bool isEmpty() const + { + return type_ == Type::Empty; + } + explicit operator bool() const + { + return type_ != Type::Empty; + } std::size_t size() const; -#ifndef __DOXYGEN__ - template<typename T, - std::enable_if_t< - std::is_same_v<bool, T> || - std::is_same_v<double, T> || - std::is_same_v<int8_t, T> || - std::is_same_v<uint8_t, T> || - std::is_same_v<int16_t, T> || - std::is_same_v<uint16_t, T> || - std::is_same_v<int32_t, T> || - std::is_same_v<uint32_t, T> || - std::is_same_v<std::string, T> || - std::is_same_v<Size, T>> * = nullptr> -#else template<typename T> -#endif - std::optional<T> get() const; + std::optional<T> get() const + { + return Getter<T>{}.get(*this); + } - template<typename T> - T get(const T &defaultValue) const + template<typename T, typename U> + T get(U &&defaultValue) const { - return get<T>().value_or(defaultValue); + return get<T>().value_or(std::forward<U>(defaultValue)); } #ifndef __DOXYGEN__ template<typename T, std::enable_if_t< std::is_same_v<bool, T> || + std::is_same_v<float, T> || std::is_same_v<double, T> || std::is_same_v<int8_t, T> || std::is_same_v<uint8_t, T> || @@ -208,25 +207,33 @@ public: const YamlObject &operator[](std::size_t index) const; - bool contains(const std::string &key) const; - const YamlObject &operator[](const std::string &key) const; + bool contains(std::string_view key) const; + const YamlObject &operator[](std::string_view key) const; private: LIBCAMERA_DISABLE_COPY_AND_MOVE(YamlObject) + template<typename T> + friend struct Getter; friend class YamlParserContext; enum class Type { Dictionary, List, Value, + Empty, + }; + + template<typename T, typename Enable = void> + struct Getter { + std::optional<T> get(const YamlObject &obj) const; }; Type type_; std::string value_; Container list_; - std::map<std::string, YamlObject *> dictionary_; + std::map<std::string, YamlObject *, std::less<>> dictionary_; }; class YamlParser final diff --git a/include/libcamera/ipa/ipa_controls.h b/include/libcamera/ipa/ipa_controls.h index e5da1946..980668c8 100644 --- a/include/libcamera/ipa/ipa_controls.h +++ b/include/libcamera/ipa/ipa_controls.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_controls.h - IPA Control handling + * IPA Control handling */ #pragma once @@ -46,7 +46,8 @@ struct ipa_control_info_entry { uint32_t id; uint32_t type; uint32_t offset; - uint32_t padding[1]; + uint8_t direction; + uint8_t padding[3]; }; #ifdef __cplusplus diff --git a/include/libcamera/ipa/ipa_interface.h b/include/libcamera/ipa/ipa_interface.h index 8884f0ed..dce9637a 100644 --- a/include/libcamera/ipa/ipa_interface.h +++ b/include/libcamera/ipa/ipa_interface.h @@ -2,24 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_interface.h - Image Processing Algorithm interface + * Image Processing Algorithm interface */ #pragma once -#include <stddef.h> -#include <stdint.h> - -#include <map> -#include <vector> - -#include <libcamera/base/flags.h> -#include <libcamera/base/signal.h> - -#include <libcamera/controls.h> -#include <libcamera/framebuffer.h> -#include <libcamera/geometry.h> - namespace libcamera { /* @@ -33,8 +20,8 @@ public: virtual ~IPAInterface() = default; }; -} /* namespace libcamera */ - extern "C" { libcamera::IPAInterface *ipaCreate(); } + +} /* namespace libcamera */ diff --git a/include/libcamera/ipa/ipa_module_info.h b/include/libcamera/ipa/ipa_module_info.h index b19b00f7..3507a6d7 100644 --- a/include/libcamera/ipa/ipa_module_info.h +++ b/include/libcamera/ipa/ipa_module_info.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_module_info.h - Image Processing Algorithm module information + * Image Processing Algorithm module information */ #pragma once diff --git a/include/libcamera/ipa/ipu3.mojom b/include/libcamera/ipa/ipu3.mojom index d1b1c6b8..d9a50b01 100644 --- a/include/libcamera/ipa/ipu3.mojom +++ b/include/libcamera/ipa/ipu3.mojom @@ -31,14 +31,14 @@ interface IPAIPU3Interface { unmapBuffers(array<uint32> ids); [async] queueRequest(uint32 frame, libcamera.ControlList controls); - [async] fillParamsBuffer(uint32 frame, uint32 bufferId); - [async] processStatsBuffer(uint32 frame, int64 frameTimestamp, - uint32 bufferId, libcamera.ControlList sensorControls); + [async] computeParams(uint32 frame, uint32 bufferId); + [async] processStats(uint32 frame, int64 frameTimestamp, + uint32 bufferId, libcamera.ControlList sensorControls); }; interface IPAIPU3EventInterface { setSensorControls(uint32 frame, libcamera.ControlList sensorControls, libcamera.ControlList lensControls); - paramsBufferReady(uint32 frame); + paramsComputed(uint32 frame); metadataReady(uint32 frame, libcamera.ControlList metadata); }; diff --git a/include/libcamera/ipa/mali-c55.mojom b/include/libcamera/ipa/mali-c55.mojom new file mode 100644 index 00000000..5d7eb4ee --- /dev/null +++ b/include/libcamera/ipa/mali-c55.mojom @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +module ipa.mali_c55; + +import "include/libcamera/ipa/core.mojom"; + +struct IPAConfigInfo { + libcamera.IPACameraSensorInfo sensorInfo; + libcamera.ControlInfoMap sensorControls; +}; + +interface IPAMaliC55Interface { + init(libcamera.IPASettings settings, IPAConfigInfo configInfo) + => (int32 ret, libcamera.ControlInfoMap ipaControls); + start() => (int32 ret); + stop(); + + configure(IPAConfigInfo configInfo, uint8 bayerOrder) + => (int32 ret, libcamera.ControlInfoMap ipaControls); + + mapBuffers(array<libcamera.IPABuffer> buffers, bool readOnly); + unmapBuffers(array<libcamera.IPABuffer> buffers); + + [async] queueRequest(uint32 request, libcamera.ControlList reqControls); + [async] fillParams(uint32 request, uint32 bufferId); + [async] processStats(uint32 request, uint32 bufferId, + libcamera.ControlList sensorControls); +}; + +interface IPAMaliC55EventInterface { + paramsComputed(uint32 request); + statsProcessed(uint32 request, libcamera.ControlList metadata); + setSensorControls(libcamera.ControlList sensorControls); +}; diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build index f3b4881c..3ee3ada3 100644 --- a/include/libcamera/ipa/meson.build +++ b/include/libcamera/ipa/meson.build @@ -11,8 +11,6 @@ libcamera_ipa_headers = files([ install_headers(libcamera_ipa_headers, subdir : libcamera_ipa_include_dir) -libcamera_generated_ipa_headers = [] - ipa_headers_install_dir = get_option('includedir') / libcamera_ipa_include_dir # @@ -28,10 +26,11 @@ ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module', '--output-root', meson.project_build_root(), '--input-root', meson.project_source_root(), '--mojoms', '@INPUT@' - ]) + ], + env : py_build_env) # core_ipa_interface.h -libcamera_generated_ipa_headers += custom_target('core_ipa_interface_h', +libcamera_ipa_headers += custom_target('core_ipa_interface_h', input : ipa_mojom_core, output : 'core_ipa_interface.h', depends : mojom_templates, @@ -44,10 +43,11 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_interface_h', '--libcamera_generate_core_header', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) # core_ipa_serializer.h -libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h', +libcamera_ipa_headers += custom_target('core_ipa_serializer_h', input : ipa_mojom_core, output : 'core_ipa_serializer.h', depends : mojom_templates, @@ -58,13 +58,17 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h', '--libcamera_generate_core_serializer', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) # Mapping from pipeline handler name to mojom file pipeline_ipa_mojom_mapping = { 'ipu3': 'ipu3.mojom', + 'mali-c55': 'mali-c55.mojom', 'rkisp1': 'rkisp1.mojom', + 'rpi/pisp': 'raspberrypi.mojom', 'rpi/vc4': 'raspberrypi.mojom', + 'simple': 'soft.mojom', 'vimc': 'vimc.mojom', } @@ -100,7 +104,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping '--output-root', meson.project_build_root(), '--input-root', meson.project_source_root(), '--mojoms', '@INPUT@' - ]) + ], + env : py_build_env) # {interface}_ipa_interface.h header = custom_target(name + '_ipa_interface_h', @@ -116,7 +121,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping '--libcamera_generate_header', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) # {interface}_ipa_serializer.h serializer = custom_target(name + '_ipa_serializer_h', @@ -130,7 +136,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping '--libcamera_generate_serializer', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) # {interface}_ipa_proxy.h proxy_header = custom_target(name + '_proxy_h', @@ -144,14 +151,15 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping '--libcamera_generate_proxy_h', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) ipa_mojoms += { 'name': name, 'mojom': mojom, } - libcamera_generated_ipa_headers += [header, serializer, proxy_header] + libcamera_ipa_headers += [header, serializer, proxy_header] endforeach ipa_mojom_files = [] diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom index 5986c436..e30c70bd 100644 --- a/include/libcamera/ipa/raspberrypi.mojom +++ b/include/libcamera/ipa/raspberrypi.mojom @@ -12,10 +12,6 @@ import "include/libcamera/ipa/core.mojom"; const uint32 MaxLsGridSize = 0x8000; struct SensorConfig { - uint32 gainDelay; - uint32 exposureDelay; - uint32 vblankDelay; - uint32 hblankDelay; uint32 sensorMetadata; }; @@ -272,7 +268,7 @@ interface IPARPiEventInterface { * \param[in] delayContext IPA context index used for this request * * This asynchronous event is signalled to the pipeline handler when - * the IPA requires sensor specific controls (e.g. shutter speed, gain, + * the IPA requires sensor specific controls (e.g. exposure time, gain, * blanking) to be applied. */ setDelayedControls(libcamera.ControlList controls, uint32 delayContext); diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom index 1009e970..043ad27e 100644 --- a/include/libcamera/ipa/rkisp1.mojom +++ b/include/libcamera/ipa/rkisp1.mojom @@ -11,6 +11,7 @@ import "include/libcamera/ipa/core.mojom"; struct IPAConfigInfo { libcamera.IPACameraSensorInfo sensorInfo; libcamera.ControlInfoMap sensorControls; + uint32 paramFormat; }; interface IPARkISP1Interface { @@ -30,13 +31,13 @@ interface IPARkISP1Interface { unmapBuffers(array<uint32> ids); [async] queueRequest(uint32 frame, libcamera.ControlList reqControls); - [async] fillParamsBuffer(uint32 frame, uint32 bufferId); - [async] processStatsBuffer(uint32 frame, uint32 bufferId, - libcamera.ControlList sensorControls); + [async] computeParams(uint32 frame, uint32 bufferId); + [async] processStats(uint32 frame, uint32 bufferId, + libcamera.ControlList sensorControls); }; interface IPARkISP1EventInterface { - paramsBufferReady(uint32 frame); + paramsComputed(uint32 frame, uint32 bytesused); setSensorControls(uint32 frame, libcamera.ControlList sensorControls); metadataReady(uint32 frame, libcamera.ControlList metadata); }; diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom new file mode 100644 index 00000000..77328c5f --- /dev/null +++ b/include/libcamera/ipa/soft.mojom @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry. + */ + +module ipa.soft; + +import "include/libcamera/ipa/core.mojom"; + +struct IPAConfigInfo { + libcamera.ControlInfoMap sensorControls; +}; + +interface IPASoftInterface { + init(libcamera.IPASettings settings, + libcamera.SharedFD fdStats, + libcamera.SharedFD fdParams, + libcamera.IPACameraSensorInfo sensorInfo, + libcamera.ControlInfoMap sensorControls) + => (int32 ret, libcamera.ControlInfoMap ipaControls, bool ccmEnabled); + start() => (int32 ret); + stop(); + configure(IPAConfigInfo configInfo) + => (int32 ret); + + [async] queueRequest(uint32 frame, libcamera.ControlList sensorControls); + [async] computeParams(uint32 frame); + [async] processStats(uint32 frame, + uint32 bufferId, + libcamera.ControlList sensorControls); +}; + +interface IPASoftEventInterface { + setSensorControls(libcamera.ControlList sensorControls); + setIspParams(); + metadataReady(uint32 frame, libcamera.ControlList metadata); +}; diff --git a/include/libcamera/ipa/vimc.mojom b/include/libcamera/ipa/vimc.mojom index dd991f7e..c5c5fe83 100644 --- a/include/libcamera/ipa/vimc.mojom +++ b/include/libcamera/ipa/vimc.mojom @@ -47,9 +47,9 @@ interface IPAVimcInterface { * interface functions that mimick how other pipeline handlers typically * handle parameters at runtime. */ - [async] fillParamsBuffer(uint32 frame, uint32 bufferId); + [async] computeParams(uint32 frame, uint32 bufferId); }; interface IPAVimcEventInterface { - paramsBufferReady(uint32 bufferId, [flags] TestFlag flags); + paramsComputed(uint32 bufferId, [flags] TestFlag flags); }; diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h index cd842f67..e1c6341c 100644 --- a/include/libcamera/logging.h +++ b/include/libcamera/logging.h @@ -2,11 +2,13 @@ /* * Copyright (C) 2019, Google Inc. * - * logging.h - Logging infrastructure + * Logging infrastructure */ #pragma once +#include <ostream> + namespace libcamera { enum LoggingTarget { diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build index a24c50d6..30ea76f9 100644 --- a/include/libcamera/meson.build +++ b/include/libcamera/meson.build @@ -32,20 +32,66 @@ install_headers(libcamera_public_headers, libcamera_headers_install_dir = get_option('includedir') / libcamera_include_dir -# control_ids.h and property_ids.h -control_source_files = [ - 'control_ids', - 'property_ids', -] +controls_map = { + 'controls': { + 'core': 'control_ids_core.yaml', + 'debug': 'control_ids_debug.yaml', + 'draft': 'control_ids_draft.yaml', + 'rpi/pisp': 'control_ids_rpi.yaml', + 'rpi/vc4': 'control_ids_rpi.yaml', + }, + + 'properties': { + 'draft': 'property_ids_draft.yaml', + 'core': 'property_ids_core.yaml', + } +} control_headers = [] +controls_files = [] +controls_files_names = [] +properties_files = [] +properties_files_names = [] + +foreach mode, entry : controls_map + files_list = [] + input_files = [] + foreach vendor, header : entry + if vendor not in ['core', 'debug', 'draft'] + if vendor not in pipelines + continue + endif + endif + + if header in files_list + continue + endif -foreach header : control_source_files - input_files = files('../../src/libcamera/' + header +'.yaml', header + '.h.in') + files_list += header + input_files += files('../../src/libcamera/' + header) + endforeach + + outfile = '' + if mode == 'controls' + outfile = 'control_ids.h' + controls_files += input_files + controls_files_names += files_list + else + outfile = 'property_ids.h' + properties_files += input_files + properties_files_names += files_list + endif + + template_file = files('control_ids.h.in') + ranges_file = files('../../src/libcamera/control_ranges.yaml') control_headers += custom_target(header + '_h', input : input_files, - output : header + '.h', - command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'], + output : outfile, + command : [gen_controls, '-o', '@OUTPUT@', + '--mode', mode, '-t', template_file, + '-r', ranges_file, '@INPUT@'], + depend_files : [py_mod_controls], + env : py_build_env, install : true, install_dir : libcamera_headers_install_dir) endforeach @@ -65,24 +111,25 @@ formats_h = custom_target('formats_h', install_dir : libcamera_headers_install_dir) libcamera_public_headers += formats_h +# version.h +version = libcamera_version.split('.') +libcamera_version_config = configuration_data() +libcamera_version_config.set('LIBCAMERA_VERSION_MAJOR', version[0]) +libcamera_version_config.set('LIBCAMERA_VERSION_MINOR', version[1]) +libcamera_version_config.set('LIBCAMERA_VERSION_PATCH', version[2]) + +version_h = configure_file(input : 'version.h.in', + output : 'version.h', + configuration : libcamera_version_config, + install_dir : libcamera_headers_install_dir) +libcamera_public_headers += version_h + # libcamera.h libcamera_h = custom_target('gen-header', input : 'meson.build', output : 'libcamera.h', - command : [gen_header, meson.current_source_dir(), '@OUTPUT@'], + command : [gen_header, '@OUTPUT@', libcamera_public_headers], install : true, install_dir : libcamera_headers_install_dir) libcamera_public_headers += libcamera_h - -# version.h -version = libcamera_version.split('.') -libcamera_version_config = configuration_data() -libcamera_version_config.set('LIBCAMERA_VERSION_MAJOR', version[0]) -libcamera_version_config.set('LIBCAMERA_VERSION_MINOR', version[1]) -libcamera_version_config.set('LIBCAMERA_VERSION_PATCH', version[2]) - -configure_file(input : 'version.h.in', - output : 'version.h', - configuration : libcamera_version_config, - install_dir : libcamera_headers_install_dir) diff --git a/include/libcamera/orientation.h b/include/libcamera/orientation.h index 9a2c2fb2..a3b40e63 100644 --- a/include/libcamera/orientation.h +++ b/include/libcamera/orientation.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Ideas On Board Oy * - * orientation.h - Image orientation + * Image orientation */ #pragma once diff --git a/include/libcamera/pixel_format.h b/include/libcamera/pixel_format.h index d49c5f78..1b4d8c7c 100644 --- a/include/libcamera/pixel_format.h +++ b/include/libcamera/pixel_format.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * pixel_format.h - libcamera Pixel Format + * libcamera Pixel Format */ #pragma once #include <ostream> -#include <set> #include <stdint.h> #include <string> diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in deleted file mode 100644 index ff019408..00000000 --- a/include/libcamera/property_ids.h.in +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * property_ids.h - Property ID list - * - * This file is auto-generated. Do not edit. - */ - -#pragma once - -#include <stdint.h> - -#include <libcamera/controls.h> - -namespace libcamera { - -namespace properties { - -enum { -${ids} -}; - -${controls} - -namespace draft { - -${draft_controls} - -} /* namespace draft */ - -extern const ControlIdMap properties; - -} /* namespace properties */ - -} /* namespace libcamera */ diff --git a/include/libcamera/request.h b/include/libcamera/request.h index dffde153..e214a9d1 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * request.h - Capture request handling + * Capture request handling */ #pragma once @@ -12,7 +12,6 @@ #include <ostream> #include <stdint.h> #include <string> -#include <unordered_set> #include <libcamera/base/class.h> #include <libcamera/base/signal.h> diff --git a/include/libcamera/stream.h b/include/libcamera/stream.h index 4e94187d..b5e8f0a9 100644 --- a/include/libcamera/stream.h +++ b/include/libcamera/stream.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * stream.h - Video stream for a Camera + * Video stream for a Camera */ #pragma once #include <map> -#include <memory> #include <ostream> #include <string> #include <vector> @@ -62,6 +61,8 @@ private: StreamFormats formats_; }; +std::ostream &operator<<(std::ostream &out, const StreamConfiguration &cfg); + enum class StreamRole { Raw, StillCapture, diff --git a/include/libcamera/transform.h b/include/libcamera/transform.h index 44cb4c6f..4517412a 100644 --- a/include/libcamera/transform.h +++ b/include/libcamera/transform.h @@ -2,13 +2,11 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * transform.h - 2D plane transforms + * 2D plane transforms */ #pragma once -#include <string> - namespace libcamera { enum class Orientation; diff --git a/include/libcamera/version.h.in b/include/libcamera/version.h.in index 6e24d0a8..50bf1001 100644 --- a/include/libcamera/version.h.in +++ b/include/libcamera/version.h.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * version.h - Library version information + * Library version information * * This file is auto-generated. Do not edit. */ diff --git a/include/linux/README b/include/linux/README index 9f61517a..f9f68641 100644 --- a/include/linux/README +++ b/include/linux/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from v5.19 of the Linux kernel. Do not +Files in this directory are imported from v6.13-rc1-68-gf9bbbd9a696d of the Linux kernel. Do not modify them manually. diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index b1523cb8..5a6fda66 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -85,6 +85,88 @@ struct dma_buf_sync { #define DMA_BUF_NAME_LEN 32 +/** + * struct dma_buf_export_sync_file - Get a sync_file from a dma-buf + * + * Userspace can perform a DMA_BUF_IOCTL_EXPORT_SYNC_FILE to retrieve the + * current set of fences on a dma-buf file descriptor as a sync_file. CPU + * waits via poll() or other driver-specific mechanisms typically wait on + * whatever fences are on the dma-buf at the time the wait begins. This + * is similar except that it takes a snapshot of the current fences on the + * dma-buf for waiting later instead of waiting immediately. This is + * useful for modern graphics APIs such as Vulkan which assume an explicit + * synchronization model but still need to inter-operate with dma-buf. + * + * The intended usage pattern is the following: + * + * 1. Export a sync_file with flags corresponding to the expected GPU usage + * via DMA_BUF_IOCTL_EXPORT_SYNC_FILE. + * + * 2. Submit rendering work which uses the dma-buf. The work should wait on + * the exported sync file before rendering and produce another sync_file + * when complete. + * + * 3. Import the rendering-complete sync_file into the dma-buf with flags + * corresponding to the GPU usage via DMA_BUF_IOCTL_IMPORT_SYNC_FILE. + * + * Unlike doing implicit synchronization via a GPU kernel driver's exec ioctl, + * the above is not a single atomic operation. If userspace wants to ensure + * ordering via these fences, it is the respnosibility of userspace to use + * locks or other mechanisms to ensure that no other context adds fences or + * submits work between steps 1 and 3 above. + */ +struct dma_buf_export_sync_file { + /** + * @flags: Read/write flags + * + * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both. + * + * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set, + * the returned sync file waits on any writers of the dma-buf to + * complete. Waiting on the returned sync file is equivalent to + * poll() with POLLIN. + * + * If DMA_BUF_SYNC_WRITE is set, the returned sync file waits on + * any users of the dma-buf (read or write) to complete. Waiting + * on the returned sync file is equivalent to poll() with POLLOUT. + * If both DMA_BUF_SYNC_WRITE and DMA_BUF_SYNC_READ are set, this + * is equivalent to just DMA_BUF_SYNC_WRITE. + */ + __u32 flags; + /** @fd: Returned sync file descriptor */ + __s32 fd; +}; + +/** + * struct dma_buf_import_sync_file - Insert a sync_file into a dma-buf + * + * Userspace can perform a DMA_BUF_IOCTL_IMPORT_SYNC_FILE to insert a + * sync_file into a dma-buf for the purposes of implicit synchronization + * with other dma-buf consumers. This allows clients using explicitly + * synchronized APIs such as Vulkan to inter-op with dma-buf consumers + * which expect implicit synchronization such as OpenGL or most media + * drivers/video. + */ +struct dma_buf_import_sync_file { + /** + * @flags: Read/write flags + * + * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both. + * + * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set, + * this inserts the sync_file as a read-only fence. Any subsequent + * implicitly synchronized writes to this dma-buf will wait on this + * fence but reads will not. + * + * If DMA_BUF_SYNC_WRITE is set, this inserts the sync_file as a + * write fence. All subsequent implicitly synchronized access to + * this dma-buf will wait on this fence. + */ + __u32 flags; + /** @fd: Sync file descriptor */ + __s32 fd; +}; + #define DMA_BUF_BASE 'b' #define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync) @@ -94,5 +176,7 @@ struct dma_buf_sync { #define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 1, const char *) #define DMA_BUF_SET_NAME_A _IOW(DMA_BUF_BASE, 1, __u32) #define DMA_BUF_SET_NAME_B _IOW(DMA_BUF_BASE, 1, __u64) +#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) +#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file) #endif diff --git a/include/linux/dma-heap.h b/include/linux/dma-heap.h index 96b90cf0..63b1e9ed 100644 --- a/include/linux/dma-heap.h +++ b/include/linux/dma-heap.h @@ -19,7 +19,7 @@ #define DMA_HEAP_VALID_FD_FLAGS (O_CLOEXEC | O_ACCMODE) /* Currently no heap flags */ -#define DMA_HEAP_VALID_HEAP_FLAGS (0) +#define DMA_HEAP_VALID_HEAP_FLAGS (0ULL) /** * struct dma_heap_allocation_data - metadata passed from userspace for diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h index 1496e097..db679877 100644 --- a/include/linux/drm_fourcc.h +++ b/include/linux/drm_fourcc.h @@ -54,7 +54,7 @@ extern "C" { * Format modifiers may change any property of the buffer, including the number * of planes and/or the required allocation size. Format modifiers are * vendor-namespaced, and as such the relationship between a fourcc code and a - * modifier is specific to the modifer being used. For example, some modifiers + * modifier is specific to the modifier being used. For example, some modifiers * may preserve meaning - such as number of planes - from the fourcc code, * whereas others may not. * @@ -79,7 +79,7 @@ extern "C" { * format. * - Higher-level programs interfacing with KMS/GBM/EGL/Vulkan/etc: these users * see modifiers as opaque tokens they can check for equality and intersect. - * These users musn't need to know to reason about the modifier value + * These users mustn't need to know to reason about the modifier value * (i.e. they are not expected to extract information out of the modifier). * * Vendors should document their modifier usage in as much detail as @@ -88,6 +88,18 @@ extern "C" { * * The authoritative list of format modifier codes is found in * `include/uapi/drm/drm_fourcc.h` + * + * Open Source User Waiver + * ----------------------- + * + * Because this is the authoritative source for pixel formats and modifiers + * referenced by GL, Vulkan extensions and other standards and hence used both + * by open source and closed source driver stacks, the usual requirement for an + * upstream in-kernel or open source userspace user does not apply. + * + * To ensure, as much as feasible, compatibility across stacks and avoid + * confusion with incompatible enumerations stakeholders for all relevant driver + * stacks should approve additions. */ #define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \ @@ -99,18 +111,42 @@ extern "C" { #define DRM_FORMAT_INVALID 0 /* color index */ +#define DRM_FORMAT_C1 fourcc_code('C', '1', ' ', ' ') /* [7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte */ +#define DRM_FORMAT_C2 fourcc_code('C', '2', ' ', ' ') /* [7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte */ +#define DRM_FORMAT_C4 fourcc_code('C', '4', ' ', ' ') /* [7:0] C0:C1 4:4 two pixels/byte */ #define DRM_FORMAT_C8 fourcc_code('C', '8', ' ', ' ') /* [7:0] C */ -/* 8 bpp Red */ +/* 1 bpp Darkness (inverse relationship between channel value and brightness) */ +#define DRM_FORMAT_D1 fourcc_code('D', '1', ' ', ' ') /* [7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte */ + +/* 2 bpp Darkness (inverse relationship between channel value and brightness) */ +#define DRM_FORMAT_D2 fourcc_code('D', '2', ' ', ' ') /* [7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte */ + +/* 4 bpp Darkness (inverse relationship between channel value and brightness) */ +#define DRM_FORMAT_D4 fourcc_code('D', '4', ' ', ' ') /* [7:0] D0:D1 4:4 two pixels/byte */ + +/* 8 bpp Darkness (inverse relationship between channel value and brightness) */ +#define DRM_FORMAT_D8 fourcc_code('D', '8', ' ', ' ') /* [7:0] D */ + +/* 1 bpp Red (direct relationship between channel value and brightness) */ +#define DRM_FORMAT_R1 fourcc_code('R', '1', ' ', ' ') /* [7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte */ + +/* 2 bpp Red (direct relationship between channel value and brightness) */ +#define DRM_FORMAT_R2 fourcc_code('R', '2', ' ', ' ') /* [7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte */ + +/* 4 bpp Red (direct relationship between channel value and brightness) */ +#define DRM_FORMAT_R4 fourcc_code('R', '4', ' ', ' ') /* [7:0] R0:R1 4:4 two pixels/byte */ + +/* 8 bpp Red (direct relationship between channel value and brightness) */ #define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ -/* 10 bpp Red */ +/* 10 bpp Red (direct relationship between channel value and brightness) */ #define DRM_FORMAT_R10 fourcc_code('R', '1', '0', ' ') /* [15:0] x:R 6:10 little endian */ -/* 12 bpp Red */ +/* 12 bpp Red (direct relationship between channel value and brightness) */ #define DRM_FORMAT_R12 fourcc_code('R', '1', '2', ' ') /* [15:0] x:R 4:12 little endian */ -/* 16 bpp Red */ +/* 16 bpp Red (direct relationship between channel value and brightness) */ #define DRM_FORMAT_R16 fourcc_code('R', '1', '6', ' ') /* [15:0] R little endian */ /* 16 bpp RG */ @@ -174,6 +210,10 @@ extern "C" { #define DRM_FORMAT_RGBA1010102 fourcc_code('R', 'A', '3', '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */ #define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */ +/* 48 bpp RGB */ +#define DRM_FORMAT_RGB161616 fourcc_code('R', 'G', '4', '8') /* [47:0] R:G:B 16:16:16 little endian */ +#define DRM_FORMAT_BGR161616 fourcc_code('B', 'G', '4', '8') /* [47:0] B:G:R 16:16:16 little endian */ + /* 64 bpp RGB */ #define DRM_FORMAT_XRGB16161616 fourcc_code('X', 'R', '4', '8') /* [63:0] x:R:G:B 16:16:16:16 little endian */ #define DRM_FORMAT_XBGR16161616 fourcc_code('X', 'B', '4', '8') /* [63:0] x:B:G:R 16:16:16:16 little endian */ @@ -287,6 +327,8 @@ extern "C" { * index 1 = Cr:Cb plane, [39:0] Cr1:Cb1:Cr0:Cb0 little endian */ #define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5') /* 2x2 subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV20 fourcc_code('N', 'V', '2', '0') /* 2x1 subsampled Cr:Cb plane */ +#define DRM_FORMAT_NV30 fourcc_code('N', 'V', '3', '0') /* non-subsampled Cr:Cb plane */ /* * 2 plane YCbCr MSB aligned @@ -448,6 +490,7 @@ extern "C" { #define DRM_FORMAT_MOD_VENDOR_ALLWINNER 0x09 #define DRM_FORMAT_MOD_VENDOR_AMLOGIC 0x0a #define DRM_FORMAT_MOD_VENDOR_MIPI 0x0b +#define DRM_FORMAT_MOD_VENDOR_RPI 0x0c /* add more to the end as needed */ @@ -567,7 +610,7 @@ extern "C" { * This is a tiled layout using 4Kb tiles in row-major layout. * Within the tile pixels are laid out in 16 256 byte units / sub-tiles which * are arranged in four groups (two wide, two high) with column-major layout. - * Each group therefore consits out of four 256 byte units, which are also laid + * Each group therefore consists out of four 256 byte units, which are also laid * out as 2x2 column-major. * 256 byte units are made out of four 64 byte blocks of pixels, producing * either a square block or a 2:1 unit. @@ -626,7 +669,7 @@ extern "C" { * * The main surface is Y-tiled and is at plane index 0 whereas CCS is linear * and at index 1. The clear color is stored at index 2, and the pitch should - * be ignored. The clear color structure is 256 bits. The first 128 bits + * be 64 bytes aligned. The clear color structure is 256 bits. The first 128 bits * represents Raw Clear Color Red, Green, Blue and Alpha color each represented * by 32 bits. The raw clear color is consumed by the 3d engine and generates * the converted clear color of size 64 bits. The first 32 bits store the Lower @@ -679,14 +722,57 @@ extern "C" { * outside of the GEM object in a reserved memory area dedicated for the * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The * main surface pitch is required to be a multiple of four Tile 4 widths. The - * clear color is stored at plane index 1 and the pitch should be ignored. The - * format of the 256 bits of clear color data matches the one used for the - * I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description + * clear color is stored at plane index 1 and the pitch should be 64 bytes + * aligned. The format of the 256 bits of clear color data matches the one used + * for the I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description * for details. */ #define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC fourcc_mod_code(INTEL, 12) /* + * Intel Color Control Surfaces (CCS) for display ver. 14 render compression. + * + * The main surface is tile4 and at plane index 0, the CCS is linear and + * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in + * main surface. In other words, 4 bits in CCS map to a main surface cache + * line pair. The main surface pitch is required to be a multiple of four + * tile4 widths. + */ +#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS fourcc_mod_code(INTEL, 13) + +/* + * Intel Color Control Surfaces (CCS) for display ver. 14 media compression + * + * The main surface is tile4 and at plane index 0, the CCS is linear and + * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in + * main surface. In other words, 4 bits in CCS map to a main surface cache + * line pair. The main surface pitch is required to be a multiple of four + * tile4 widths. For semi-planar formats like NV12, CCS planes follow the + * Y and UV planes i.e., planes 0 and 1 are used for Y and UV surfaces, + * planes 2 and 3 for the respective CCS. + */ +#define I915_FORMAT_MOD_4_TILED_MTL_MC_CCS fourcc_mod_code(INTEL, 14) + +/* + * Intel Color Control Surface with Clear Color (CCS) for display ver. 14 render + * compression. + * + * The main surface is tile4 and is at plane index 0 whereas CCS is linear + * and at index 1. The clear color is stored at index 2, and the pitch should + * be ignored. The clear color structure is 256 bits. The first 128 bits + * represents Raw Clear Color Red, Green, Blue and Alpha color each represented + * by 32 bits. The raw clear color is consumed by the 3d engine and generates + * the converted clear color of size 64 bits. The first 32 bits store the Lower + * Converted Clear Color value and the next 32 bits store the Higher Converted + * Clear Color value when applicable. The Converted Clear Color values are + * consumed by the DE. The last 64 bits are used to store Color Discard Enable + * and Depth Clear Value Valid which are ignored by the DE. A CCS cache line + * corresponds to an area of 4x1 tiles in the main surface. The main surface + * pitch is required to be a multiple of 4 tile widths. + */ +#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS_CC fourcc_mod_code(INTEL, 15) + +/* * IPU3 Bayer packing layout * * The IPU3 raw Bayer formats use a custom packing layout where there are no @@ -795,6 +881,35 @@ extern "C" { */ #define DRM_FORMAT_MOD_VIVANTE_SPLIT_SUPER_TILED fourcc_mod_code(VIVANTE, 4) +/* + * Vivante TS (tile-status) buffer modifiers. They can be combined with all of + * the color buffer tiling modifiers defined above. When TS is present it's a + * separate buffer containing the clear/compression status of each tile. The + * modifiers are defined as VIVANTE_MOD_TS_c_s, where c is the color buffer + * tile size in bytes covered by one entry in the status buffer and s is the + * number of status bits per entry. + * We reserve the top 8 bits of the Vivante modifier space for tile status + * clear/compression modifiers, as future cores might add some more TS layout + * variations. + */ +#define VIVANTE_MOD_TS_64_4 (1ULL << 48) +#define VIVANTE_MOD_TS_64_2 (2ULL << 48) +#define VIVANTE_MOD_TS_128_4 (3ULL << 48) +#define VIVANTE_MOD_TS_256_4 (4ULL << 48) +#define VIVANTE_MOD_TS_MASK (0xfULL << 48) + +/* + * Vivante compression modifiers. Those depend on a TS modifier being present + * as the TS bits get reinterpreted as compression tags instead of simple + * clear markers when compression is enabled. + */ +#define VIVANTE_MOD_COMP_DEC400 (1ULL << 52) +#define VIVANTE_MOD_COMP_MASK (0xfULL << 52) + +/* Masking out the extension bits will yield the base modifier. */ +#define VIVANTE_MOD_EXT_MASK (VIVANTE_MOD_TS_MASK | \ + VIVANTE_MOD_COMP_MASK) + /* NVIDIA frame buffer modifiers */ /* @@ -1068,7 +1183,7 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) */ /* - * The top 4 bits (out of the 56 bits alloted for specifying vendor specific + * The top 4 bits (out of the 56 bits allotted for specifying vendor specific * modifiers) denote the category for modifiers. Currently we have three * categories of modifiers ie AFBC, MISC and AFRC. We can have a maximum of * sixteen different categories. @@ -1384,7 +1499,7 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) * Amlogic FBC Memory Saving mode * * Indicates the storage is packed when pixel size is multiple of word - * boudaries, i.e. 8bit should be stored in this mode to save allocation + * boundaries, i.e. 8bit should be stored in this mode to save allocation * memory. * * This mode reduces body layout to 3072 bytes per 64x32 superblock with @@ -1440,6 +1555,8 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) #define AMD_FMT_MOD_TILE_VER_GFX9 1 #define AMD_FMT_MOD_TILE_VER_GFX10 2 #define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3 +#define AMD_FMT_MOD_TILE_VER_GFX11 4 +#define AMD_FMT_MOD_TILE_VER_GFX12 5 /* * 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical @@ -1450,11 +1567,29 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) /* * 64K_D for non-32 bpp is the same for GFX9/GFX10/GFX10_RBPLUS and hence has * GFX9 as canonical version. + * + * 64K_D_2D on GFX12 is identical to 64K_D on GFX11. */ #define AMD_FMT_MOD_TILE_GFX9_64K_D 10 #define AMD_FMT_MOD_TILE_GFX9_64K_S_X 25 #define AMD_FMT_MOD_TILE_GFX9_64K_D_X 26 #define AMD_FMT_MOD_TILE_GFX9_64K_R_X 27 +#define AMD_FMT_MOD_TILE_GFX11_256K_R_X 31 + +/* Gfx12 swizzle modes: + * 0 - LINEAR + * 1 - 256B_2D - 2D block dimensions + * 2 - 4KB_2D + * 3 - 64KB_2D + * 4 - 256KB_2D + * 5 - 4KB_3D - 3D block dimensions + * 6 - 64KB_3D + * 7 - 256KB_3D + */ +#define AMD_FMT_MOD_TILE_GFX12_256B_2D 1 +#define AMD_FMT_MOD_TILE_GFX12_4K_2D 2 +#define AMD_FMT_MOD_TILE_GFX12_64K_2D 3 +#define AMD_FMT_MOD_TILE_GFX12_256K_2D 4 #define AMD_FMT_MOD_DCC_BLOCK_64B 0 #define AMD_FMT_MOD_DCC_BLOCK_128B 1 @@ -1554,6 +1689,9 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) */ #define MIPI_FORMAT_MOD_CSI2_PACKED fourcc_mod_code(MIPI, 1) +#define PISP_FORMAT_MOD_COMPRESS_MODE1 fourcc_mod_code(RPI, 1) +#define PISP_FORMAT_MOD_COMPRESS_MODE2 fourcc_mod_code(RPI, 2) + #if defined(__cplusplus) } #endif diff --git a/include/linux/intel-ipu3.h b/include/linux/intel-ipu3.h index 5c298ec5..8c192f35 100644 --- a/include/linux/intel-ipu3.h +++ b/include/linux/intel-ipu3.h @@ -626,8 +626,11 @@ struct ipu3_uapi_stats_3a { * @b: white balance gain for B channel. * @gb: white balance gain for Gb channel. * - * Precision u3.13, range [0, 8). White balance correction is done by applying - * a multiplicative gain to each color channels prior to BNR. + * For BNR parameters WB gain factor for the three channels [Ggr, Ggb, Gb, Gr]. + * Their precision is U3.13 and the range is (0, 8) and the actual gain is + * Gx + 1, it is typically Gx = 1. + * + * Pout = {Pin * (1 + Gx)}. */ struct ipu3_uapi_bnr_static_config_wb_gains_config { __u16 gr; @@ -2482,11 +2485,9 @@ struct ipu3_uapi_anr_config { * &ipu3_uapi_yuvp1_y_ee_nr_config * @yds: y down scaler config. See &ipu3_uapi_yuvp1_yds_config * @chnr: chroma noise reduction config. See &ipu3_uapi_yuvp1_chnr_config - * @reserved1: reserved * @yds2: y channel down scaler config. See &ipu3_uapi_yuvp1_yds_config * @tcc: total color correction config as defined in struct * &ipu3_uapi_yuvp2_tcc_static_config - * @reserved2: reserved * @anr: advanced noise reduction config.See &ipu3_uapi_anr_config * @awb_fr: AWB filter response config. See ipu3_uapi_awb_fr_config * @ae: auto exposure config As specified by &ipu3_uapi_ae_config @@ -2721,7 +2722,6 @@ struct ipu3_uapi_obgrid_param { * @acc_ae: 0 = no update, 1 = update. * @acc_af: 0 = no update, 1 = update. * @acc_awb: 0 = no update, 1 = update. - * @__acc_osys: 0 = no update, 1 = update. * @reserved3: Not used. * @lin_vmem_params: 0 = no update, 1 = update. * @tnr3_vmem_params: 0 = no update, 1 = update. diff --git a/include/linux/mali-c55-config.h b/include/linux/mali-c55-config.h new file mode 100644 index 00000000..b3141559 --- /dev/null +++ b/include/linux/mali-c55-config.h @@ -0,0 +1,909 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ARM Mali-C55 ISP Driver - Userspace API + * + * Copyright (C) 2023 Ideas on Board Oy + */ + +#ifndef __UAPI_MALI_C55_CONFIG_H +#define __UAPI_MALI_C55_CONFIG_H + +#include <linux/types.h> + +/* + * Frames are split into zones of almost equal width and height - a zone is a + * rectangular tile of a frame. The metering blocks within the ISP collect + * aggregated statistics per zone. A maximum of 15x15 zones can be configured, + * and so the statistics buffer within the hardware is sized to accommodate + * that. + * + * The utilised number of zones is runtime configurable. + */ +#define MALI_C55_MAX_ZONES (15 * 15) + +/** + * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics + * + * @bins: 1024 element array of 16-bit pixel counts. + * + * The 1024-bin histogram module collects image-global but zone-weighted + * intensity distributions of pixels in fixed-width bins. The modules can be + * configured into different "plane modes" which affect the contents of the + * collected statistics. In plane mode 0, pixel intensities are taken regardless + * of colour plane into a single 1024-bin histogram with a bin width of 4. In + * plane mode 1, four 256-bin histograms with a bin width of 16 are collected - + * one for each CFA colour plane. In plane modes 4, 5, 6 and 7 two 512-bin + * histograms with a bin width of 8 are collected - in each mode one of the + * colour planes is collected into the first histogram and all the others are + * combined into the second. The histograms are stored consecutively in the bins + * array. + * + * The 16-bit pixel counts are stored as a 4-bit exponent in the most + * significant bits followed by a 12-bit mantissa. Conversion to a usable + * format can be done according to the following pseudo-code:: + * + * if (e == 0) { + * bin = m * 2; + * } else { + * bin = (m + 4096) * 2^e + * } + * + * where + * e is the exponent value in range 0..15 + * m is the mantissa value in range 0..4095 + * + * The pixels used in calculating the statistics can be masked using three + * methods: + * + * 1. Pixels can be skipped in X and Y directions independently. + * 2. Minimum/Maximum intensities can be configured + * 3. Zones can be differentially weighted, including 0 weighted to mask them + * + * The data for this histogram can be collected from different tap points in the + * ISP depending on configuration - after the white balance or digital gain + * blocks, or immediately after the input crossbar. + */ +struct mali_c55_ae_1024bin_hist { + __u16 bins[1024]; +} __attribute__((packed)); + +/** + * struct mali_c55_ae_5bin_hist - Auto Exposure 5-bin histogram statistics + * + * @hist0: 16-bit normalised pixel count for the 0th intensity bin + * @hist1: 16-bit normalised pixel count for the 1st intensity bin + * @hist3: 16-bit normalised pixel count for the 3rd intensity bin + * @hist4: 16-bit normalised pixel count for the 4th intensity bin + * + * The ISP generates a 5-bin histogram of normalised pixel counts within bins of + * pixel intensity for each of 225 possible zones within a frame. The centre bin + * of the histogram for each zone is not available from the hardware and must be + * calculated by subtracting the values of hist0, hist1, hist3 and hist4 from + * 0xffff as in the following equation: + * + * hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4) + */ +struct mali_c55_ae_5bin_hist { + __u16 hist0; + __u16 hist1; + __u16 hist3; + __u16 hist4; +} __attribute__((packed)); + +/** + * struct mali_c55_awb_average_ratios - Auto White Balance colour ratios + * + * @avg_rg_gr: Average R/G or G/R ratio in Q4.8 format. + * @avg_bg_br: Average B/G or B/R ratio in Q4.8 format. + * @num_pixels: The number of pixels used in the AWB calculation + * + * The ISP calculates and collects average colour ratios for each zone in an + * image and stores them in Q4.8 format (the lowest 8 bits are fractional, with + * bits [11:8] representing the integer). The exact ratios collected (either + * R/G, B/G or G/R, B/R) are configurable through the parameters buffer. The + * value of the 4 high bits is undefined. + */ +struct mali_c55_awb_average_ratios { + __u16 avg_rg_gr; + __u16 avg_bg_br; + __u32 num_pixels; +} __attribute__((packed)); + +/** + * struct mali_c55_af_statistics - Auto Focus edge and intensity statistics + * + * @intensity_stats: Packed mantissa and exponent value for pixel intensity + * @edge_stats: Packed mantissa and exponent values for edge intensity + * + * The ISP collects the squared sum of pixel intensities for each zone within a + * configurable Region of Interest on the frame. Additionally, the same data are + * collected after being passed through a bandpass filter which removes high and + * low frequency components - these are referred to as the edge statistics. + * + * The intensity and edge statistics for a zone can be used to calculate the + * contrast information for a zone + * + * C = E2 / I2 + * + * Where I2 is the intensity statistic for a zone and E2 is the edge statistic + * for that zone. Optimum focus is reached when C is at its maximum. + * + * The intensity and edge statistics are stored packed into a non-standard 16 + * bit floating point format, where the 7 most significant bits represent the + * exponent and the 9 least significant bits the mantissa. This format can be + * unpacked with the following pseudocode:: + * + * if (e == 0) { + * x = m; + * } else { + * x = 2^e-1 * (m + 2^9) + * } + * + * where + * e is the exponent value in range 0..127 + * m is the mantissa value in range 0..511 + */ +struct mali_c55_af_statistics { + __u16 intensity_stats; + __u16 edge_stats; +} __attribute__((packed)); + +/** + * struct mali_c55_stats_buffer - 3A statistics for the mali-c55 ISP + * + * @ae_1024bin_hist: 1024-bin frame-global pixel intensity histogram + * @iridix_1024bin_hist: Post-Iridix block 1024-bin histogram + * @ae_5bin_hists: 5-bin pixel intensity histograms for AEC + * @reserved1: Undefined buffer space + * @awb_ratios: Color balance ratios for Auto White Balance + * @reserved2: Undefined buffer space + * @af_statistics: Pixel intensity statistics for Auto Focus + * @reserved3: Undefined buffer space + * + * This struct describes the metering statistics space in the Mali-C55 ISP's + * hardware in its entirety. The space between each defined area is marked as + * "unknown" and may not be 0, but should not be used. The @ae_5bin_hists, + * @awb_ratios and @af_statistics members are arrays of statistics per-zone. + * The zones are arranged in the array in raster order starting from the top + * left corner of the image. + */ + +struct mali_c55_stats_buffer { + struct mali_c55_ae_1024bin_hist ae_1024bin_hist; + struct mali_c55_ae_1024bin_hist iridix_1024bin_hist; + struct mali_c55_ae_5bin_hist ae_5bin_hists[MALI_C55_MAX_ZONES]; + __u32 reserved1[14]; + struct mali_c55_awb_average_ratios awb_ratios[MALI_C55_MAX_ZONES]; + __u32 reserved2[14]; + struct mali_c55_af_statistics af_statistics[MALI_C55_MAX_ZONES]; + __u32 reserved3[15]; +} __attribute__((packed)); + +/** + * enum mali_c55_param_buffer_version - Mali-C55 parameters block versioning + * + * @MALI_C55_PARAM_BUFFER_V1: First version of Mali-C55 parameters block + */ +enum mali_c55_param_buffer_version { + MALI_C55_PARAM_BUFFER_V1, +}; + +/** + * enum mali_c55_param_block_type - Enumeration of Mali-C55 parameter blocks + * + * This enumeration defines the types of Mali-C55 parameters block. Each block + * configures a specific processing block of the Mali-C55 ISP. The block + * type allows the driver to correctly interpret the parameters block data. + * + * It is the responsibility of userspace to correctly set the type of each + * parameters block. + * + * @MALI_C55_PARAM_BLOCK_SENSOR_OFFS: Sensor pre-shading black level offset + * @MALI_C55_PARAM_BLOCK_AEXP_HIST: Auto-exposure 1024-bin histogram + * configuration + * @MALI_C55_PARAM_BLOCK_AEXP_IHIST: Post-Iridix auto-exposure 1024-bin + * histogram configuration + * @MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS: Auto-exposure 1024-bin histogram + * weighting + * @MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS: Post-Iridix auto-exposure 1024-bin + * histogram weighting + * @MALI_C55_PARAM_BLOCK_DIGITAL_GAIN: Digital gain + * @MALI_C55_PARAM_BLOCK_AWB_GAINS: Auto-white balance gains + * @MALI_C55_PARAM_BLOCK_AWB_CONFIG: Auto-white balance statistics config + * @MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP: Auto-white balance gains for AEXP-0 tap + * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration + * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection + */ +enum mali_c55_param_block_type { + MALI_C55_PARAM_BLOCK_SENSOR_OFFS, + MALI_C55_PARAM_BLOCK_AEXP_HIST, + MALI_C55_PARAM_BLOCK_AEXP_IHIST, + MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS, + MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS, + MALI_C55_PARAM_BLOCK_DIGITAL_GAIN, + MALI_C55_PARAM_BLOCK_AWB_GAINS, + MALI_C55_PARAM_BLOCK_AWB_CONFIG, + MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP, + MALI_C55_PARAM_MESH_SHADING_CONFIG, + MALI_C55_PARAM_MESH_SHADING_SELECTION, +}; + +#define MALI_C55_PARAM_BLOCK_FL_NONE 0 +#define MALI_C55_PARAM_BLOCK_FL_DISABLED BIT(0) + +/** + * struct mali_c55_params_block_header - Mali-C55 parameter block header + * + * This structure represents the common part of all the ISP configuration + * blocks. Each parameters block embeds an instance of this structure type + * as its first member, followed by the block-specific configuration data. The + * driver inspects this common header to discern the block type and its size and + * properly handle the block content by casting it to the correct block-specific + * type. + * + * The @type field is one of the values enumerated by + * :c:type:`mali_c55_param_block_type` and specifies how the data should be + * interpreted by the driver. The @size field specifies the size of the + * parameters block and is used by the driver for validation purposes. The + * @flags field holds a bitmask of per-block flags MALI_C55_PARAM_BLOCK_FL_*. + * + * If userspace wants to disable an ISP block the + * MALI_C55_PARAM_BLOCK_FL_DISABLED bit should be set in the @flags field. In + * that case userspace may optionally omit the remainder of the configuration + * block, which will in any case be ignored by the driver. If a new + * configuration of an ISP block has to be applied userspace shall fully + * populate the ISP block and omit setting the MALI_C55_PARAM_BLOCK_FL_DISABLED + * bit in the @flags field. + * + * Userspace is responsible for correctly populating the parameters block header + * fields (@type, @flags and @size) and correctly populate the block-specific + * parameters. + * + * For example: + * + * .. code-block:: c + * + * void populate_sensor_offs(struct mali_c55_params_block_header *block) { + * block->type = MALI_C55_PARAM_BLOCK_SENSOR_OFFS; + * block->enabled = MALI_C55_PARAM_BLOCK_FL_NONE; + * block->size = sizeof(struct mali_c55_params_sensor_off_preshading); + * + * struct mali_c55_params_sensor_off_preshading *sensor_offs = + * (struct mali_c55_params_sensor_off_preshading *)block; + * + * sensor_offs->chan00 = offset00; + * sensor_offs->chan01 = offset01; + * sensor_offs->chan10 = offset10; + * sensor_offs->chan11 = offset11; + * } + * + * @type: The parameters block type from :c:type:`mali_c55_param_block_type` + * @flags: Bitmask of block flags + * @size: Size (in bytes) of the parameters block + */ +struct mali_c55_params_block_header { + __u16 type; + __u16 flags; + __u32 size; +} __attribute__((aligned(8))); + +/** + * struct mali_c55_params_sensor_off_preshading - offset subtraction for each + * color channel + * + * Provides removal of the sensor black level from the sensor data. Separate + * offsets are provided for each of the four Bayer component color channels + * which are defaulted to R, Gr, Gb, B. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_SENSOR_OFFS from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @chan00: Offset for color channel 00 (default: R) + * @chan01: Offset for color channel 01 (default: Gr) + * @chan10: Offset for color channel 10 (default: Gb) + * @chan11: Offset for color channel 11 (default: B) + */ +struct mali_c55_params_sensor_off_preshading { + struct mali_c55_params_block_header header; + __u32 chan00; + __u32 chan01; + __u32 chan10; + __u32 chan11; +}; + +/** + * enum mali_c55_aexp_hist_tap_points - Tap points for the AEXP histogram + * @MALI_C55_AEXP_HIST_TAP_WB: After static white balance + * @MALI_C55_AEXP_HIST_TAP_FS: After WDR Frame Stitch + * @MALI_C55_AEXP_HIST_TAP_TPG: After the test pattern generator + */ +enum mali_c55_aexp_hist_tap_points { + MALI_C55_AEXP_HIST_TAP_WB = 0, + MALI_C55_AEXP_HIST_TAP_FS, + MALI_C55_AEXP_HIST_TAP_TPG, +}; + +/** + * enum mali_c55_aexp_skip_x - Horizontal pixel skipping + * @MALI_C55_AEXP_SKIP_X_EVERY_2ND: Collect every 2nd pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_3RD: Collect every 3rd pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_4TH: Collect every 4th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_5TH: Collect every 5th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_8TH: Collect every 8th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_9TH: Collect every 9th pixel horizontally + */ +enum mali_c55_aexp_skip_x { + MALI_C55_AEXP_SKIP_X_EVERY_2ND, + MALI_C55_AEXP_SKIP_X_EVERY_3RD, + MALI_C55_AEXP_SKIP_X_EVERY_4TH, + MALI_C55_AEXP_SKIP_X_EVERY_5TH, + MALI_C55_AEXP_SKIP_X_EVERY_8TH, + MALI_C55_AEXP_SKIP_X_EVERY_9TH +}; + +/** + * enum mali_c55_aexp_skip_y - Vertical pixel skipping + * @MALI_C55_AEXP_SKIP_Y_ALL: Collect every single pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_2ND: Collect every 2nd pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_3RD: Collect every 3rd pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_4TH: Collect every 4th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_5TH: Collect every 5th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_8TH: Collect every 8th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_9TH: Collect every 9th pixel vertically + */ +enum mali_c55_aexp_skip_y { + MALI_C55_AEXP_SKIP_Y_ALL, + MALI_C55_AEXP_SKIP_Y_EVERY_2ND, + MALI_C55_AEXP_SKIP_Y_EVERY_3RD, + MALI_C55_AEXP_SKIP_Y_EVERY_4TH, + MALI_C55_AEXP_SKIP_Y_EVERY_5TH, + MALI_C55_AEXP_SKIP_Y_EVERY_8TH, + MALI_C55_AEXP_SKIP_Y_EVERY_9TH +}; + +/** + * enum mali_c55_aexp_row_column_offset - Start from the first or second row or + * column + * @MALI_C55_AEXP_FIRST_ROW_OR_COL: Start from the first row / column + * @MALI_C55_AEXP_SECOND_ROW_OR_COL: Start from the second row / column + */ +enum mali_c55_aexp_row_column_offset { + MALI_C55_AEXP_FIRST_ROW_OR_COL = 1, + MALI_C55_AEXP_SECOND_ROW_OR_COL = 2, +}; + +/** + * enum mali_c55_aexp_hist_plane_mode - Mode for the AEXP Histograms + * @MALI_C55_AEXP_HIST_COMBINED: All color planes in one 1024-bin histogram + * @MALI_C55_AEXP_HIST_SEPARATE: Each color plane in one 256-bin histogram with a bin width of 16 + * @MALI_C55_AEXP_HIST_FOCUS_00: Top left plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_01: Top right plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_10: Bottom left plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_11: Bottom right plane in the first bank, rest in second bank + * + * In the "focus" modes statistics are collected into two 512-bin histograms + * with a bin width of 8. One colour plane is in the first histogram with the + * remainder combined into the second. The four options represent which of the + * four positions in a bayer pattern are the focused plane. + */ +enum mali_c55_aexp_hist_plane_mode { + MALI_C55_AEXP_HIST_COMBINED = 0, + MALI_C55_AEXP_HIST_SEPARATE = 1, + MALI_C55_AEXP_HIST_FOCUS_00 = 4, + MALI_C55_AEXP_HIST_FOCUS_01 = 5, + MALI_C55_AEXP_HIST_FOCUS_10 = 6, + MALI_C55_AEXP_HIST_FOCUS_11 = 7, +}; + +/** + * struct mali_c55_params_aexp_hist - configuration for AEXP metering hists + * + * This struct allows users to configure the 1024-bin AEXP histograms. Broadly + * speaking the parameters allow you to mask particular regions of the image and + * to select different kinds of histogram. + * + * The skip_x, offset_x, skip_y and offset_y fields allow users to ignore or + * mask pixels in the frame by their position relative to the top left pixel. + * First, the skip_y, offset_x and offset_y fields define which of the pixels + * within each 2x2 region will be counted in the statistics. + * + * If skip_y == 0 then two pixels from each covered region will be counted. If + * both offset_x and offset_y are zero, then the two left-most pixels in each + * 2x2 pixel region will be counted. Setting offset_x = 1 will discount the top + * left pixel and count the top right pixel. Setting offset_y = 1 will discount + * the bottom left pixel and count the bottom right pixel. + * + * If skip_y != 0 then only a single pixel from each region covered by the + * pattern will be counted. In this case offset_x controls whether the pixel + * that's counted is in the left (if offset_x == 0) or right (if offset_x == 1) + * column and offset_y controls whether the pixel that's counted is in the top + * (if offset_y == 0) or bottom (if offset_y == 1) row. + * + * The skip_x and skip_y fields control how the 2x2 pixel region is repeated + * across the image data. The first instance of the region is always in the top + * left of the image data. The skip_x field controls how many pixels are ignored + * in the x direction before the pixel masking region is repeated. The skip_y + * field controls how many pixels are ignored in the y direction before the + * pixel masking region is repeated. + * + * These fields can be used to reduce the number of pixels counted for the + * statistics, but it's important to be careful to configure them correctly. + * Some combinations of values will result in colour components from the input + * data being ignored entirely, for example in the following configuration: + * + * skip_x = 0 + * offset_x = 0 + * skip_y = 0 + * offset_y = 0 + * + * Only the R and Gb components of RGGB data that was input would be collected. + * Similarly in the following configuration: + * + * skip_x = 0 + * offset_x = 0 + * skip_y = 1 + * offset_y = 1 + * + * Only the Gb component of RGGB data that was input would be collected. To + * correct things such that all 4 colour components were included it would be + * necessary to set the skip_x and skip_y fields in a way that resulted in all + * four colour components being collected: + * + * skip_x = 1 + * offset_x = 0 + * skip_y = 1 + * offset_y = 1 + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AEXP_HIST or + * MALI_C55_PARAM_BLOCK_AEXP_IHIST from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @skip_x: Horizontal decimation. See enum mali_c55_aexp_skip_x + * @offset_x: Skip the first column, or not. See enum mali_c55_aexp_row_column_offset + * @skip_y: Vertical decimation. See enum mali_c55_aexp_skip_y + * @offset_y: Skip the first row, or not. See enum mali_c55_aexp_row_column_offset + * @scale_bottom: Scale pixels in bottom half of intensity range: 0=1x ,1=2x, 2=4x, 4=8x, 4=16x + * @scale_top: scale pixels in top half of intensity range: 0=1x ,1=2x, 2=4x, 4=8x, 4=16x + * @plane_mode: Plane separation mode. See enum mali_c55_aexp_hist_plane_mode + * @tap_point: Tap point for histogram from enum mali_c55_aexp_hist_tap_points. + * This parameter is unused for the post-Iridix Histogram + */ +struct mali_c55_params_aexp_hist { + struct mali_c55_params_block_header header; + __u8 skip_x; + __u8 offset_x; + __u8 skip_y; + __u8 offset_y; + __u8 scale_bottom; + __u8 scale_top; + __u8 plane_mode; + __u8 tap_point; +}; + +/** + * struct mali_c55_params_aexp_weights - Array of weights for AEXP metering + * + * This struct allows users to configure the weighting for both of the 1024-bin + * AEXP histograms. The pixel data collected for each zone is multiplied by the + * corresponding weight from this array, which may be zero if the intention is + * to mask off the zone entirely. + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS + * or MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @nodes_used_horiz: Number of active zones horizontally [0..15] + * @nodes_used_vert: Number of active zones vertically [0..15] + * @zone_weights: Zone weighting. Index is row*col where 0,0 is the top + * left zone continuing in raster order. Each zone can be + * weighted in the range [0..15]. The number of rows and + * columns is defined by @nodes_used_vert and + * @nodes_used_horiz + */ +struct mali_c55_params_aexp_weights { + struct mali_c55_params_block_header header; + __u8 nodes_used_horiz; + __u8 nodes_used_vert; + __u8 zone_weights[MALI_C55_MAX_ZONES]; +}; + +/** + * struct mali_c55_params_digital_gain - Digital gain value + * + * This struct carries a digital gain value to set in the ISP. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_DIGITAL_GAIN from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @gain: The digital gain value to apply, in Q5.8 format. + */ +struct mali_c55_params_digital_gain { + struct mali_c55_params_block_header header; + __u16 gain; +}; + +/** + * enum mali_c55_awb_stats_mode - Statistics mode for AWB + * @MALI_C55_AWB_MODE_GRBR: Statistics collected as Green/Red and Blue/Red ratios + * @MALI_C55_AWB_MODE_RGBG: Statistics collected as Red/Green and Blue/Green ratios + */ +enum mali_c55_awb_stats_mode { + MALI_C55_AWB_MODE_GRBR = 0, + MALI_C55_AWB_MODE_RGBG, +}; + +/** + * struct mali_c55_params_awb_gains - Gain settings for auto white balance + * + * This struct allows users to configure the gains for auto-white balance. There + * are four gain settings corresponding to each colour channel in the bayer + * domain. Although named generically, the association between the gain applied + * and the colour channel is done automatically within the ISP depending on the + * input format, and so the following mapping always holds true:: + * + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * All of the gains are stored in Q4.8 format. + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AWB_GAINS or + * MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @gain00: Multiplier for colour channel 00 + * @gain01: Multiplier for colour channel 01 + * @gain10: Multiplier for colour channel 10 + * @gain11: Multiplier for colour channel 11 + */ +struct mali_c55_params_awb_gains { + struct mali_c55_params_block_header header; + __u16 gain00; + __u16 gain01; + __u16 gain10; + __u16 gain11; +}; + +/** + * enum mali_c55_params_awb_tap_points - Tap points for the AWB statistics + * @MALI_C55_AWB_STATS_TAP_PF: Immediately after the Purple Fringe block + * @MALI_C55_AWB_STATS_TAP_CNR: Immediately after the CNR block + */ +enum mali_c55_params_awb_tap_points { + MALI_C55_AWB_STATS_TAP_PF = 0, + MALI_C55_AWB_STATS_TAP_CNR, +}; + +/** + * struct mali_c55_params_awb_config - Stats settings for auto-white balance + * + * This struct allows the configuration of the statistics generated for auto + * white balance. Pixel intensity limits can be set to exclude overly bright or + * dark regions of an image from the statistics entirely. Colour ratio minima + * and maxima can be set to discount pixels who's ratios fall outside the + * defined boundaries; there are two sets of registers to do this - the + * "min/max" ratios which bound a region and the "high/low" ratios which further + * trim the upper and lower ratios. For example with the boundaries configured + * as follows, only pixels whos colour ratios falls into the region marked "A" + * would be counted:: + * + * cr_high + * 2.0 | | + * | cb_max --> _________________________v_____ + * 1.8 | | \ | + * | | \ | + * 1.6 | | \ | + * | | \ | + * c 1.4 | cb_low -->|\ A \|<-- cb_high + * b | | \ | + * 1.2 | | \ | + * r | | \ | + * a 1.0 | cb_min --> |____\_________________________| + * t | ^ ^ ^ + * i 0.8 | | | | + * o | cr_min | cr_max + * s 0.6 | | + * | cr_low + * 0.4 | + * | + * 0.2 | + * | + * 0.0 |_______________________________________________________________ + * 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 + * cr ratios + * + * header.type should be set to MALI_C55_PARAM_BLOCK_AWB_CONFIG from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @tap_point: The tap point from enum mali_c55_params_awb_tap_points + * @stats_mode: AWB statistics collection mode, see :c:type:`mali_c55_awb_stats_mode` + * @white_level: Upper pixel intensity (I.E. raw pixel values) limit + * @black_level: Lower pixel intensity (I.E. raw pixel values) limit + * @cr_max: Maximum R/G ratio (Q4.8 format) + * @cr_min: Minimum R/G ratio (Q4.8 format) + * @cb_max: Maximum B/G ratio (Q4.8 format) + * @cb_min: Minimum B/G ratio (Q4.8 format) + * @nodes_used_horiz: Number of active zones horizontally [0..15] + * @nodes_used_vert: Number of active zones vertically [0..15] + * @cr_high: R/G ratio trim high (Q4.8 format) + * @cr_low: R/G ratio trim low (Q4.8 format) + * @cb_high: B/G ratio trim high (Q4.8 format) + * @cb_low: B/G ratio trim low (Q4.8 format) + */ +struct mali_c55_params_awb_config { + struct mali_c55_params_block_header header; + __u8 tap_point; + __u8 stats_mode; + __u16 white_level; + __u16 black_level; + __u16 cr_max; + __u16 cr_min; + __u16 cb_max; + __u16 cb_min; + __u8 nodes_used_horiz; + __u8 nodes_used_vert; + __u16 cr_high; + __u16 cr_low; + __u16 cb_high; + __u16 cb_low; +}; + +#define MALI_C55_NUM_MESH_SHADING_ELEMENTS 3072 + +/** + * struct mali_c55_params_mesh_shading_config - Mesh shading configuration + * + * The mesh shading correction module allows programming a separate table of + * either 16x16 or 32x32 node coefficients for 3 different light sources. The + * final correction coefficients applied are computed by blending the + * coefficients from two tables together. + * + * A page of 1024 32-bit integers is associated to each colour channel, with + * pages stored consecutively in memory. Each 32-bit integer packs 3 8-bit + * correction coefficients for a single node, one for each of the three light + * sources. The 8 most significant bits are unused. The following table + * describes the layout:: + * + * +----------- Page (Colour Plane) 0 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 0 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 1 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 1023 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +----------- Page (Colour Plane) 1 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 1024 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 1025 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 2047 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +----------- Page (Colour Plane) 2 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 2048 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 2049 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 3071 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * + * The @mesh_scale member determines the precision and minimum and maximum gain. + * For example if @mesh_scale is 0 and therefore selects 0 - 2x gain, a value of + * 0 in a coefficient means 0.0 gain, a value of 128 means 1.0 gain and 255 + * means 2.0 gain. + * + * header.type should be set to MALI_C55_PARAM_MESH_SHADING_CONFIG from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @mesh_show: Output the mesh data rather than image data + * @mesh_scale: Set the precision and maximum gain range of mesh shading + * - 0 = 0-2x gain + * - 1 = 0-4x gain + * - 2 = 0-8x gain + * - 3 = 0-16x gain + * - 4 = 1-2x gain + * - 5 = 1-3x gain + * - 6 = 1-5x gain + * - 7 = 1-9x gain + * @mesh_page_r: Mesh page select for red colour plane [0..2] + * @mesh_page_g: Mesh page select for green colour plane [0..2] + * @mesh_page_b: Mesh page select for blue colour plane [0..2] + * @mesh_width: Number of horizontal nodes minus 1 [15,31] + * @mesh_height: Number of vertical nodes minus 1 [15,31] + * @mesh: Mesh shading correction tables + */ +struct mali_c55_params_mesh_shading_config { + struct mali_c55_params_block_header header; + __u8 mesh_show; + __u8 mesh_scale; + __u8 mesh_page_r; + __u8 mesh_page_g; + __u8 mesh_page_b; + __u8 mesh_width; + __u8 mesh_height; + __u32 mesh[MALI_C55_NUM_MESH_SHADING_ELEMENTS]; +}; + +/** enum mali_c55_params_mesh_alpha_bank - Mesh shading table bank selection + * @MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS1 - Select Light Sources 0 and 1 + * @MALI_C55_MESH_ALPHA_BANK_LS1_AND_LS2 - Select Light Sources 1 and 2 + * @MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS2 - Select Light Sources 0 and 2 + */ +enum mali_c55_params_mesh_alpha_bank { + MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS1 = 0, + MALI_C55_MESH_ALPHA_BANK_LS1_AND_LS2 = 1, + MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS2 = 4 +}; + +/** + * struct mali_c55_params_mesh_shading_selection - Mesh table selection + * + * The module computes the final correction coefficients by blending the ones + * from two light source tables, which are selected (independently for each + * colour channel) by the @mesh_alpha_bank_r/g/b fields. + * + * The final blended coefficients for each node are calculated using the + * following equation: + * + * Final coefficient = (a * LS\ :sub:`b`\ + (256 - a) * LS\ :sub:`a`\) / 256 + * + * Where a is the @mesh_alpha_r/g/b value, and LS\ :sub:`a`\ and LS\ :sub:`b`\ + * are the node cofficients for the two tables selected by the + * @mesh_alpha_bank_r/g/b value. + * + * The scale of the applied correction may also be controlled by tuning the + * @mesh_strength member. This is a modifier to the final coefficients which can + * be used to globally reduce the gains applied. + * + * header.type should be set to MALI_C55_PARAM_MESH_SHADING_SELECTION from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @mesh_alpha_bank_r: Red mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_bank_g: Green mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_bank_b: Blue mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_r: Blend coefficient for R [0..255] + * @mesh_alpha_g: Blend coefficient for G [0..255] + * @mesh_alpha_b: Blend coefficient for B [0..255] + * @mesh_strength: Mesh strength in Q4.12 format [0..4096] + */ +struct mali_c55_params_mesh_shading_selection { + struct mali_c55_params_block_header header; + __u8 mesh_alpha_bank_r; + __u8 mesh_alpha_bank_g; + __u8 mesh_alpha_bank_b; + __u8 mesh_alpha_r; + __u8 mesh_alpha_g; + __u8 mesh_alpha_b; + __u16 mesh_strength; +}; + +/** + * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters + * + * Though the parameters for the Mali-C55 are passed as optional blocks, the + * driver still needs to know the absolute maximum size so that it can allocate + * a buffer sized appropriately to accommodate userspace attempting to set all + * possible parameters in a single frame. + * + * Some structs are in this list multiple times. Where that's the case, it just + * reflects the fact that the same struct can be used with multiple different + * header types from :c:type:`mali_c55_param_block_type`. + */ +#define MALI_C55_PARAMS_MAX_SIZE \ + (sizeof(struct mali_c55_params_sensor_off_preshading) + \ + sizeof(struct mali_c55_params_aexp_hist) + \ + sizeof(struct mali_c55_params_aexp_weights) + \ + sizeof(struct mali_c55_params_aexp_hist) + \ + sizeof(struct mali_c55_params_aexp_weights) + \ + sizeof(struct mali_c55_params_digital_gain) + \ + sizeof(struct mali_c55_params_awb_gains) + \ + sizeof(struct mali_c55_params_awb_config) + \ + sizeof(struct mali_c55_params_awb_gains) + \ + sizeof(struct mali_c55_params_mesh_shading_config) + \ + sizeof(struct mali_c55_params_mesh_shading_selection)) + +/** + * struct mali_c55_params_buffer - 3A configuration parameters + * + * This struct contains the configuration parameters of the Mali-C55 ISP + * algorithms, serialized by userspace into a data buffer. Each configuration + * parameter block is represented by a block-specific structure which contains a + * :c:type:`mali_c55_params_block_header` entry as first member. Userspace + * populates the @data buffer with configuration parameters for the blocks that + * it intends to configure. As a consequence, the data buffer effective size + * changes according to the number of ISP blocks that userspace intends to + * configure. + * + * The parameters buffer is versioned by the @version field to allow modifying + * and extending its definition. Userspace shall populate the @version field to + * inform the driver about the version it intends to use. The driver will parse + * and handle the @data buffer according to the data layout specific to the + * indicated version and return an error if the desired version is not + * supported. + * + * For each ISP block that userspace wants to configure, a block-specific + * structure is appended to the @data buffer, one after the other without gaps + * in between nor overlaps. Userspace shall populate the @total_size field with + * the effective size, in bytes, of the @data buffer. + * + * The expected memory layout of the parameters buffer is:: + * + * +-------------------- struct mali_c55_params_buffer ------------------+ + * | version = MALI_C55_PARAM_BUFFER_V1; | + * | total_size = sizeof(struct mali_c55_params_sensor_off_preshading) | + * | sizeof(struct mali_c55_params_aexp_hist); | + * | +------------------------- data ---------------------------------+ | + * | | +--------- struct mali_c55_params_sensor_off_preshading ------+ | | + * | | | +-------- struct mali_c55_params_block_header header -----+ | | | + * | | | | type = MALI_C55_PARAM_BLOCK_SENSOR_OFFS; | | | | + * | | | | flags = MALI_C55_PARAM_BLOCK_FL_NONE; | | | | + * | | | | size = | | | | + * | | | | sizeof(struct mali_c55_params_sensor_off_preshading);| | | | + * | | | +---------------------------------------------------------+ | | | + * | | | chan00 = ...; | | | + * | | | chan01 = ...; | | | + * | | | chan10 = ...; | | | + * | | | chan11 = ...; | | | + * | | +------------ struct mali_c55_params_aexp_hist ---------------+ | | + * | | | +-------- struct mali_c55_params_block_header header -----+ | | | + * | | | | type = MALI_C55_PARAM_BLOCK_AEXP_HIST; | | | | + * | | | | flags = MALI_C55_PARAM_BLOCK_FL_NONE; | | | | + * | | | | size = sizeof(struct mali_c55_params_aexp_hist); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | skip_x = ...; | | | + * | | | offset_x = ...; | | | + * | | | skip_y = ...; | | | + * | | | offset_y = ...; | | | + * | | | scale_bottom = ...; | | | + * | | | scale_top = ...; | | | + * | | | plane_mode = ...; | | | + * | | | tap_point = ...; | | | + * | | +-------------------------------------------------------------+ | | + * | +-----------------------------------------------------------------+ | + * +---------------------------------------------------------------------+ + * + * @version: The version from :c:type:`mali_c55_param_buffer_version` + * @total_size: The Mali-C55 configuration data effective size, excluding this + * header + * @data: The Mali-C55 configuration blocks data + */ +struct mali_c55_params_buffer { + __u8 version; + __u32 total_size; + __u8 data[MALI_C55_PARAMS_MAX_SIZE]; +}; + +#endif /* __UAPI_MALI_C55_CONFIG_H */ diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h index 0dfc11ee..bf467168 100644 --- a/include/linux/media-bus-format.h +++ b/include/linux/media-bus-format.h @@ -34,7 +34,7 @@ #define MEDIA_BUS_FMT_FIXED 0x0001 -/* RGB - next is 0x101e */ +/* RGB - next is 0x1027 */ #define MEDIA_BUS_FMT_RGB444_1X12 0x1016 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE 0x1001 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE 0x1002 @@ -46,8 +46,12 @@ #define MEDIA_BUS_FMT_RGB565_2X8_BE 0x1007 #define MEDIA_BUS_FMT_RGB565_2X8_LE 0x1008 #define MEDIA_BUS_FMT_RGB666_1X18 0x1009 +#define MEDIA_BUS_FMT_RGB666_2X9_BE 0x1025 +#define MEDIA_BUS_FMT_BGR666_1X18 0x1023 #define MEDIA_BUS_FMT_RBG888_1X24 0x100e #define MEDIA_BUS_FMT_RGB666_1X24_CPADHI 0x1015 +#define MEDIA_BUS_FMT_BGR666_1X24_CPADHI 0x1024 +#define MEDIA_BUS_FMT_RGB565_1X24_CPADHI 0x1022 #define MEDIA_BUS_FMT_RGB666_1X7X3_SPWG 0x1010 #define MEDIA_BUS_FMT_BGR888_1X24 0x1013 #define MEDIA_BUS_FMT_BGR888_3X8 0x101b @@ -59,13 +63,18 @@ #define MEDIA_BUS_FMT_RGB888_3X8_DELTA 0x101d #define MEDIA_BUS_FMT_RGB888_1X7X4_SPWG 0x1011 #define MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA 0x1012 +#define MEDIA_BUS_FMT_RGB666_1X30_CPADLO 0x101e +#define MEDIA_BUS_FMT_RGB888_1X30_CPADLO 0x101f #define MEDIA_BUS_FMT_ARGB8888_1X32 0x100d #define MEDIA_BUS_FMT_RGB888_1X32_PADHI 0x100f #define MEDIA_BUS_FMT_RGB101010_1X30 0x1018 +#define MEDIA_BUS_FMT_RGB666_1X36_CPADLO 0x1020 +#define MEDIA_BUS_FMT_RGB888_1X36_CPADLO 0x1021 #define MEDIA_BUS_FMT_RGB121212_1X36 0x1019 #define MEDIA_BUS_FMT_RGB161616_1X48 0x101a +#define MEDIA_BUS_FMT_RGB202020_1X60 0x1026 -/* YUV (including grey) - next is 0x202e */ +/* YUV (including grey) - next is 0x202f */ #define MEDIA_BUS_FMT_Y8_1X8 0x2001 #define MEDIA_BUS_FMT_UV8_1X8 0x2015 #define MEDIA_BUS_FMT_UYVY8_1_5X8 0x2002 @@ -88,6 +97,7 @@ #define MEDIA_BUS_FMT_YUYV12_2X12 0x201e #define MEDIA_BUS_FMT_YVYU12_2X12 0x201f #define MEDIA_BUS_FMT_Y14_1X14 0x202d +#define MEDIA_BUS_FMT_Y16_1X16 0x202e #define MEDIA_BUS_FMT_UYVY8_1X16 0x200f #define MEDIA_BUS_FMT_VYUY8_1X16 0x2010 #define MEDIA_BUS_FMT_YUYV8_1X16 0x2011 @@ -112,7 +122,7 @@ #define MEDIA_BUS_FMT_YUV16_1X48 0x202a #define MEDIA_BUS_FMT_UYYVYY16_0_5X48 0x202b -/* Bayer - next is 0x3021 */ +/* Bayer - next is 0x3025 */ #define MEDIA_BUS_FMT_SBGGR8_1X8 0x3001 #define MEDIA_BUS_FMT_SGBRG8_1X8 0x3013 #define MEDIA_BUS_FMT_SGRBG8_1X8 0x3002 @@ -145,6 +155,10 @@ #define MEDIA_BUS_FMT_SGBRG16_1X16 0x301e #define MEDIA_BUS_FMT_SGRBG16_1X16 0x301f #define MEDIA_BUS_FMT_SRGGB16_1X16 0x3020 +#define MEDIA_BUS_FMT_SBGGR20_1X20 0x3021 +#define MEDIA_BUS_FMT_SGBRG20_1X20 0x3022 +#define MEDIA_BUS_FMT_SGRBG20_1X20 0x3023 +#define MEDIA_BUS_FMT_SRGGB20_1X20 0x3024 /* JPEG compressed formats - next is 0x4002 */ #define MEDIA_BUS_FMT_JPEG_1X8 0x4001 @@ -165,4 +179,17 @@ */ #define MEDIA_BUS_FMT_METADATA_FIXED 0x7001 +/* Generic line based metadata formats for serial buses. Next is 0x8008. */ +#define MEDIA_BUS_FMT_META_8 0x8001 +#define MEDIA_BUS_FMT_META_10 0x8002 +#define MEDIA_BUS_FMT_META_12 0x8003 +#define MEDIA_BUS_FMT_META_14 0x8004 +#define MEDIA_BUS_FMT_META_16 0x8005 +#define MEDIA_BUS_FMT_META_20 0x8006 +#define MEDIA_BUS_FMT_META_24 0x8007 + +/* Specific metadata formats. Next is 0x9003. */ +#define MEDIA_BUS_FMT_CCS_EMBEDDED 0x9001 +#define MEDIA_BUS_FMT_OV2740_EMBEDDED 0x9002 + #endif /* __LINUX_MEDIA_BUS_FORMAT_H */ diff --git a/include/linux/media.h b/include/linux/media.h index e3123d1a..4a733b9b 100644 --- a/include/linux/media.h +++ b/include/linux/media.h @@ -20,7 +20,6 @@ #ifndef __LINUX_MEDIA_H #define __LINUX_MEDIA_H -#include <stdint.h> #include <linux/ioctl.h> #include <linux/types.h> @@ -141,8 +140,8 @@ struct media_device_info { #define MEDIA_ENT_F_DV_ENCODER (MEDIA_ENT_F_BASE + 0x6002) /* Entity flags */ -#define MEDIA_ENT_FL_DEFAULT (1 << 0) -#define MEDIA_ENT_FL_CONNECTOR (1 << 1) +#define MEDIA_ENT_FL_DEFAULT (1U << 0) +#define MEDIA_ENT_FL_CONNECTOR (1U << 1) /* OR with the entity id value to find the next entity */ #define MEDIA_ENT_ID_FLAG_NEXT (1U << 31) @@ -204,9 +203,10 @@ struct media_entity_desc { }; }; -#define MEDIA_PAD_FL_SINK (1 << 0) -#define MEDIA_PAD_FL_SOURCE (1 << 1) -#define MEDIA_PAD_FL_MUST_CONNECT (1 << 2) +#define MEDIA_PAD_FL_SINK (1U << 0) +#define MEDIA_PAD_FL_SOURCE (1U << 1) +#define MEDIA_PAD_FL_MUST_CONNECT (1U << 2) +#define MEDIA_PAD_FL_INTERNAL (1U << 3) struct media_pad_desc { __u32 entity; /* entity ID */ @@ -215,14 +215,14 @@ struct media_pad_desc { __u32 reserved[2]; }; -#define MEDIA_LNK_FL_ENABLED (1 << 0) -#define MEDIA_LNK_FL_IMMUTABLE (1 << 1) -#define MEDIA_LNK_FL_DYNAMIC (1 << 2) +#define MEDIA_LNK_FL_ENABLED (1U << 0) +#define MEDIA_LNK_FL_IMMUTABLE (1U << 1) +#define MEDIA_LNK_FL_DYNAMIC (1U << 2) #define MEDIA_LNK_FL_LINK_TYPE (0xf << 28) -# define MEDIA_LNK_FL_DATA_LINK (0 << 28) -# define MEDIA_LNK_FL_INTERFACE_LINK (1 << 28) -# define MEDIA_LNK_FL_ANCILLARY_LINK (2 << 28) +# define MEDIA_LNK_FL_DATA_LINK (0U << 28) +# define MEDIA_LNK_FL_INTERFACE_LINK (1U << 28) +# define MEDIA_LNK_FL_ANCILLARY_LINK (2U << 28) struct media_link_desc { struct media_pad_desc source; @@ -277,7 +277,7 @@ struct media_links_enum { * struct media_device_info. */ #define MEDIA_V2_ENTITY_HAS_FLAGS(media_version) \ - ((media_version) >= ((4 << 16) | (19 << 8) | 0)) + ((media_version) >= ((4U << 16) | (19U << 8) | 0U)) struct media_v2_entity { __u32 id; @@ -312,7 +312,7 @@ struct media_v2_interface { * struct media_device_info. */ #define MEDIA_V2_PAD_HAS_INDEX(media_version) \ - ((media_version) >= ((4 << 16) | (19 << 8) | 0)) + ((media_version) >= ((4U << 16) | (19U << 8) | 0U)) struct media_v2_pad { __u32 id; @@ -415,7 +415,7 @@ struct media_v2_topology { #define MEDIA_INTF_T_ALSA_TIMER (MEDIA_INTF_T_ALSA_BASE + 7) /* Obsolete symbol for media_version, no longer used in the kernel */ -#define MEDIA_API_VERSION ((0 << 16) | (1 << 8) | 0) +#define MEDIA_API_VERSION ((0U << 16) | (1U << 8) | 0U) #endif /* __LINUX_MEDIA_H */ diff --git a/include/linux/rkisp1-config.h b/include/linux/rkisp1-config.h index ec7cde8c..edbc6cb6 100644 --- a/include/linux/rkisp1-config.h +++ b/include/linux/rkisp1-config.h @@ -165,6 +165,11 @@ #define RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS 6 /* + * Compand + */ +#define RKISP1_CIF_ISP_COMPAND_NUM_POINTS 64 + +/* * Measurement types */ #define RKISP1_CIF_ISP_STAT_AWB (1U << 0) @@ -175,16 +180,21 @@ /** * enum rkisp1_cif_isp_version - ISP variants * - * @RKISP1_V10: used at least in rk3288 and rk3399 - * @RKISP1_V11: declared in the original vendor code, but not used - * @RKISP1_V12: used at least in rk3326 and px30 - * @RKISP1_V13: used at least in rk1808 + * @RKISP1_V10: Used at least in RK3288 and RK3399. + * @RKISP1_V11: Declared in the original vendor code, but not used. Same number + * of entries in grids and histogram as v10. + * @RKISP1_V12: Used at least in RK3326 and PX30. + * @RKISP1_V13: Used at least in RK1808. Same number of entries in grids and + * histogram as v12. + * @RKISP1_V_IMX8MP: Used in at least i.MX8MP. Same number of entries in grids + * and histogram as v10. */ enum rkisp1_cif_isp_version { RKISP1_V10 = 10, RKISP1_V11, RKISP1_V12, RKISP1_V13, + RKISP1_V_IMX8MP, }; enum rkisp1_cif_isp_histogram_mode { @@ -584,10 +594,9 @@ enum rkisp1_cif_isp_goc_mode { * as is reported by the hw_revision field of the struct media_device_info * that is returned by ioctl MEDIA_IOC_DEVICE_INFO. * - * Versions <= V11 have RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10 - * entries, versions >= V12 have RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12 - * entries. RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES is equal to the maximum - * of the two. + * V10 has RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10 entries, V12 has + * RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12 entries. + * RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES is equal to the maximum of the two. */ struct rkisp1_cif_isp_goc_config { __u32 mode; @@ -607,10 +616,10 @@ struct rkisp1_cif_isp_goc_config { * as is reported by the hw_revision field of the struct media_device_info * that is returned by ioctl MEDIA_IOC_DEVICE_INFO. * - * Versions <= V11 have RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10 - * entries, versions >= V12 have RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12 - * entries. RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE is equal to the maximum - * of the two. + * V10 has RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10 entries, V12 has + * RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12 entries. + * RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE is equal to the maximum of the + * two. */ struct rkisp1_cif_isp_hst_config { __u32 mode; @@ -847,6 +856,39 @@ struct rkisp1_params_cfg { struct rkisp1_cif_isp_isp_other_cfg others; }; +/** + * struct rkisp1_cif_isp_compand_bls_config - Rockchip ISP1 Companding parameters (BLS) + * @r: Fixed subtraction value for Bayer pattern R + * @gr: Fixed subtraction value for Bayer pattern Gr + * @gb: Fixed subtraction value for Bayer pattern Gb + * @b: Fixed subtraction value for Bayer pattern B + * + * The values will be subtracted from the sensor values. Note that unlike the + * dedicated BLS block, the BLS values in the compander are 20-bit unsigned. + */ +struct rkisp1_cif_isp_compand_bls_config { + __u32 r; + __u32 gr; + __u32 gb; + __u32 b; +}; + +/** + * struct rkisp1_cif_isp_compand_curve_config - Rockchip ISP1 Companding + * parameters (expand and compression curves) + * @px: Compand curve x-values. Each value stores the distance from the + * previous x-value, expressed as log2 of the distance on 5 bits. + * @x: Compand curve x-values. The functionality of these parameters are + * unknown due to do a lack of hardware documentation, but these are left + * here for future compatibility purposes. + * @y: Compand curve y-values + */ +struct rkisp1_cif_isp_compand_curve_config { + __u8 px[RKISP1_CIF_ISP_COMPAND_NUM_POINTS]; + __u32 x[RKISP1_CIF_ISP_COMPAND_NUM_POINTS]; + __u32 y[RKISP1_CIF_ISP_COMPAND_NUM_POINTS]; +}; + /*---------- PART2: Measurement Statistics ------------*/ /** @@ -902,9 +944,9 @@ struct rkisp1_cif_isp_bls_meas_val { * as is reported by the hw_revision field of the struct media_device_info * that is returned by ioctl MEDIA_IOC_DEVICE_INFO. * - * Versions <= V11 have RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries, - * versions >= V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries. - * RKISP1_CIF_ISP_AE_MEAN_MAX is equal to the maximum of the two. + * V10 has RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries, V12 has + * RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries. RKISP1_CIF_ISP_AE_MEAN_MAX is equal + * to the maximum of the two. * * Image is divided into 5x5 blocks on V10 and 9x9 blocks on V12. */ @@ -944,21 +986,21 @@ struct rkisp1_cif_isp_af_stat { * integer part. * * The window of the measurements area is divided to 5x5 sub-windows for - * V10/V11 and to 9x9 sub-windows for V12. The histogram is then computed for - * each sub-window independently and the final result is a weighted average of - * the histogram measurements on all sub-windows. The window of the - * measurements area and the weight of each sub-window are configurable using + * V10 and to 9x9 sub-windows for V12. The histogram is then computed for each + * sub-window independently and the final result is a weighted average of the + * histogram measurements on all sub-windows. The window of the measurements + * area and the weight of each sub-window are configurable using * struct @rkisp1_cif_isp_hst_config. * - * The histogram contains 16 bins in V10/V11 and 32 bins in V12/V13. + * The histogram contains 16 bins in V10 and 32 bins in V12. * * The number of entries of @hist_bins depends on the hardware revision * as is reported by the hw_revision field of the struct media_device_info * that is returned by ioctl MEDIA_IOC_DEVICE_INFO. * - * Versions <= V11 have RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10 entries, - * versions >= V12 have RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12 entries. - * RKISP1_CIF_ISP_HIST_BIN_N_MAX is equal to the maximum of the two. + * V10 has RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10 entries, V12 has + * RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12 entries. RKISP1_CIF_ISP_HIST_BIN_N_MAX is + * equal to the maximum of the two. */ struct rkisp1_cif_isp_hist_stat { __u32 hist_bins[RKISP1_CIF_ISP_HIST_BIN_N_MAX]; @@ -992,4 +1034,544 @@ struct rkisp1_stat_buffer { struct rkisp1_cif_isp_stat params; }; +/*---------- PART3: Extensible Configuration Parameters ------------*/ + +/** + * enum rkisp1_ext_params_block_type - RkISP1 extensible params block type + * + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS: Black level subtraction + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC: Defect pixel cluster correction + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG: Sensor de-gamma + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN: Auto white balance gains + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT: ISP filtering + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM: Bayer de-mosaic + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK: Cross-talk correction + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC: Gamma out correction + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF: De-noise pre-filter + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH: De-noise pre-filter strength + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC: Color processing + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_IE: Image effects + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC: Lens shading correction + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS: Auto white balance statistics + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS: Histogram statistics + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS: Auto exposure statistics + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS: Auto-focus statistics + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS: BLS in the compand block + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND: Companding expand curve + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS: Companding compress curve + */ +enum rkisp1_ext_params_block_type { + RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC, + RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG, + RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN, + RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT, + RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM, + RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK, + RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC, + RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF, + RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH, + RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC, + RKISP1_EXT_PARAMS_BLOCK_TYPE_IE, + RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC, + RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND, + RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS, +}; + +#define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0) +#define RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE (1U << 1) + +/** + * struct rkisp1_ext_params_block_header - RkISP1 extensible parameters block + * header + * + * This structure represents the common part of all the ISP configuration + * blocks. Each parameters block shall embed an instance of this structure type + * as its first member, followed by the block-specific configuration data. The + * driver inspects this common header to discern the block type and its size and + * properly handle the block content by casting it to the correct block-specific + * type. + * + * The @type field is one of the values enumerated by + * :c:type:`rkisp1_ext_params_block_type` and specifies how the data should be + * interpreted by the driver. The @size field specifies the size of the + * parameters block and is used by the driver for validation purposes. + * + * The @flags field is a bitmask of per-block flags RKISP1_EXT_PARAMS_FL_*. + * + * When userspace wants to configure and enable an ISP block it shall fully + * populate the block configuration and set the + * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE bit in the @flags field. + * + * When userspace simply wants to disable an ISP block the + * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bit should be set in @flags field. The + * driver ignores the rest of the block configuration structure in this case. + * + * If a new configuration of an ISP block has to be applied userspace shall + * fully populate the ISP block configuration and omit setting the + * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits + * in the @flags field. + * + * Setting both the RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and + * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits in the @flags field is not allowed + * and not accepted by the driver. + * + * Userspace is responsible for correctly populating the parameters block header + * fields (@type, @flags and @size) and the block-specific parameters. + * + * For example: + * + * .. code-block:: c + * + * void populate_bls(struct rkisp1_ext_params_block_header *block) { + * struct rkisp1_ext_params_bls_config *bls = + * (struct rkisp1_ext_params_bls_config *)block; + * + * bls->header.type = RKISP1_EXT_PARAMS_BLOCK_ID_BLS; + * bls->header.flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; + * bls->header.size = sizeof(*bls); + * + * bls->config.enable_auto = 0; + * bls->config.fixed_val.r = blackLevelRed_; + * bls->config.fixed_val.gr = blackLevelGreenR_; + * bls->config.fixed_val.gb = blackLevelGreenB_; + * bls->config.fixed_val.b = blackLevelBlue_; + * } + * + * @type: The parameters block type, see + * :c:type:`rkisp1_ext_params_block_type` + * @flags: A bitmask of block flags + * @size: Size (in bytes) of the parameters block, including this header + */ +struct rkisp1_ext_params_block_header { + __u16 type; + __u16 flags; + __u32 size; +}; + +/** + * struct rkisp1_ext_params_bls_config - RkISP1 extensible params BLS config + * + * RkISP1 extensible parameters Black Level Subtraction configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Black Level Subtraction configuration, see + * :c:type:`rkisp1_cif_isp_bls_config` + */ +struct rkisp1_ext_params_bls_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_bls_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_dpcc_config - RkISP1 extensible params DPCC config + * + * RkISP1 extensible parameters Defective Pixel Cluster Correction configuration + * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Defective Pixel Cluster Correction configuration, see + * :c:type:`rkisp1_cif_isp_dpcc_config` + */ +struct rkisp1_ext_params_dpcc_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_dpcc_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_sdg_config - RkISP1 extensible params SDG config + * + * RkISP1 extensible parameters Sensor Degamma configuration block. Identified + * by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Sensor Degamma configuration, see + * :c:type:`rkisp1_cif_isp_sdg_config` + */ +struct rkisp1_ext_params_sdg_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_sdg_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_lsc_config - RkISP1 extensible params LSC config + * + * RkISP1 extensible parameters Lens Shading Correction configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Lens Shading Correction configuration, see + * :c:type:`rkisp1_cif_isp_lsc_config` + */ +struct rkisp1_ext_params_lsc_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_lsc_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_awb_gain_config - RkISP1 extensible params AWB + * gain config + * + * RkISP1 extensible parameters Auto-White Balance Gains configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Auto-White Balance Gains configuration, see + * :c:type:`rkisp1_cif_isp_awb_gain_config` + */ +struct rkisp1_ext_params_awb_gain_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_awb_gain_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_flt_config - RkISP1 extensible params FLT config + * + * RkISP1 extensible parameters Filter configuration block. Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Filter configuration, see :c:type:`rkisp1_cif_isp_flt_config` + */ +struct rkisp1_ext_params_flt_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_flt_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_bdm_config - RkISP1 extensible params BDM config + * + * RkISP1 extensible parameters Demosaicing configuration block. Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Demosaicing configuration, see :c:type:`rkisp1_cif_isp_bdm_config` + */ +struct rkisp1_ext_params_bdm_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_bdm_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_ctk_config - RkISP1 extensible params CTK config + * + * RkISP1 extensible parameters Cross-Talk configuration block. Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Cross-Talk configuration, see :c:type:`rkisp1_cif_isp_ctk_config` + */ +struct rkisp1_ext_params_ctk_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_ctk_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_goc_config - RkISP1 extensible params GOC config + * + * RkISP1 extensible parameters Gamma-Out configuration block. Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Gamma-Out configuration, see :c:type:`rkisp1_cif_isp_goc_config` + */ +struct rkisp1_ext_params_goc_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_goc_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_dpf_config - RkISP1 extensible params DPF config + * + * RkISP1 extensible parameters De-noise Pre-Filter configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: De-noise Pre-Filter configuration, see + * :c:type:`rkisp1_cif_isp_dpf_config` + */ +struct rkisp1_ext_params_dpf_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_dpf_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_dpf_strength_config - RkISP1 extensible params DPF + * strength config + * + * RkISP1 extensible parameters De-noise Pre-Filter strength configuration + * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: De-noise Pre-Filter strength configuration, see + * :c:type:`rkisp1_cif_isp_dpf_strength_config` + */ +struct rkisp1_ext_params_dpf_strength_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_dpf_strength_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_cproc_config - RkISP1 extensible params CPROC config + * + * RkISP1 extensible parameters Color Processing configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Color processing configuration, see + * :c:type:`rkisp1_cif_isp_cproc_config` + */ +struct rkisp1_ext_params_cproc_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_cproc_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_ie_config - RkISP1 extensible params IE config + * + * RkISP1 extensible parameters Image Effect configuration block. Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_IE`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Image Effect configuration, see :c:type:`rkisp1_cif_isp_ie_config` + */ +struct rkisp1_ext_params_ie_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_ie_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_awb_meas_config - RkISP1 extensible params AWB + * Meas config + * + * RkISP1 extensible parameters Auto-White Balance Measurement configuration + * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Auto-White Balance measure configuration, see + * :c:type:`rkisp1_cif_isp_awb_meas_config` + */ +struct rkisp1_ext_params_awb_meas_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_awb_meas_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_hst_config - RkISP1 extensible params Histogram config + * + * RkISP1 extensible parameters Histogram statistics configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Histogram statistics configuration, see + * :c:type:`rkisp1_cif_isp_hst_config` + */ +struct rkisp1_ext_params_hst_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_hst_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_aec_config - RkISP1 extensible params AEC config + * + * RkISP1 extensible parameters Auto-Exposure statistics configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Auto-Exposure statistics configuration, see + * :c:type:`rkisp1_cif_isp_aec_config` + */ +struct rkisp1_ext_params_aec_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_aec_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_afc_config - RkISP1 extensible params AFC config + * + * RkISP1 extensible parameters Auto-Focus statistics configuration block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Auto-Focus statistics configuration, see + * :c:type:`rkisp1_cif_isp_afc_config` + */ +struct rkisp1_ext_params_afc_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_afc_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_compand_bls_config - RkISP1 extensible params + * Compand BLS config + * + * RkISP1 extensible parameters Companding configuration block (black level + * subtraction). Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Companding BLS configuration, see + * :c:type:`rkisp1_cif_isp_compand_bls_config` + */ +struct rkisp1_ext_params_compand_bls_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_compand_bls_config config; +} __attribute__((aligned(8))); + +/** + * struct rkisp1_ext_params_compand_curve_config - RkISP1 extensible params + * Compand curve config + * + * RkISP1 extensible parameters Companding configuration block (expand and + * compression curves). Identified by + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND` or + * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS`. + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: Companding curve configuration, see + * :c:type:`rkisp1_cif_isp_compand_curve_config` + */ +struct rkisp1_ext_params_compand_curve_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_compand_curve_config config; +} __attribute__((aligned(8))); + +/* + * The rkisp1_ext_params_compand_curve_config structure is counted twice as it + * is used for both the COMPAND_EXPAND and COMPAND_COMPRESS block types. + */ +#define RKISP1_EXT_PARAMS_MAX_SIZE \ + (sizeof(struct rkisp1_ext_params_bls_config) +\ + sizeof(struct rkisp1_ext_params_dpcc_config) +\ + sizeof(struct rkisp1_ext_params_sdg_config) +\ + sizeof(struct rkisp1_ext_params_lsc_config) +\ + sizeof(struct rkisp1_ext_params_awb_gain_config) +\ + sizeof(struct rkisp1_ext_params_flt_config) +\ + sizeof(struct rkisp1_ext_params_bdm_config) +\ + sizeof(struct rkisp1_ext_params_ctk_config) +\ + sizeof(struct rkisp1_ext_params_goc_config) +\ + sizeof(struct rkisp1_ext_params_dpf_config) +\ + sizeof(struct rkisp1_ext_params_dpf_strength_config) +\ + sizeof(struct rkisp1_ext_params_cproc_config) +\ + sizeof(struct rkisp1_ext_params_ie_config) +\ + sizeof(struct rkisp1_ext_params_awb_meas_config) +\ + sizeof(struct rkisp1_ext_params_hst_config) +\ + sizeof(struct rkisp1_ext_params_aec_config) +\ + sizeof(struct rkisp1_ext_params_afc_config) +\ + sizeof(struct rkisp1_ext_params_compand_bls_config) +\ + sizeof(struct rkisp1_ext_params_compand_curve_config) +\ + sizeof(struct rkisp1_ext_params_compand_curve_config)) + +/** + * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version + * + * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters + */ +enum rksip1_ext_param_buffer_version { + RKISP1_EXT_PARAM_BUFFER_V1 = 1, +}; + +/** + * struct rkisp1_ext_params_cfg - RkISP1 extensible parameters configuration + * + * This struct contains the configuration parameters of the RkISP1 ISP + * algorithms, serialized by userspace into a data buffer. Each configuration + * parameter block is represented by a block-specific structure which contains a + * :c:type:`rkisp1_ext_params_block_header` entry as first member. Userspace + * populates the @data buffer with configuration parameters for the blocks that + * it intends to configure. As a consequence, the data buffer effective size + * changes according to the number of ISP blocks that userspace intends to + * configure and is set by userspace in the @data_size field. + * + * The parameters buffer is versioned by the @version field to allow modifying + * and extending its definition. Userspace shall populate the @version field to + * inform the driver about the version it intends to use. The driver will parse + * and handle the @data buffer according to the data layout specific to the + * indicated version and return an error if the desired version is not + * supported. + * + * Currently the single RKISP1_EXT_PARAM_BUFFER_V1 version is supported. + * When a new format version will be added, a mechanism for userspace to query + * the supported format versions will be implemented in the form of a read-only + * V4L2 control. If such control is not available, userspace should assume only + * RKISP1_EXT_PARAM_BUFFER_V1 is supported by the driver. + * + * For each ISP block that userspace wants to configure, a block-specific + * structure is appended to the @data buffer, one after the other without gaps + * in between nor overlaps. Userspace shall populate the @data_size field with + * the effective size, in bytes, of the @data buffer. + * + * The expected memory layout of the parameters buffer is:: + * + * +-------------------- struct rkisp1_ext_params_cfg -------------------+ + * | version = RKISP_EXT_PARAMS_BUFFER_V1; | + * | data_size = sizeof(struct rkisp1_ext_params_bls_config) | + * | + sizeof(struct rkisp1_ext_params_dpcc_config); | + * | +------------------------- data ---------------------------------+ | + * | | +------------- struct rkisp1_ext_params_bls_config -----------+ | | + * | | | +-------- struct rkisp1_ext_params_block_header ---------+ | | | + * | | | | type = RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS; | | | | + * | | | | flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; | | | | + * | | | | size = sizeof(struct rkisp1_ext_params_bls_config); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | +---------- struct rkisp1_cif_isp_bls_config -------------+ | | | + * | | | | enable_auto = 0; | | | | + * | | | | fixed_val.r = 256; | | | | + * | | | | fixed_val.gr = 256; | | | | + * | | | | fixed_val.gb = 256; | | | | + * | | | | fixed_val.b = 256; | | | | + * | | | +---------------------------------------------------------+ | | | + * | | +------------ struct rkisp1_ext_params_dpcc_config -----------+ | | + * | | | +-------- struct rkisp1_ext_params_block_header ---------+ | | | + * | | | | type = RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC; | | | | + * | | | | flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; | | | | + * | | | | size = sizeof(struct rkisp1_ext_params_dpcc_config); | | | | + * | | | +---------------------------------------------------------+ | | | + * | | | +---------- struct rkisp1_cif_isp_dpcc_config ------------+ | | | + * | | | | mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE; | | | | + * | | | | output_mode = | | | | + * | | | | RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER; | | | | + * | | | | set_use = ... ; | | | | + * | | | | ... = ... ; | | | | + * | | | +---------------------------------------------------------+ | | | + * | | +-------------------------------------------------------------+ | | + * | +-----------------------------------------------------------------+ | + * +---------------------------------------------------------------------+ + * + * @version: The RkISP1 extensible parameters buffer version, see + * :c:type:`rksip1_ext_param_buffer_version` + * @data_size: The RkISP1 configuration data effective size, excluding this + * header + * @data: The RkISP1 extensible configuration data blocks + */ +struct rkisp1_ext_params_cfg { + __u32 version; + __u32 data_size; + __u8 data[RKISP1_EXT_PARAMS_MAX_SIZE]; +}; + #endif /* _RKISP1_CONFIG_H */ diff --git a/include/linux/udmabuf.h b/include/linux/udmabuf.h new file mode 100644 index 00000000..76cc7de9 --- /dev/null +++ b/include/linux/udmabuf.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_UDMABUF_H +#define _LINUX_UDMABUF_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#define UDMABUF_FLAGS_CLOEXEC 0x01 + +struct udmabuf_create { + __u32 memfd; + __u32 flags; + __u64 offset; + __u64 size; +}; + +struct udmabuf_create_item { + __u32 memfd; + __u32 __pad; + __u64 offset; + __u64 size; +}; + +struct udmabuf_create_list { + __u32 flags; + __u32 count; + struct udmabuf_create_item list[]; +}; + +#define UDMABUF_CREATE _IOW('u', 0x42, struct udmabuf_create) +#define UDMABUF_CREATE_LIST _IOW('u', 0x43, struct udmabuf_create_list) + +#endif /* _LINUX_UDMABUF_H */ diff --git a/include/linux/v4l2-common.h b/include/linux/v4l2-common.h index 14de1731..c3ca11e3 100644 --- a/include/linux/v4l2-common.h +++ b/include/linux/v4l2-common.h @@ -10,45 +10,6 @@ * * Copyright (C) 2012 Nokia Corporation * Contact: Sakari Ailus <sakari.ailus@iki.fi> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * */ #ifndef __V4L2_COMMON__ diff --git a/include/linux/v4l2-controls.h b/include/linux/v4l2-controls.h index 9d2a8237..882a8180 100644 --- a/include/linux/v4l2-controls.h +++ b/include/linux/v4l2-controls.h @@ -4,44 +4,6 @@ * * Copyright (C) 1999-2012 the contributors * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Alternatively you can redistribute this file under the terms of the - * BSD license as stated below: - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * 3. The names of its contributors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * * The contents of this header was split off from videodev2.h. All control * definitions should be added to this header, which is included by * videodev2.h. @@ -153,8 +115,10 @@ enum v4l2_colorfx { /* USER-class private control IDs */ -/* The base for the meye driver controls. See linux/meye.h for the list - * of controls. We reserve 16 controls for this driver. */ +/* + * The base for the meye driver controls. This driver was removed, but + * we keep this define in case any software still uses it. + */ #define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000) /* The base for the bttv driver controls. @@ -229,6 +193,30 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_ISL7998X_BASE (V4L2_CID_USER_BASE + 0x1180) +/* + * The base for DW100 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_DW100_BASE (V4L2_CID_USER_BASE + 0x1190) + +/* + * The base for Aspeed driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_ASPEED_BASE (V4L2_CID_USER_BASE + 0x11a0) + +/* + * The base for Nuvoton NPCM driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_NPCM_BASE (V4L2_CID_USER_BASE + 0x11b0) + +/* + * The base for THine THP7312 driver controls. + * We reserve 32 controls for this driver. + */ +#define V4L2_CID_USER_THP7312_BASE (V4L2_CID_USER_BASE + 0x11c0) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ @@ -828,6 +816,90 @@ enum v4l2_mpeg_video_frame_skip_mode { #define V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY (V4L2_CID_CODEC_BASE + 653) #define V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE (V4L2_CID_CODEC_BASE + 654) +#define V4L2_CID_MPEG_VIDEO_AV1_PROFILE (V4L2_CID_CODEC_BASE + 655) +/** + * enum v4l2_mpeg_video_av1_profile - AV1 profiles + * + * @V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN: compliant decoders must be able to decode + * streams with seq_profile equal to 0. + * @V4L2_MPEG_VIDEO_AV1_PROFILE_HIGH: compliant decoders must be able to decode + * streams with seq_profile equal less than or equal to 1. + * @V4L2_MPEG_VIDEO_AV1_PROFILE_PROFESSIONAL: compliant decoders must be able to + * decode streams with seq_profile less than or equal to 2. + * + * Conveys the highest profile a decoder can work with. + */ +enum v4l2_mpeg_video_av1_profile { + V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN = 0, + V4L2_MPEG_VIDEO_AV1_PROFILE_HIGH = 1, + V4L2_MPEG_VIDEO_AV1_PROFILE_PROFESSIONAL = 2, +}; + +#define V4L2_CID_MPEG_VIDEO_AV1_LEVEL (V4L2_CID_CODEC_BASE + 656) +/** + * enum v4l2_mpeg_video_av1_level - AV1 levels + * + * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_0: Level 2.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_1: Level 2.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_2: Level 2.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_2_3: Level 2.3. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_0: Level 3.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_1: Level 3.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_2: Level 3.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_3_3: Level 3.3. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_0: Level 4.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_1: Level 4.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_2: Level 4.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_4_3: Level 4.3. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_0: Level 5.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_1: Level 5.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_2: Level 5.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_5_3: Level 5.3. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_0: Level 6.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_1: Level 6.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_2: Level 6.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_6_3: Level 6.3. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_0: Level 7.0. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_1: Level 7.1. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_2: Level 7.2. + * @V4L2_MPEG_VIDEO_AV1_LEVEL_7_3: Level 7.3. + * + * Conveys the highest level a decoder can work with. + */ +enum v4l2_mpeg_video_av1_level { + V4L2_MPEG_VIDEO_AV1_LEVEL_2_0 = 0, + V4L2_MPEG_VIDEO_AV1_LEVEL_2_1 = 1, + V4L2_MPEG_VIDEO_AV1_LEVEL_2_2 = 2, + V4L2_MPEG_VIDEO_AV1_LEVEL_2_3 = 3, + + V4L2_MPEG_VIDEO_AV1_LEVEL_3_0 = 4, + V4L2_MPEG_VIDEO_AV1_LEVEL_3_1 = 5, + V4L2_MPEG_VIDEO_AV1_LEVEL_3_2 = 6, + V4L2_MPEG_VIDEO_AV1_LEVEL_3_3 = 7, + + V4L2_MPEG_VIDEO_AV1_LEVEL_4_0 = 8, + V4L2_MPEG_VIDEO_AV1_LEVEL_4_1 = 9, + V4L2_MPEG_VIDEO_AV1_LEVEL_4_2 = 10, + V4L2_MPEG_VIDEO_AV1_LEVEL_4_3 = 11, + + V4L2_MPEG_VIDEO_AV1_LEVEL_5_0 = 12, + V4L2_MPEG_VIDEO_AV1_LEVEL_5_1 = 13, + V4L2_MPEG_VIDEO_AV1_LEVEL_5_2 = 14, + V4L2_MPEG_VIDEO_AV1_LEVEL_5_3 = 15, + + V4L2_MPEG_VIDEO_AV1_LEVEL_6_0 = 16, + V4L2_MPEG_VIDEO_AV1_LEVEL_6_1 = 17, + V4L2_MPEG_VIDEO_AV1_LEVEL_6_2 = 18, + V4L2_MPEG_VIDEO_AV1_LEVEL_6_3 = 19, + + V4L2_MPEG_VIDEO_AV1_LEVEL_7_0 = 20, + V4L2_MPEG_VIDEO_AV1_LEVEL_7_1 = 21, + V4L2_MPEG_VIDEO_AV1_LEVEL_7_2 = 22, + V4L2_MPEG_VIDEO_AV1_LEVEL_7_3 = 23 +}; + +#define V4L2_CID_MPEG_VIDEO_AVERAGE_QP (V4L2_CID_CODEC_BASE + 657) + /* MPEG-class control IDs specific to the CX2341x driver as defined by V4L2 */ #define V4L2_CID_CODEC_CX2341X_BASE (V4L2_CTRL_CLASS_CODEC | 0x1000) #define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE (V4L2_CID_CODEC_CX2341X_BASE+0) @@ -1015,6 +1087,8 @@ enum v4l2_auto_focus_range { #define V4L2_CID_CAMERA_SENSOR_ROTATION (V4L2_CID_CAMERA_CLASS_BASE+35) +#define V4L2_CID_HDR_SENSOR_MODE (V4L2_CID_CAMERA_CLASS_BASE+36) + /* FM Modulator class control IDs */ #define V4L2_CID_FM_TX_CLASS_BASE (V4L2_CTRL_CLASS_FM_TX | 0x900) @@ -1732,7 +1806,7 @@ struct v4l2_vp8_segment { * @sharpness_level: matches sharpness_level syntax element. * @level: matches loop_filter_level syntax element. * @padding: padding field. Should be zeroed by applications. - * @flags: see V4L2_VP8_LF_FLAG_{}. + * @flags: see V4L2_VP8_LF_{}. * * This structure contains loop filter related parameters. * See the 'mb_lf_adjustments()' part of the frame header syntax, @@ -1999,6 +2073,469 @@ struct v4l2_ctrl_mpeg2_quantisation { __u8 chroma_non_intra_quantiser_matrix[64]; }; +#define V4L2_CID_STATELESS_HEVC_SPS (V4L2_CID_CODEC_STATELESS_BASE + 400) +#define V4L2_CID_STATELESS_HEVC_PPS (V4L2_CID_CODEC_STATELESS_BASE + 401) +#define V4L2_CID_STATELESS_HEVC_SLICE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 402) +#define V4L2_CID_STATELESS_HEVC_SCALING_MATRIX (V4L2_CID_CODEC_STATELESS_BASE + 403) +#define V4L2_CID_STATELESS_HEVC_DECODE_PARAMS (V4L2_CID_CODEC_STATELESS_BASE + 404) +#define V4L2_CID_STATELESS_HEVC_DECODE_MODE (V4L2_CID_CODEC_STATELESS_BASE + 405) +#define V4L2_CID_STATELESS_HEVC_START_CODE (V4L2_CID_CODEC_STATELESS_BASE + 406) +#define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407) + +enum v4l2_stateless_hevc_decode_mode { + V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, + V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, +}; + +enum v4l2_stateless_hevc_start_code { + V4L2_STATELESS_HEVC_START_CODE_NONE, + V4L2_STATELESS_HEVC_START_CODE_ANNEX_B, +}; + +#define V4L2_HEVC_SLICE_TYPE_B 0 +#define V4L2_HEVC_SLICE_TYPE_P 1 +#define V4L2_HEVC_SLICE_TYPE_I 2 + +#define V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE (1ULL << 0) +#define V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED (1ULL << 1) +#define V4L2_HEVC_SPS_FLAG_AMP_ENABLED (1ULL << 2) +#define V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET (1ULL << 3) +#define V4L2_HEVC_SPS_FLAG_PCM_ENABLED (1ULL << 4) +#define V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED (1ULL << 5) +#define V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT (1ULL << 6) +#define V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED (1ULL << 7) +#define V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED (1ULL << 8) + +/** + * struct v4l2_ctrl_hevc_sps - ITU-T Rec. H.265: Sequence parameter set + * + * @video_parameter_set_id: specifies the value of the + * vps_video_parameter_set_id of the active VPS + * @seq_parameter_set_id: provides an identifier for the SPS for + * reference by other syntax elements + * @pic_width_in_luma_samples: specifies the width of each decoded picture + * in units of luma samples + * @pic_height_in_luma_samples: specifies the height of each decoded picture + * in units of luma samples + * @bit_depth_luma_minus8: this value plus 8specifies the bit depth of the + * samples of the luma array + * @bit_depth_chroma_minus8: this value plus 8 specifies the bit depth of the + * samples of the chroma arrays + * @log2_max_pic_order_cnt_lsb_minus4: this value plus 4 specifies the value of + * the variable MaxPicOrderCntLsb + * @sps_max_dec_pic_buffering_minus1: this value plus 1 specifies the maximum + * required size of the decoded picture + * buffer for the codec video sequence + * @sps_max_num_reorder_pics: indicates the maximum allowed number of pictures + * @sps_max_latency_increase_plus1: not equal to 0 is used to compute the + * value of SpsMaxLatencyPictures array + * @log2_min_luma_coding_block_size_minus3: plus 3 specifies the minimum + * luma coding block size + * @log2_diff_max_min_luma_coding_block_size: specifies the difference between + * the maximum and minimum luma + * coding block size + * @log2_min_luma_transform_block_size_minus2: plus 2 specifies the minimum luma + * transform block size + * @log2_diff_max_min_luma_transform_block_size: specifies the difference between + * the maximum and minimum luma + * transform block size + * @max_transform_hierarchy_depth_inter: specifies the maximum hierarchy + * depth for transform units of + * coding units coded in inter + * prediction mode + * @max_transform_hierarchy_depth_intra: specifies the maximum hierarchy + * depth for transform units of + * coding units coded in intra + * prediction mode + * @pcm_sample_bit_depth_luma_minus1: this value plus 1 specifies the number of + * bits used to represent each of PCM sample + * values of the luma component + * @pcm_sample_bit_depth_chroma_minus1: this value plus 1 specifies the number + * of bits used to represent each of PCM + * sample values of the chroma components + * @log2_min_pcm_luma_coding_block_size_minus3: this value plus 3 specifies the + * minimum size of coding blocks + * @log2_diff_max_min_pcm_luma_coding_block_size: specifies the difference between + * the maximum and minimum size of + * coding blocks + * @num_short_term_ref_pic_sets: specifies the number of st_ref_pic_set() + * syntax structures included in the SPS + * @num_long_term_ref_pics_sps: specifies the number of candidate long-term + * reference pictures that are specified in the SPS + * @chroma_format_idc: specifies the chroma sampling + * @sps_max_sub_layers_minus1: this value plus 1 specifies the maximum number + * of temporal sub-layers + * @reserved: padding field. Should be zeroed by applications. + * @flags: see V4L2_HEVC_SPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_sps { + __u8 video_parameter_set_id; + __u8 seq_parameter_set_id; + __u16 pic_width_in_luma_samples; + __u16 pic_height_in_luma_samples; + __u8 bit_depth_luma_minus8; + __u8 bit_depth_chroma_minus8; + __u8 log2_max_pic_order_cnt_lsb_minus4; + __u8 sps_max_dec_pic_buffering_minus1; + __u8 sps_max_num_reorder_pics; + __u8 sps_max_latency_increase_plus1; + __u8 log2_min_luma_coding_block_size_minus3; + __u8 log2_diff_max_min_luma_coding_block_size; + __u8 log2_min_luma_transform_block_size_minus2; + __u8 log2_diff_max_min_luma_transform_block_size; + __u8 max_transform_hierarchy_depth_inter; + __u8 max_transform_hierarchy_depth_intra; + __u8 pcm_sample_bit_depth_luma_minus1; + __u8 pcm_sample_bit_depth_chroma_minus1; + __u8 log2_min_pcm_luma_coding_block_size_minus3; + __u8 log2_diff_max_min_pcm_luma_coding_block_size; + __u8 num_short_term_ref_pic_sets; + __u8 num_long_term_ref_pics_sps; + __u8 chroma_format_idc; + __u8 sps_max_sub_layers_minus1; + + __u8 reserved[6]; + __u64 flags; +}; + +#define V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED (1ULL << 0) +#define V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT (1ULL << 1) +#define V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED (1ULL << 2) +#define V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT (1ULL << 3) +#define V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED (1ULL << 4) +#define V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED (1ULL << 5) +#define V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED (1ULL << 6) +#define V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT (1ULL << 7) +#define V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED (1ULL << 8) +#define V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED (1ULL << 9) +#define V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED (1ULL << 10) +#define V4L2_HEVC_PPS_FLAG_TILES_ENABLED (1ULL << 11) +#define V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED (1ULL << 12) +#define V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED (1ULL << 13) +#define V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED (1ULL << 14) +#define V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED (1ULL << 15) +#define V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER (1ULL << 16) +#define V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT (1ULL << 17) +#define V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT (1ULL << 18) +#define V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT (1ULL << 19) +#define V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING (1ULL << 20) + +/** + * struct v4l2_ctrl_hevc_pps - ITU-T Rec. H.265: Picture parameter set + * + * @pic_parameter_set_id: identifies the PPS for reference by other + * syntax elements + * @num_extra_slice_header_bits: specifies the number of extra slice header + * bits that are present in the slice header RBSP + * for coded pictures referring to the PPS. + * @num_ref_idx_l0_default_active_minus1: this value plus 1 specifies the + * inferred value of num_ref_idx_l0_active_minus1 + * @num_ref_idx_l1_default_active_minus1: this value plus 1 specifies the + * inferred value of num_ref_idx_l1_active_minus1 + * @init_qp_minus26: this value plus 26 specifies the initial value of SliceQp Y for + * each slice referring to the PPS + * @diff_cu_qp_delta_depth: specifies the difference between the luma coding + * tree block size and the minimum luma coding block + * size of coding units that convey cu_qp_delta_abs + * and cu_qp_delta_sign_flag + * @pps_cb_qp_offset: specify the offsets to the luma quantization parameter Cb + * @pps_cr_qp_offset: specify the offsets to the luma quantization parameter Cr + * @num_tile_columns_minus1: this value plus 1 specifies the number of tile columns + * partitioning the picture + * @num_tile_rows_minus1: this value plus 1 specifies the number of tile rows partitioning + * the picture + * @column_width_minus1: this value plus 1 specifies the width of the each tile column in + * units of coding tree blocks + * @row_height_minus1: this value plus 1 specifies the height of the each tile row in + * units of coding tree blocks + * @pps_beta_offset_div2: specify the default deblocking parameter offsets for + * beta divided by 2 + * @pps_tc_offset_div2: specify the default deblocking parameter offsets for tC + * divided by 2 + * @log2_parallel_merge_level_minus2: this value plus 2 specifies the value of + * the variable Log2ParMrgLevel + * @reserved: padding field. Should be zeroed by applications. + * @flags: see V4L2_HEVC_PPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_pps { + __u8 pic_parameter_set_id; + __u8 num_extra_slice_header_bits; + __u8 num_ref_idx_l0_default_active_minus1; + __u8 num_ref_idx_l1_default_active_minus1; + __s8 init_qp_minus26; + __u8 diff_cu_qp_delta_depth; + __s8 pps_cb_qp_offset; + __s8 pps_cr_qp_offset; + __u8 num_tile_columns_minus1; + __u8 num_tile_rows_minus1; + __u8 column_width_minus1[20]; + __u8 row_height_minus1[22]; + __s8 pps_beta_offset_div2; + __s8 pps_tc_offset_div2; + __u8 log2_parallel_merge_level_minus2; + __u8 reserved; + __u64 flags; +}; + +#define V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE 0x01 + +#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME 0 +#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_FIELD 1 +#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_FIELD 2 +#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_BOTTOM 3 +#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_TOP 4 +#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_BOTTOM_TOP 5 +#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM 6 +#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME_DOUBLING 7 +#define V4L2_HEVC_SEI_PIC_STRUCT_FRAME_TRIPLING 8 +#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_PAIRED_PREVIOUS_BOTTOM 9 +#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_PAIRED_PREVIOUS_TOP 10 +#define V4L2_HEVC_SEI_PIC_STRUCT_TOP_PAIRED_NEXT_BOTTOM 11 +#define V4L2_HEVC_SEI_PIC_STRUCT_BOTTOM_PAIRED_NEXT_TOP 12 + +#define V4L2_HEVC_DPB_ENTRIES_NUM_MAX 16 + +/** + * struct v4l2_hevc_dpb_entry - HEVC decoded picture buffer entry + * + * @timestamp: timestamp of the V4L2 capture buffer to use as reference. + * @flags: long term flag for the reference frame + * @field_pic: whether the reference is a field picture or a frame. + * @reserved: padding field. Should be zeroed by applications. + * @pic_order_cnt_val: the picture order count of the current picture. + */ +struct v4l2_hevc_dpb_entry { + __u64 timestamp; + __u8 flags; + __u8 field_pic; + __u16 reserved; + __s32 pic_order_cnt_val; +}; + +/** + * struct v4l2_hevc_pred_weight_table - HEVC weighted prediction parameters + * + * @delta_luma_weight_l0: the difference of the weighting factor applied + * to the luma prediction value for list 0 + * @luma_offset_l0: the additive offset applied to the luma prediction value + * for list 0 + * @delta_chroma_weight_l0: the difference of the weighting factor applied + * to the chroma prediction values for list 0 + * @chroma_offset_l0: the difference of the additive offset applied to + * the chroma prediction values for list 0 + * @delta_luma_weight_l1: the difference of the weighting factor applied + * to the luma prediction value for list 1 + * @luma_offset_l1: the additive offset applied to the luma prediction value + * for list 1 + * @delta_chroma_weight_l1: the difference of the weighting factor applied + * to the chroma prediction values for list 1 + * @chroma_offset_l1: the difference of the additive offset applied to + * the chroma prediction values for list 1 + * @luma_log2_weight_denom: the base 2 logarithm of the denominator for + * all luma weighting factors + * @delta_chroma_log2_weight_denom: the difference of the base 2 logarithm + * of the denominator for all chroma + * weighting factors + */ +struct v4l2_hevc_pred_weight_table { + __s8 delta_luma_weight_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __s8 luma_offset_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __s8 delta_chroma_weight_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2]; + __s8 chroma_offset_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2]; + + __s8 delta_luma_weight_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __s8 luma_offset_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __s8 delta_chroma_weight_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2]; + __s8 chroma_offset_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX][2]; + + __u8 luma_log2_weight_denom; + __s8 delta_chroma_log2_weight_denom; +}; + +#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA (1ULL << 0) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA (1ULL << 1) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED (1ULL << 2) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO (1ULL << 3) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT (1ULL << 4) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0 (1ULL << 5) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_USE_INTEGER_MV (1ULL << 6) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED (1ULL << 7) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED (1ULL << 8) +#define V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT (1ULL << 9) + +/** + * struct v4l2_ctrl_hevc_slice_params - HEVC slice parameters + * + * This control is a dynamically sized 1-dimensional array, + * V4L2_CTRL_FLAG_DYNAMIC_ARRAY flag must be set when using it. + * + * @bit_size: size (in bits) of the current slice data + * @data_byte_offset: offset (in bytes) to the video data in the current slice data + * @num_entry_point_offsets: specifies the number of entry point offset syntax + * elements in the slice header. + * @nal_unit_type: specifies the coding type of the slice (B, P or I) + * @nuh_temporal_id_plus1: minus 1 specifies a temporal identifier for the NAL unit + * @slice_type: see V4L2_HEVC_SLICE_TYPE_{} + * @colour_plane_id: specifies the colour plane associated with the current slice + * @slice_pic_order_cnt: specifies the picture order count + * @num_ref_idx_l0_active_minus1: this value plus 1 specifies the maximum + * reference index for reference picture list 0 + * that may be used to decode the slice + * @num_ref_idx_l1_active_minus1: this value plus 1 specifies the maximum + * reference index for reference picture list 1 + * that may be used to decode the slice + * @collocated_ref_idx: specifies the reference index of the collocated picture used + * for temporal motion vector prediction + * @five_minus_max_num_merge_cand: specifies the maximum number of merging + * motion vector prediction candidates supported in + * the slice subtracted from 5 + * @slice_qp_delta: specifies the initial value of QpY to be used for the coding + * blocks in the slice + * @slice_cb_qp_offset: specifies a difference to be added to the value of pps_cb_qp_offset + * @slice_cr_qp_offset: specifies a difference to be added to the value of pps_cr_qp_offset + * @slice_act_y_qp_offset: screen content extension parameters + * @slice_act_cb_qp_offset: screen content extension parameters + * @slice_act_cr_qp_offset: screen content extension parameters + * @slice_beta_offset_div2: specify the deblocking parameter offsets for beta divided by 2 + * @slice_tc_offset_div2: specify the deblocking parameter offsets for tC divided by 2 + * @pic_struct: indicates whether a picture should be displayed as a frame or as one or + * more fields + * @reserved0: padding field. Should be zeroed by applications. + * @slice_segment_addr: specifies the address of the first coding tree block in + * the slice segment + * @ref_idx_l0: the list of L0 reference elements as indices in the DPB + * @ref_idx_l1: the list of L1 reference elements as indices in the DPB + * @short_term_ref_pic_set_size: specifies the size of short-term reference + * pictures set included in the SPS + * @long_term_ref_pic_set_size: specifies the size of long-term reference + * pictures set include in the SPS + * @pred_weight_table: the prediction weight coefficients for inter-picture + * prediction + * @reserved1: padding field. Should be zeroed by applications. + * @flags: see V4L2_HEVC_SLICE_PARAMS_FLAG_{} + */ +struct v4l2_ctrl_hevc_slice_params { + __u32 bit_size; + __u32 data_byte_offset; + __u32 num_entry_point_offsets; + + /* ISO/IEC 23008-2, ITU-T Rec. H.265: NAL unit header */ + __u8 nal_unit_type; + __u8 nuh_temporal_id_plus1; + + /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */ + __u8 slice_type; + __u8 colour_plane_id; + __s32 slice_pic_order_cnt; + __u8 num_ref_idx_l0_active_minus1; + __u8 num_ref_idx_l1_active_minus1; + __u8 collocated_ref_idx; + __u8 five_minus_max_num_merge_cand; + __s8 slice_qp_delta; + __s8 slice_cb_qp_offset; + __s8 slice_cr_qp_offset; + __s8 slice_act_y_qp_offset; + __s8 slice_act_cb_qp_offset; + __s8 slice_act_cr_qp_offset; + __s8 slice_beta_offset_div2; + __s8 slice_tc_offset_div2; + + /* ISO/IEC 23008-2, ITU-T Rec. H.265: Picture timing SEI message */ + __u8 pic_struct; + + __u8 reserved0[3]; + /* ISO/IEC 23008-2, ITU-T Rec. H.265: General slice segment header */ + __u32 slice_segment_addr; + __u8 ref_idx_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u8 ref_idx_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u16 short_term_ref_pic_set_size; + __u16 long_term_ref_pic_set_size; + + /* ISO/IEC 23008-2, ITU-T Rec. H.265: Weighted prediction parameter */ + struct v4l2_hevc_pred_weight_table pred_weight_table; + + __u8 reserved1[2]; + __u64 flags; +}; + +#define V4L2_HEVC_DECODE_PARAM_FLAG_IRAP_PIC 0x1 +#define V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC 0x2 +#define V4L2_HEVC_DECODE_PARAM_FLAG_NO_OUTPUT_OF_PRIOR 0x4 + +/** + * struct v4l2_ctrl_hevc_decode_params - HEVC decode parameters + * + * @pic_order_cnt_val: picture order count + * @short_term_ref_pic_set_size: specifies the size of short-term reference + * pictures set included in the SPS of the first slice + * @long_term_ref_pic_set_size: specifies the size of long-term reference + * pictures set include in the SPS of the first slice + * @num_active_dpb_entries: the number of entries in dpb + * @num_poc_st_curr_before: the number of reference pictures in the short-term + * set that come before the current frame + * @num_poc_st_curr_after: the number of reference pictures in the short-term + * set that come after the current frame + * @num_poc_lt_curr: the number of reference pictures in the long-term set + * @poc_st_curr_before: provides the index of the short term before references + * in DPB array + * @poc_st_curr_after: provides the index of the short term after references + * in DPB array + * @poc_lt_curr: provides the index of the long term references in DPB array + * @num_delta_pocs_of_ref_rps_idx: same as the derived value NumDeltaPocs[RefRpsIdx], + * can be used to parse the RPS data in slice headers + * instead of skipping it with @short_term_ref_pic_set_size. + * @reserved: padding field. Should be zeroed by applications. + * @dpb: the decoded picture buffer, for meta-data about reference frames + * @flags: see V4L2_HEVC_DECODE_PARAM_FLAG_{} + */ +struct v4l2_ctrl_hevc_decode_params { + __s32 pic_order_cnt_val; + __u16 short_term_ref_pic_set_size; + __u16 long_term_ref_pic_set_size; + __u8 num_active_dpb_entries; + __u8 num_poc_st_curr_before; + __u8 num_poc_st_curr_after; + __u8 num_poc_lt_curr; + __u8 poc_st_curr_before[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u8 poc_st_curr_after[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u8 poc_lt_curr[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u8 num_delta_pocs_of_ref_rps_idx; + __u8 reserved[3]; + struct v4l2_hevc_dpb_entry dpb[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]; + __u64 flags; +}; + +/** + * struct v4l2_ctrl_hevc_scaling_matrix - HEVC scaling lists parameters + * + * @scaling_list_4x4: scaling list is used for the scaling process for + * transform coefficients. The values on each scaling + * list are expected in raster scan order + * @scaling_list_8x8: scaling list is used for the scaling process for + * transform coefficients. The values on each scaling + * list are expected in raster scan order + * @scaling_list_16x16: scaling list is used for the scaling process for + * transform coefficients. The values on each scaling + * list are expected in raster scan order + * @scaling_list_32x32: scaling list is used for the scaling process for + * transform coefficients. The values on each scaling + * list are expected in raster scan order + * @scaling_list_dc_coef_16x16: scaling list is used for the scaling process + * for transform coefficients. The values on each + * scaling list are expected in raster scan order. + * @scaling_list_dc_coef_32x32: scaling list is used for the scaling process + * for transform coefficients. The values on each + * scaling list are expected in raster scan order. + */ +struct v4l2_ctrl_hevc_scaling_matrix { + __u8 scaling_list_4x4[6][16]; + __u8 scaling_list_8x8[6][64]; + __u8 scaling_list_16x16[6][64]; + __u8 scaling_list_32x32[2][64]; + __u8 scaling_list_dc_coef_16x16[6]; + __u8 scaling_list_dc_coef_32x32[2]; +}; + #define V4L2_CID_COLORIMETRY_CLASS_BASE (V4L2_CTRL_CLASS_COLORIMETRY | 0x900) #define V4L2_CID_COLORIMETRY_CLASS (V4L2_CTRL_CLASS_COLORIMETRY | 1) @@ -2317,6 +2854,645 @@ struct v4l2_ctrl_vp9_compressed_hdr { struct v4l2_vp9_mv_probs mv; }; +/* Stateless AV1 controls */ + +#define V4L2_AV1_TOTAL_REFS_PER_FRAME 8 +#define V4L2_AV1_CDEF_MAX 8 +#define V4L2_AV1_NUM_PLANES_MAX 3 /* 1 if monochrome, 3 otherwise */ +#define V4L2_AV1_MAX_SEGMENTS 8 +#define V4L2_AV1_MAX_OPERATING_POINTS (1 << 5) /* 5 bits to encode */ +#define V4L2_AV1_REFS_PER_FRAME 7 +#define V4L2_AV1_MAX_NUM_Y_POINTS (1 << 4) /* 4 bits to encode */ +#define V4L2_AV1_MAX_NUM_CB_POINTS (1 << 4) /* 4 bits to encode */ +#define V4L2_AV1_MAX_NUM_CR_POINTS (1 << 4) /* 4 bits to encode */ +#define V4L2_AV1_AR_COEFFS_SIZE 25 /* (2 * 3 * (3 + 1)) + 1 */ +#define V4L2_AV1_MAX_NUM_PLANES 3 +#define V4L2_AV1_MAX_TILE_COLS 64 +#define V4L2_AV1_MAX_TILE_ROWS 64 +#define V4L2_AV1_MAX_TILE_COUNT 512 + +#define V4L2_AV1_SEQUENCE_FLAG_STILL_PICTURE 0x00000001 +#define V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK 0x00000002 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_FILTER_INTRA 0x00000004 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTRA_EDGE_FILTER 0x00000008 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTERINTRA_COMPOUND 0x00000010 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_MASKED_COMPOUND 0x00000020 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_WARPED_MOTION 0x00000040 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_DUAL_FILTER 0x00000080 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT 0x00000100 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_JNT_COMP 0x00000200 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS 0x00000400 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_SUPERRES 0x00000800 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF 0x00001000 +#define V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION 0x00002000 +#define V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME 0x00004000 +#define V4L2_AV1_SEQUENCE_FLAG_COLOR_RANGE 0x00008000 +#define V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X 0x00010000 +#define V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y 0x00020000 +#define V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT 0x00040000 +#define V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q 0x00080000 + +#define V4L2_CID_STATELESS_AV1_SEQUENCE (V4L2_CID_CODEC_STATELESS_BASE + 500) +/** + * struct v4l2_ctrl_av1_sequence - AV1 Sequence + * + * Represents an AV1 Sequence OBU. See section 5.5 "Sequence header OBU syntax" + * for more details. + * + * @flags: See V4L2_AV1_SEQUENCE_FLAG_{}. + * @seq_profile: specifies the features that can be used in the coded video + * sequence. + * @order_hint_bits: specifies the number of bits used for the order_hint field + * at each frame. + * @bit_depth: the bitdepth to use for the sequence as described in section + * 5.5.2 "Color config syntax". + * @reserved: padding field. Should be zeroed by applications. + * @max_frame_width_minus_1: specifies the maximum frame width minus 1 for the + * frames represented by this sequence header. + * @max_frame_height_minus_1: specifies the maximum frame height minus 1 for the + * frames represented by this sequence header. + */ +struct v4l2_ctrl_av1_sequence { + __u32 flags; + __u8 seq_profile; + __u8 order_hint_bits; + __u8 bit_depth; + __u8 reserved; + __u16 max_frame_width_minus_1; + __u16 max_frame_height_minus_1; +}; + +#define V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY (V4L2_CID_CODEC_STATELESS_BASE + 501) +/** + * struct v4l2_ctrl_av1_tile_group_entry - AV1 Tile Group entry + * + * Represents a single AV1 tile inside an AV1 Tile Group. Note that MiRowStart, + * MiRowEnd, MiColStart and MiColEnd can be retrieved from struct + * v4l2_av1_tile_info in struct v4l2_ctrl_av1_frame using tile_row and + * tile_col. See section 6.10.1 "General tile group OBU semantics" for more + * details. + * + * @tile_offset: offset from the OBU data, i.e. where the coded tile data + * actually starts. + * @tile_size: specifies the size in bytes of the coded tile. Equivalent to + * "TileSize" in the AV1 Specification. + * @tile_row: specifies the row of the current tile. Equivalent to "TileRow" in + * the AV1 Specification. + * @tile_col: specifies the col of the current tile. Equivalent to "TileCol" in + * the AV1 Specification. + */ +struct v4l2_ctrl_av1_tile_group_entry { + __u32 tile_offset; + __u32 tile_size; + __u32 tile_row; + __u32 tile_col; +}; + +/** + * enum v4l2_av1_warp_model - AV1 Warp Model as described in section 3 + * "Symbols and abbreviated terms" of the AV1 Specification. + * + * @V4L2_AV1_WARP_MODEL_IDENTITY: Warp model is just an identity transform. + * @V4L2_AV1_WARP_MODEL_TRANSLATION: Warp model is a pure translation. + * @V4L2_AV1_WARP_MODEL_ROTZOOM: Warp model is a rotation + symmetric zoom + + * translation. + * @V4L2_AV1_WARP_MODEL_AFFINE: Warp model is a general affine transform. + */ +enum v4l2_av1_warp_model { + V4L2_AV1_WARP_MODEL_IDENTITY = 0, + V4L2_AV1_WARP_MODEL_TRANSLATION = 1, + V4L2_AV1_WARP_MODEL_ROTZOOM = 2, + V4L2_AV1_WARP_MODEL_AFFINE = 3, +}; + +/** + * enum v4l2_av1_reference_frame - AV1 reference frames + * + * @V4L2_AV1_REF_INTRA_FRAME: Intra Frame Reference + * @V4L2_AV1_REF_LAST_FRAME: Last Reference Frame + * @V4L2_AV1_REF_LAST2_FRAME: Last2 Reference Frame + * @V4L2_AV1_REF_LAST3_FRAME: Last3 Reference Frame + * @V4L2_AV1_REF_GOLDEN_FRAME: Golden Reference Frame + * @V4L2_AV1_REF_BWDREF_FRAME: BWD Reference Frame + * @V4L2_AV1_REF_ALTREF2_FRAME: Alternative2 Reference Frame + * @V4L2_AV1_REF_ALTREF_FRAME: Alternative Reference Frame + */ +enum v4l2_av1_reference_frame { + V4L2_AV1_REF_INTRA_FRAME = 0, + V4L2_AV1_REF_LAST_FRAME = 1, + V4L2_AV1_REF_LAST2_FRAME = 2, + V4L2_AV1_REF_LAST3_FRAME = 3, + V4L2_AV1_REF_GOLDEN_FRAME = 4, + V4L2_AV1_REF_BWDREF_FRAME = 5, + V4L2_AV1_REF_ALTREF2_FRAME = 6, + V4L2_AV1_REF_ALTREF_FRAME = 7, +}; + +#define V4L2_AV1_GLOBAL_MOTION_IS_INVALID(ref) (1 << (ref)) + +#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_GLOBAL 0x1 +#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_ROT_ZOOM 0x2 +#define V4L2_AV1_GLOBAL_MOTION_FLAG_IS_TRANSLATION 0x4 +/** + * struct v4l2_av1_global_motion - AV1 Global Motion parameters as described in + * section 6.8.17 "Global motion params semantics" of the AV1 specification. + * + * @flags: A bitfield containing the flags per reference frame. See + * V4L2_AV1_GLOBAL_MOTION_FLAG_{} + * @type: The type of global motion transform used. + * @params: this field has the same meaning as "gm_params" in the AV1 + * specification. + * @invalid: bitfield indicating whether the global motion params are invalid + * for a given reference frame. See section 7.11.3.6 Setup shear process and + * the variable "warpValid". Use V4L2_AV1_GLOBAL_MOTION_IS_INVALID(ref) to + * create a suitable mask. + * @reserved: padding field. Should be zeroed by applications. + */ + +struct v4l2_av1_global_motion { + __u8 flags[V4L2_AV1_TOTAL_REFS_PER_FRAME]; + enum v4l2_av1_warp_model type[V4L2_AV1_TOTAL_REFS_PER_FRAME]; + __s32 params[V4L2_AV1_TOTAL_REFS_PER_FRAME][6]; + __u8 invalid; + __u8 reserved[3]; +}; + +/** + * enum v4l2_av1_frame_restoration_type - AV1 Frame Restoration Type + * @V4L2_AV1_FRAME_RESTORE_NONE: no filtering is applied. + * @V4L2_AV1_FRAME_RESTORE_WIENER: Wiener filter process is invoked. + * @V4L2_AV1_FRAME_RESTORE_SGRPROJ: self guided filter process is invoked. + * @V4L2_AV1_FRAME_RESTORE_SWITCHABLE: restoration filter is swichtable. + */ +enum v4l2_av1_frame_restoration_type { + V4L2_AV1_FRAME_RESTORE_NONE = 0, + V4L2_AV1_FRAME_RESTORE_WIENER = 1, + V4L2_AV1_FRAME_RESTORE_SGRPROJ = 2, + V4L2_AV1_FRAME_RESTORE_SWITCHABLE = 3, +}; + +#define V4L2_AV1_LOOP_RESTORATION_FLAG_USES_LR 0x1 +#define V4L2_AV1_LOOP_RESTORATION_FLAG_USES_CHROMA_LR 0x2 + +/** + * struct v4l2_av1_loop_restoration - AV1 Loop Restauration as described in + * section 6.10.15 "Loop restoration params semantics" of the AV1 specification. + * + * @flags: See V4L2_AV1_LOOP_RESTORATION_FLAG_{}. + * @lr_unit_shift: specifies if the luma restoration size should be halved. + * @lr_uv_shift: specifies if the chroma size should be half the luma size. + * @reserved: padding field. Should be zeroed by applications. + * @frame_restoration_type: specifies the type of restoration used for each + * plane. See enum v4l2_av1_frame_restoration_type. + * @loop_restoration_size: specifies the size of loop restoration units in units + * of samples in the current plane. + */ +struct v4l2_av1_loop_restoration { + __u8 flags; + __u8 lr_unit_shift; + __u8 lr_uv_shift; + __u8 reserved; + enum v4l2_av1_frame_restoration_type frame_restoration_type[V4L2_AV1_NUM_PLANES_MAX]; + __u32 loop_restoration_size[V4L2_AV1_MAX_NUM_PLANES]; +}; + +/** + * struct v4l2_av1_cdef - AV1 CDEF params semantics as described in section + * 6.10.14 "CDEF params semantics" of the AV1 specification + * + * @damping_minus_3: controls the amount of damping in the deringing filter. + * @bits: specifies the number of bits needed to specify which CDEF filter to + * apply. + * @y_pri_strength: specifies the strength of the primary filter. + * @y_sec_strength: specifies the strength of the secondary filter. + * @uv_pri_strength: specifies the strength of the primary filter. + * @uv_sec_strength: specifies the strength of the secondary filter. + */ +struct v4l2_av1_cdef { + __u8 damping_minus_3; + __u8 bits; + __u8 y_pri_strength[V4L2_AV1_CDEF_MAX]; + __u8 y_sec_strength[V4L2_AV1_CDEF_MAX]; + __u8 uv_pri_strength[V4L2_AV1_CDEF_MAX]; + __u8 uv_sec_strength[V4L2_AV1_CDEF_MAX]; +}; + +#define V4L2_AV1_SEGMENTATION_FLAG_ENABLED 0x1 +#define V4L2_AV1_SEGMENTATION_FLAG_UPDATE_MAP 0x2 +#define V4L2_AV1_SEGMENTATION_FLAG_TEMPORAL_UPDATE 0x4 +#define V4L2_AV1_SEGMENTATION_FLAG_UPDATE_DATA 0x8 +#define V4L2_AV1_SEGMENTATION_FLAG_SEG_ID_PRE_SKIP 0x10 + +/** + * enum v4l2_av1_segment_feature - AV1 segment features as described in section + * 3 "Symbols and abbreviated terms" of the AV1 specification. + * + * @V4L2_AV1_SEG_LVL_ALT_Q: Index for quantizer segment feature. + * @V4L2_AV1_SEG_LVL_ALT_LF_Y_V: Index for vertical luma loop filter segment + * feature. + * @V4L2_AV1_SEG_LVL_REF_FRAME: Index for reference frame segment feature. + * @V4L2_AV1_SEG_LVL_REF_SKIP: Index for skip segment feature. + * @V4L2_AV1_SEG_LVL_REF_GLOBALMV: Index for global mv feature. + * @V4L2_AV1_SEG_LVL_MAX: Number of segment features. + */ +enum v4l2_av1_segment_feature { + V4L2_AV1_SEG_LVL_ALT_Q = 0, + V4L2_AV1_SEG_LVL_ALT_LF_Y_V = 1, + V4L2_AV1_SEG_LVL_REF_FRAME = 5, + V4L2_AV1_SEG_LVL_REF_SKIP = 6, + V4L2_AV1_SEG_LVL_REF_GLOBALMV = 7, + V4L2_AV1_SEG_LVL_MAX = 8 +}; + +#define V4L2_AV1_SEGMENT_FEATURE_ENABLED(id) (1 << (id)) + +/** + * struct v4l2_av1_segmentation - AV1 Segmentation params as defined in section + * 6.8.13 "Segmentation params semantics" of the AV1 specification. + * + * @flags: see V4L2_AV1_SEGMENTATION_FLAG_{}. + * @last_active_seg_id: indicates the highest numbered segment id that has some + * enabled feature. This is used when decoding the segment id to only decode + * choices corresponding to used segments. + * @feature_enabled: bitmask defining which features are enabled in each + * segment. Use V4L2_AV1_SEGMENT_FEATURE_ENABLED to build a suitable mask. + * @feature_data: data attached to each feature. Data entry is only valid if the + * feature is enabled + */ +struct v4l2_av1_segmentation { + __u8 flags; + __u8 last_active_seg_id; + __u8 feature_enabled[V4L2_AV1_MAX_SEGMENTS]; + __s16 feature_data[V4L2_AV1_MAX_SEGMENTS][V4L2_AV1_SEG_LVL_MAX]; +}; + +#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_ENABLED 0x1 +#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_UPDATE 0x2 +#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_PRESENT 0x4 +#define V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_MULTI 0x8 + +/** + * struct v4l2_av1_loop_filter - AV1 Loop filter params as defined in section + * 6.8.10 "Loop filter semantics" and 6.8.16 "Loop filter delta parameters + * semantics" of the AV1 specification. + * + * @flags: see V4L2_AV1_LOOP_FILTER_FLAG_{} + * @level: an array containing loop filter strength values. Different loop + * filter strength values from the array are used depending on the image plane + * being filtered, and the edge direction (vertical or horizontal) being + * filtered. + * @sharpness: indicates the sharpness level. The loop_filter_level and + * loop_filter_sharpness together determine when a block edge is filtered, and + * by how much the filtering can change the sample values. The loop filter + * process is described in section 7.14 of the AV1 specification. + * @ref_deltas: contains the adjustment needed for the filter level based on the + * chosen reference frame. If this syntax element is not present, it maintains + * its previous value. + * @mode_deltas: contains the adjustment needed for the filter level based on + * the chosen mode. If this syntax element is not present, it maintains its + * previous value. + * @delta_lf_res: specifies the left shift which should be applied to decoded + * loop filter delta values. + */ +struct v4l2_av1_loop_filter { + __u8 flags; + __u8 level[4]; + __u8 sharpness; + __s8 ref_deltas[V4L2_AV1_TOTAL_REFS_PER_FRAME]; + __s8 mode_deltas[2]; + __u8 delta_lf_res; +}; + +#define V4L2_AV1_QUANTIZATION_FLAG_DIFF_UV_DELTA 0x1 +#define V4L2_AV1_QUANTIZATION_FLAG_USING_QMATRIX 0x2 +#define V4L2_AV1_QUANTIZATION_FLAG_DELTA_Q_PRESENT 0x4 + +/** + * struct v4l2_av1_quantization - AV1 Quantization params as defined in section + * 6.8.11 "Quantization params semantics" of the AV1 specification. + * + * @flags: see V4L2_AV1_QUANTIZATION_FLAG_{} + * @base_q_idx: indicates the base frame qindex. This is used for Y AC + * coefficients and as the base value for the other quantizers. + * @delta_q_y_dc: indicates the Y DC quantizer relative to base_q_idx. + * @delta_q_u_dc: indicates the U DC quantizer relative to base_q_idx. + * @delta_q_u_ac: indicates the U AC quantizer relative to base_q_idx. + * @delta_q_v_dc: indicates the V DC quantizer relative to base_q_idx. + * @delta_q_v_ac: indicates the V AC quantizer relative to base_q_idx. + * @qm_y: specifies the level in the quantizer matrix that should be used for + * luma plane decoding. + * @qm_u: specifies the level in the quantizer matrix that should be used for + * chroma U plane decoding. + * @qm_v: specifies the level in the quantizer matrix that should be used for + * chroma V plane decoding. + * @delta_q_res: specifies the left shift which should be applied to decoded + * quantizer index delta values. + */ +struct v4l2_av1_quantization { + __u8 flags; + __u8 base_q_idx; + __s8 delta_q_y_dc; + __s8 delta_q_u_dc; + __s8 delta_q_u_ac; + __s8 delta_q_v_dc; + __s8 delta_q_v_ac; + __u8 qm_y; + __u8 qm_u; + __u8 qm_v; + __u8 delta_q_res; +}; + +#define V4L2_AV1_TILE_INFO_FLAG_UNIFORM_TILE_SPACING 0x1 + +/** + * struct v4l2_av1_tile_info - AV1 Tile info as defined in section 6.8.14 "Tile + * info semantics" of the AV1 specification. + * + * @flags: see V4L2_AV1_TILE_INFO_FLAG_{} + * @context_update_tile_id: specifies which tile to use for the CDF update. + * @tile_rows: specifies the number of tiles down the frame. + * @tile_cols: specifies the number of tiles across the frame. + * @mi_col_starts: an array specifying the start column (in units of 4x4 luma + * samples) for each tile across the image. + * @mi_row_starts: an array specifying the start row (in units of 4x4 luma + * samples) for each tile down the image. + * @width_in_sbs_minus_1: specifies the width of a tile minus 1 in units of + * superblocks. + * @height_in_sbs_minus_1: specifies the height of a tile minus 1 in units of + * superblocks. + * @tile_size_bytes: specifies the number of bytes needed to code each tile + * size. + * @reserved: padding field. Should be zeroed by applications. + */ +struct v4l2_av1_tile_info { + __u8 flags; + __u8 context_update_tile_id; + __u8 tile_cols; + __u8 tile_rows; + __u32 mi_col_starts[V4L2_AV1_MAX_TILE_COLS + 1]; + __u32 mi_row_starts[V4L2_AV1_MAX_TILE_ROWS + 1]; + __u32 width_in_sbs_minus_1[V4L2_AV1_MAX_TILE_COLS]; + __u32 height_in_sbs_minus_1[V4L2_AV1_MAX_TILE_ROWS]; + __u8 tile_size_bytes; + __u8 reserved[3]; +}; + +/** + * enum v4l2_av1_frame_type - AV1 Frame Type + * + * @V4L2_AV1_KEY_FRAME: Key frame + * @V4L2_AV1_INTER_FRAME: Inter frame + * @V4L2_AV1_INTRA_ONLY_FRAME: Intra-only frame + * @V4L2_AV1_SWITCH_FRAME: Switch frame + */ +enum v4l2_av1_frame_type { + V4L2_AV1_KEY_FRAME = 0, + V4L2_AV1_INTER_FRAME = 1, + V4L2_AV1_INTRA_ONLY_FRAME = 2, + V4L2_AV1_SWITCH_FRAME = 3 +}; + +/** + * enum v4l2_av1_interpolation_filter - AV1 interpolation filter types + * + * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP: eight tap filter + * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH: eight tap smooth filter + * @V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP: eight tap sharp filter + * @V4L2_AV1_INTERPOLATION_FILTER_BILINEAR: bilinear filter + * @V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE: filter selection is signaled at + * the block level + * + * See section 6.8.9 "Interpolation filter semantics" of the AV1 specification + * for more details. + */ +enum v4l2_av1_interpolation_filter { + V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP = 0, + V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH = 1, + V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP = 2, + V4L2_AV1_INTERPOLATION_FILTER_BILINEAR = 3, + V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE = 4, +}; + +/** + * enum v4l2_av1_tx_mode - AV1 Tx mode as described in section 6.8.21 "TX mode + * semantics" of the AV1 specification. + * @V4L2_AV1_TX_MODE_ONLY_4X4: the inverse transform will use only 4x4 + * transforms + * @V4L2_AV1_TX_MODE_LARGEST: the inverse transform will use the largest + * transform size that fits inside the block + * @V4L2_AV1_TX_MODE_SELECT: the choice of transform size is specified + * explicitly for each block. + */ +enum v4l2_av1_tx_mode { + V4L2_AV1_TX_MODE_ONLY_4X4 = 0, + V4L2_AV1_TX_MODE_LARGEST = 1, + V4L2_AV1_TX_MODE_SELECT = 2 +}; + +#define V4L2_AV1_FRAME_FLAG_SHOW_FRAME 0x00000001 +#define V4L2_AV1_FRAME_FLAG_SHOWABLE_FRAME 0x00000002 +#define V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE 0x00000004 +#define V4L2_AV1_FRAME_FLAG_DISABLE_CDF_UPDATE 0x00000008 +#define V4L2_AV1_FRAME_FLAG_ALLOW_SCREEN_CONTENT_TOOLS 0x00000010 +#define V4L2_AV1_FRAME_FLAG_FORCE_INTEGER_MV 0x00000020 +#define V4L2_AV1_FRAME_FLAG_ALLOW_INTRABC 0x00000040 +#define V4L2_AV1_FRAME_FLAG_USE_SUPERRES 0x00000080 +#define V4L2_AV1_FRAME_FLAG_ALLOW_HIGH_PRECISION_MV 0x00000100 +#define V4L2_AV1_FRAME_FLAG_IS_MOTION_MODE_SWITCHABLE 0x00000200 +#define V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS 0x00000400 +#define V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF 0x00000800 +#define V4L2_AV1_FRAME_FLAG_ALLOW_WARPED_MOTION 0x00001000 +#define V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT 0x00002000 +#define V4L2_AV1_FRAME_FLAG_REDUCED_TX_SET 0x00004000 +#define V4L2_AV1_FRAME_FLAG_SKIP_MODE_ALLOWED 0x00008000 +#define V4L2_AV1_FRAME_FLAG_SKIP_MODE_PRESENT 0x00010000 +#define V4L2_AV1_FRAME_FLAG_FRAME_SIZE_OVERRIDE 0x00020000 +#define V4L2_AV1_FRAME_FLAG_BUFFER_REMOVAL_TIME_PRESENT 0x00040000 +#define V4L2_AV1_FRAME_FLAG_FRAME_REFS_SHORT_SIGNALING 0x00080000 + +#define V4L2_CID_STATELESS_AV1_FRAME (V4L2_CID_CODEC_STATELESS_BASE + 502) +/** + * struct v4l2_ctrl_av1_frame - Represents an AV1 Frame Header OBU. + * + * @tile_info: tile info + * @quantization: quantization params + * @segmentation: segmentation params + * @superres_denom: the denominator for the upscaling ratio. + * @loop_filter: loop filter params + * @cdef: cdef params + * @skip_mode_frame: specifies the frames to use for compound prediction when + * skip_mode is equal to 1. + * @primary_ref_frame: specifies which reference frame contains the CDF values + * and other state that should be loaded at the start of the frame. + * @loop_restoration: loop restoration params + * @global_motion: global motion params + * @flags: see V4L2_AV1_FRAME_FLAG_{} + * @frame_type: specifies the AV1 frame type + * @order_hint: specifies OrderHintBits least significant bits of the expected + * output order for this frame. + * @upscaled_width: the upscaled width. + * @interpolation_filter: specifies the filter selection used for performing + * inter prediction. + * @tx_mode: specifies how the transform size is determined. + * @frame_width_minus_1: add 1 to get the frame's width. + * @frame_height_minus_1: add 1 to get the frame's height + * @render_width_minus_1: add 1 to get the render width of the frame in luma + * samples. + * @render_height_minus_1: add 1 to get the render height of the frame in luma + * samples. + * @current_frame_id: specifies the frame id number for the current frame. Frame + * id numbers are additional information that do not affect the decoding + * process, but provide decoders with a way of detecting missing reference + * frames so that appropriate action can be taken. + * @buffer_removal_time: specifies the frame removal time in units of DecCT clock + * ticks counted from the removal time of the last random access point for + * operating point opNum. + * @reserved: padding field. Should be zeroed by applications. + * @order_hints: specifies the expected output order hint for each reference + * frame. This field corresponds to the OrderHints variable from the + * specification (section 5.9.2 "Uncompressed header syntax"). As such, this is + * only used for non-intra frames and ignored otherwise. order_hints[0] is + * always ignored. + * @reference_frame_ts: the V4L2 timestamp of the reference frame slots. + * @ref_frame_idx: used to index into @reference_frame_ts when decoding + * inter-frames. The meaning of this array is the same as in the specification. + * The timestamp refers to the timestamp field in struct v4l2_buffer. Use + * v4l2_timeval_to_ns() to convert the struct timeval to a __u64. + * @refresh_frame_flags: contains a bitmask that specifies which reference frame + * slots will be updated with the current frame after it is decoded. + */ +struct v4l2_ctrl_av1_frame { + struct v4l2_av1_tile_info tile_info; + struct v4l2_av1_quantization quantization; + __u8 superres_denom; + struct v4l2_av1_segmentation segmentation; + struct v4l2_av1_loop_filter loop_filter; + struct v4l2_av1_cdef cdef; + __u8 skip_mode_frame[2]; + __u8 primary_ref_frame; + struct v4l2_av1_loop_restoration loop_restoration; + struct v4l2_av1_global_motion global_motion; + __u32 flags; + enum v4l2_av1_frame_type frame_type; + __u32 order_hint; + __u32 upscaled_width; + enum v4l2_av1_interpolation_filter interpolation_filter; + enum v4l2_av1_tx_mode tx_mode; + __u32 frame_width_minus_1; + __u32 frame_height_minus_1; + __u16 render_width_minus_1; + __u16 render_height_minus_1; + + __u32 current_frame_id; + __u32 buffer_removal_time[V4L2_AV1_MAX_OPERATING_POINTS]; + __u8 reserved[4]; + __u32 order_hints[V4L2_AV1_TOTAL_REFS_PER_FRAME]; + __u64 reference_frame_ts[V4L2_AV1_TOTAL_REFS_PER_FRAME]; + __s8 ref_frame_idx[V4L2_AV1_REFS_PER_FRAME]; + __u8 refresh_frame_flags; +}; + +#define V4L2_AV1_FILM_GRAIN_FLAG_APPLY_GRAIN 0x1 +#define V4L2_AV1_FILM_GRAIN_FLAG_UPDATE_GRAIN 0x2 +#define V4L2_AV1_FILM_GRAIN_FLAG_CHROMA_SCALING_FROM_LUMA 0x4 +#define V4L2_AV1_FILM_GRAIN_FLAG_OVERLAP 0x8 +#define V4L2_AV1_FILM_GRAIN_FLAG_CLIP_TO_RESTRICTED_RANGE 0x10 + +#define V4L2_CID_STATELESS_AV1_FILM_GRAIN (V4L2_CID_CODEC_STATELESS_BASE + 505) +/** + * struct v4l2_ctrl_av1_film_grain - AV1 Film Grain parameters. + * + * Film grain parameters as specified by section 6.8.20 of the AV1 Specification. + * + * @flags: see V4L2_AV1_FILM_GRAIN_{}. + * @cr_mult: represents a multiplier for the cr component used in derivation of + * the input index to the cr component scaling function. + * @grain_seed: specifies the starting value for the pseudo-random numbers used + * during film grain synthesis. + * @film_grain_params_ref_idx: indicates which reference frame contains the + * film grain parameters to be used for this frame. + * @num_y_points: specifies the number of points for the piece-wise linear + * scaling function of the luma component. + * @point_y_value: represents the x (luma value) coordinate for the i-th point + * of the piecewise linear scaling function for luma component. The values are + * signaled on the scale of 0..255. In case of 10 bit video, these values + * correspond to luma values divided by 4. In case of 12 bit video, these values + * correspond to luma values divided by 16. + * @point_y_scaling: represents the scaling (output) value for the i-th point + * of the piecewise linear scaling function for luma component. + * @num_cb_points: specifies the number of points for the piece-wise linear + * scaling function of the cb component. + * @point_cb_value: represents the x coordinate for the i-th point of the + * piece-wise linear scaling function for cb component. The values are signaled + * on the scale of 0..255. + * @point_cb_scaling: represents the scaling (output) value for the i-th point + * of the piecewise linear scaling function for cb component. + * @num_cr_points: specifies represents the number of points for the piece-wise + * linear scaling function of the cr component. + * @point_cr_value: represents the x coordinate for the i-th point of the + * piece-wise linear scaling function for cr component. The values are signaled + * on the scale of 0..255. + * @point_cr_scaling: represents the scaling (output) value for the i-th point + * of the piecewise linear scaling function for cr component. + * @grain_scaling_minus_8: represents the shift – 8 applied to the values of the + * chroma component. The grain_scaling_minus_8 can take values of 0..3 and + * determines the range and quantization step of the standard deviation of film + * grain. + * @ar_coeff_lag: specifies the number of auto-regressive coefficients for luma + * and chroma. + * @ar_coeffs_y_plus_128: specifies auto-regressive coefficients used for the Y + * plane. + * @ar_coeffs_cb_plus_128: specifies auto-regressive coefficients used for the U + * plane. + * @ar_coeffs_cr_plus_128: specifies auto-regressive coefficients used for the V + * plane. + * @ar_coeff_shift_minus_6: specifies the range of the auto-regressive + * coefficients. Values of 0, 1, 2, and 3 correspond to the ranges for + * auto-regressive coefficients of [-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, + * 0.25) respectively. + * @grain_scale_shift: specifies how much the Gaussian random numbers should be + * scaled down during the grain synthesis process. + * @cb_mult: represents a multiplier for the cb component used in derivation of + * the input index to the cb component scaling function. + * @cb_luma_mult: represents a multiplier for the average luma component used in + * derivation of the input index to the cb component scaling function. + * @cr_luma_mult: represents a multiplier for the average luma component used in + * derivation of the input index to the cr component scaling function. + * @cb_offset: represents an offset used in derivation of the input index to the + * cb component scaling function. + * @cr_offset: represents an offset used in derivation of the input index to the + * cr component scaling function. + * @reserved: padding field. Should be zeroed by applications. + */ +struct v4l2_ctrl_av1_film_grain { + __u8 flags; + __u8 cr_mult; + __u16 grain_seed; + __u8 film_grain_params_ref_idx; + __u8 num_y_points; + __u8 point_y_value[V4L2_AV1_MAX_NUM_Y_POINTS]; + __u8 point_y_scaling[V4L2_AV1_MAX_NUM_Y_POINTS]; + __u8 num_cb_points; + __u8 point_cb_value[V4L2_AV1_MAX_NUM_CB_POINTS]; + __u8 point_cb_scaling[V4L2_AV1_MAX_NUM_CB_POINTS]; + __u8 num_cr_points; + __u8 point_cr_value[V4L2_AV1_MAX_NUM_CR_POINTS]; + __u8 point_cr_scaling[V4L2_AV1_MAX_NUM_CR_POINTS]; + __u8 grain_scaling_minus_8; + __u8 ar_coeff_lag; + __u8 ar_coeffs_y_plus_128[V4L2_AV1_AR_COEFFS_SIZE]; + __u8 ar_coeffs_cb_plus_128[V4L2_AV1_AR_COEFFS_SIZE]; + __u8 ar_coeffs_cr_plus_128[V4L2_AV1_AR_COEFFS_SIZE]; + __u8 ar_coeff_shift_minus_6; + __u8 grain_scale_shift; + __u8 cb_mult; + __u8 cb_luma_mult; + __u8 cr_luma_mult; + __u16 cb_offset; + __u16 cr_offset; + __u8 reserved[4]; +}; + /* MPEG-compression definitions kept for backwards compatibility */ #define V4L2_CTRL_CLASS_MPEG V4L2_CTRL_CLASS_CODEC #define V4L2_CID_MPEG_CLASS V4L2_CID_CODEC_CLASS diff --git a/include/linux/v4l2-mediabus.h b/include/linux/v4l2-mediabus.h index 846dadfb..097ef739 100644 --- a/include/linux/v4l2-mediabus.h +++ b/include/linux/v4l2-mediabus.h @@ -3,10 +3,6 @@ * Media Bus API header * * Copyright (C) 2009, Guennadi Liakhovetski <g.liakhovetski@gmx.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef __LINUX_V4L2_MEDIABUS_H @@ -23,12 +19,18 @@ * @width: image width * @height: image height * @code: data format code (from enum v4l2_mbus_pixelcode) - * @field: used interlacing type (from enum v4l2_field) - * @colorspace: colorspace of the data (from enum v4l2_colorspace) - * @ycbcr_enc: YCbCr encoding of the data (from enum v4l2_ycbcr_encoding) - * @hsv_enc: HSV encoding of the data (from enum v4l2_hsv_encoding) - * @quantization: quantization of the data (from enum v4l2_quantization) - * @xfer_func: transfer function of the data (from enum v4l2_xfer_func) + * @field: used interlacing type (from enum v4l2_field), zero for metadata + * mbus codes + * @colorspace: colorspace of the data (from enum v4l2_colorspace), zero on + * metadata mbus codes + * @ycbcr_enc: YCbCr encoding of the data (from enum v4l2_ycbcr_encoding), zero + * for metadata mbus codes + * @hsv_enc: HSV encoding of the data (from enum v4l2_hsv_encoding), zero for + * metadata mbus codes + * @quantization: quantization of the data (from enum v4l2_quantization), zero + * for metadata mbus codes + * @xfer_func: transfer function of the data (from enum v4l2_xfer_func), zero + * for metadata mbus codes * @flags: flags (V4L2_MBUS_FRAMEFMT_*) * @reserved: reserved bytes that can be later used */ diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h index 480891db..839b1329 100644 --- a/include/linux/v4l2-subdev.h +++ b/include/linux/v4l2-subdev.h @@ -6,24 +6,12 @@ * * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> * Sakari Ailus <sakari.ailus@iki.fi> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __LINUX_V4L2_SUBDEV_H #define __LINUX_V4L2_SUBDEV_H +#include <linux/const.h> #include <linux/ioctl.h> #include <linux/types.h> #include <linux/v4l2-common.h> @@ -62,6 +50,10 @@ struct v4l2_subdev_format { * @rect: pad crop rectangle boundaries * @stream: stream number, defined in subdev routing * @reserved: drivers and applications must zero this array + * + * The subdev crop API is an obsolete interface and may be removed in the + * future. It is superseded by the selection API. No new extensions to this + * structure will be accepted. */ struct v4l2_subdev_crop { __u32 which; @@ -128,13 +120,15 @@ struct v4l2_subdev_frame_size_enum { * @pad: pad number, as reported by the media API * @interval: frame interval in seconds * @stream: stream number, defined in subdev routing + * @which: interval type (from enum v4l2_subdev_format_whence) * @reserved: drivers and applications must zero this array */ struct v4l2_subdev_frame_interval { __u32 pad; struct v4l2_fract interval; __u32 stream; - __u32 reserved[8]; + __u32 which; + __u32 reserved[7]; }; /** @@ -145,7 +139,7 @@ struct v4l2_subdev_frame_interval { * @width: frame width in pixels * @height: frame height in pixels * @interval: frame interval in seconds - * @which: format type (from enum v4l2_subdev_format_whence) + * @which: interval type (from enum v4l2_subdev_format_whence) * @stream: stream number, defined in subdev routing * @reserved: drivers and applications must zero this array */ @@ -202,29 +196,19 @@ struct v4l2_subdev_capability { /* The v4l2 sub-device video device node is registered in read-only mode. */ #define V4L2_SUBDEV_CAP_RO_SUBDEV 0x00000001 -/* The v4l2 sub-device supports multiplexed streams. */ -#define V4L2_SUBDEV_CAP_MPLEXED 0x00000002 +/* The v4l2 sub-device supports routing and multiplexed streams. */ +#define V4L2_SUBDEV_CAP_STREAMS 0x00000002 /* * Is the route active? An active route will start when streaming is enabled * on a video node. */ -#define V4L2_SUBDEV_ROUTE_FL_ACTIVE _BITUL(0) - -/* - * Is the route immutable, i.e. can it be activated and inactivated? - * Set by the driver. - */ -#define V4L2_SUBDEV_ROUTE_FL_IMMUTABLE _BITUL(1) - +#define V4L2_SUBDEV_ROUTE_FL_ACTIVE (1U << 0) /* - * Is the route a source endpoint? A source endpoint route refers to a stream - * generated internally by the subdevice (usually a sensor), and thus there - * is no sink-side endpoint for the route. The sink_pad and sink_stream - * fields are unused. - * Set by the driver. + * Is the route immutable? The ACTIVE flag of an immutable route may not be + * unset. */ -#define V4L2_SUBDEV_ROUTE_FL_SOURCE _BITUL(2) +#define V4L2_SUBDEV_ROUTE_FL_IMMUTABLE (1U << 1) /** * struct v4l2_subdev_route - A route inside a subdev @@ -249,15 +233,44 @@ struct v4l2_subdev_route { * struct v4l2_subdev_routing - Subdev routing information * * @which: configuration type (from enum v4l2_subdev_format_whence) - * @num_routes: the total number of routes in the routes array + * @len_routes: the length of the routes array, in routes; set by the user, not + * modified by the kernel * @routes: pointer to the routes array + * @num_routes: the total number of routes, possibly more than fits in the + * routes array * @reserved: drivers and applications must zero this array */ struct v4l2_subdev_routing { __u32 which; - __u32 num_routes; + __u32 len_routes; __u64 routes; - __u32 reserved[6]; + __u32 num_routes; + __u32 reserved[11]; +}; + +/* + * The client is aware of streams. Setting this flag enables the use of 'stream' + * fields (referring to the stream number) with various ioctls. If this is not + * set (which is the default), the 'stream' fields will be forced to 0 by the + * kernel. + */ +#define V4L2_SUBDEV_CLIENT_CAP_STREAMS (1ULL << 0) + +/* + * The client is aware of the struct v4l2_subdev_frame_interval which field. If + * this is not set (which is the default), the which field is forced to + * V4L2_SUBDEV_FORMAT_ACTIVE by the kernel. + */ +#define V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH (1ULL << 1) + +/** + * struct v4l2_subdev_client_capability - Capabilities of the client accessing + * the subdev + * + * @capabilities: A bitmask of V4L2_SUBDEV_CLIENT_CAP_* flags. + */ +struct v4l2_subdev_client_capability { + __u64 capabilities; }; /* Backwards compatibility define --- to be removed */ @@ -277,6 +290,9 @@ struct v4l2_subdev_routing { #define VIDIOC_SUBDEV_S_SELECTION _IOWR('V', 62, struct v4l2_subdev_selection) #define VIDIOC_SUBDEV_G_ROUTING _IOWR('V', 38, struct v4l2_subdev_routing) #define VIDIOC_SUBDEV_S_ROUTING _IOWR('V', 39, struct v4l2_subdev_routing) +#define VIDIOC_SUBDEV_G_CLIENT_CAP _IOR('V', 101, struct v4l2_subdev_client_capability) +#define VIDIOC_SUBDEV_S_CLIENT_CAP _IOWR('V', 102, struct v4l2_subdev_client_capability) + /* The following ioctls are identical to the ioctls in videodev2.h */ #define VIDIOC_SUBDEV_G_STD _IOR('V', 23, v4l2_std_id) #define VIDIOC_SUBDEV_S_STD _IOW('V', 24, v4l2_std_id) diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index bfb315d6..317d063a 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -243,6 +243,7 @@ enum v4l2_colorspace { /* DCI-P3 colorspace, used by cinema projectors */ V4L2_COLORSPACE_DCI_P3 = 12, + }; /* @@ -474,7 +475,6 @@ struct v4l2_capability { #define V4L2_CAP_META_CAPTURE 0x00800000 /* Is a metadata capture device */ #define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */ -#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */ #define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */ #define V4L2_CAP_META_OUTPUT 0x08000000 /* Is a metadata output device */ @@ -549,6 +549,15 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X', 'B', '2', '4') /* 32 RGBX-8-8-8-8 */ #define V4L2_PIX_FMT_ARGB32 v4l2_fourcc('B', 'A', '2', '4') /* 32 ARGB-8-8-8-8 */ #define V4L2_PIX_FMT_XRGB32 v4l2_fourcc('B', 'X', '2', '4') /* 32 XRGB-8-8-8-8 */ +#define V4L2_PIX_FMT_RGBX1010102 v4l2_fourcc('R', 'X', '3', '0') /* 32 RGBX-10-10-10-2 */ +#define V4L2_PIX_FMT_RGBA1010102 v4l2_fourcc('R', 'A', '3', '0') /* 32 RGBA-10-10-10-2 */ +#define V4L2_PIX_FMT_ARGB2101010 v4l2_fourcc('A', 'R', '3', '0') /* 32 ARGB-2-10-10-10 */ + +/* RGB formats (6 or 8 bytes per pixel) */ +#define V4L2_PIX_FMT_BGR48_12 v4l2_fourcc('B', '3', '1', '2') /* 48 BGR 12-bit per component */ +#define V4L2_PIX_FMT_BGR48 v4l2_fourcc('B', 'G', 'R', '6') /* 48 BGR 16-bit per component */ +#define V4L2_PIX_FMT_RGB48 v4l2_fourcc('R', 'G', 'B', '6') /* 48 RGB 16-bit per component */ +#define V4L2_PIX_FMT_ABGR64_12 v4l2_fourcc('B', '4', '1', '2') /* 64 BGRA 12-bit per component */ /* Grey formats */ #define V4L2_PIX_FMT_GREY v4l2_fourcc('G', 'R', 'E', 'Y') /* 8 Greyscale */ @@ -556,6 +565,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_Y6 v4l2_fourcc('Y', '0', '6', ' ') /* 6 Greyscale */ #define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ') /* 10 Greyscale */ #define V4L2_PIX_FMT_Y12 v4l2_fourcc('Y', '1', '2', ' ') /* 12 Greyscale */ +#define V4L2_PIX_FMT_Y012 v4l2_fourcc('Y', '0', '1', '2') /* 12 Greyscale */ #define V4L2_PIX_FMT_Y14 v4l2_fourcc('Y', '1', '4', ' ') /* 14 Greyscale */ #define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ') /* 16 Greyscale */ #define V4L2_PIX_FMT_Y16_BE v4l2_fourcc_be('Y', '1', '6', ' ') /* 16 Greyscale BE */ @@ -564,6 +574,8 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_Y10BPACK v4l2_fourcc('Y', '1', '0', 'B') /* 10 Greyscale bit-packed */ #define V4L2_PIX_FMT_Y10P v4l2_fourcc('Y', '1', '0', 'P') /* 10 Greyscale, MIPI RAW10 packed */ #define V4L2_PIX_FMT_IPU3_Y10 v4l2_fourcc('i', 'p', '3', 'y') /* IPU3 packed 10-bit greyscale */ +#define V4L2_PIX_FMT_Y12P v4l2_fourcc('Y', '1', '2', 'P') /* 12 Greyscale, MIPI RAW12 packed */ +#define V4L2_PIX_FMT_Y14P v4l2_fourcc('Y', '1', '4', 'P') /* 14 Greyscale, MIPI RAW14 packed */ /* Palette formats */ #define V4L2_PIX_FMT_PAL8 v4l2_fourcc('P', 'A', 'L', '8') /* 8 8-bit palette */ @@ -590,6 +602,15 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_YUVA32 v4l2_fourcc('Y', 'U', 'V', 'A') /* 32 YUVA-8-8-8-8 */ #define V4L2_PIX_FMT_YUVX32 v4l2_fourcc('Y', 'U', 'V', 'X') /* 32 YUVX-8-8-8-8 */ #define V4L2_PIX_FMT_M420 v4l2_fourcc('M', '4', '2', '0') /* 12 YUV 4:2:0 2 lines y, 1 line uv interleaved */ +#define V4L2_PIX_FMT_YUV48_12 v4l2_fourcc('Y', '3', '1', '2') /* 48 YUV 4:4:4 12-bit per component */ + +/* + * YCbCr packed format. For each Y2xx format, xx bits of valid data occupy the MSBs + * of the 16 bit components, and 16-xx bits of zero padding occupy the LSBs. + */ +#define V4L2_PIX_FMT_Y210 v4l2_fourcc('Y', '2', '1', '0') /* 32 YUYV 4:2:2 */ +#define V4L2_PIX_FMT_Y212 v4l2_fourcc('Y', '2', '1', '2') /* 32 YUYV 4:2:2 */ +#define V4L2_PIX_FMT_Y216 v4l2_fourcc('Y', '2', '1', '6') /* 32 YUYV 4:2:2 */ /* two planes -- one Y, one Cr + Cb interleaved */ #define V4L2_PIX_FMT_NV12 v4l2_fourcc('N', 'V', '1', '2') /* 12 Y/CbCr 4:2:0 */ @@ -598,12 +619,15 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_NV61 v4l2_fourcc('N', 'V', '6', '1') /* 16 Y/CrCb 4:2:2 */ #define V4L2_PIX_FMT_NV24 v4l2_fourcc('N', 'V', '2', '4') /* 24 Y/CbCr 4:4:4 */ #define V4L2_PIX_FMT_NV42 v4l2_fourcc('N', 'V', '4', '2') /* 24 Y/CrCb 4:4:4 */ +#define V4L2_PIX_FMT_P010 v4l2_fourcc('P', '0', '1', '0') /* 24 Y/CbCr 4:2:0 10-bit per component */ +#define V4L2_PIX_FMT_P012 v4l2_fourcc('P', '0', '1', '2') /* 24 Y/CbCr 4:2:0 12-bit per component */ /* two non contiguous planes - one Y, one Cr + Cb interleaved */ #define V4L2_PIX_FMT_NV12M v4l2_fourcc('N', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 */ #define V4L2_PIX_FMT_NV21M v4l2_fourcc('N', 'M', '2', '1') /* 21 Y/CrCb 4:2:0 */ #define V4L2_PIX_FMT_NV16M v4l2_fourcc('N', 'M', '1', '6') /* 16 Y/CbCr 4:2:2 */ #define V4L2_PIX_FMT_NV61M v4l2_fourcc('N', 'M', '6', '1') /* 16 Y/CrCb 4:2:2 */ +#define V4L2_PIX_FMT_P012M v4l2_fourcc('P', 'M', '1', '2') /* 24 Y/CbCr 4:2:0 12-bit per component */ /* three planes - Y Cb, Cr */ #define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y', 'U', 'V', '9') /* 9 YUV 4:1:0 */ @@ -625,6 +649,10 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_NV12_4L4 v4l2_fourcc('V', 'T', '1', '2') /* 12 Y/CbCr 4:2:0 4x4 tiles */ #define V4L2_PIX_FMT_NV12_16L16 v4l2_fourcc('H', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 16x16 tiles */ #define V4L2_PIX_FMT_NV12_32L32 v4l2_fourcc('S', 'T', '1', '2') /* 12 Y/CbCr 4:2:0 32x32 tiles */ +#define V4L2_PIX_FMT_NV15_4L4 v4l2_fourcc('V', 'T', '1', '5') /* 15 Y/CbCr 4:2:0 10-bit 4x4 tiles */ +#define V4L2_PIX_FMT_P010_4L4 v4l2_fourcc('T', '0', '1', '0') /* 12 Y/CbCr 4:2:0 10-bit 4x4 macroblocks */ +#define V4L2_PIX_FMT_NV12_8L128 v4l2_fourcc('A', 'T', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */ +#define V4L2_PIX_FMT_NV12_10BE_8L128 v4l2_fourcc_be('A', 'X', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */ /* Tiled YUV formats, non contiguous planes */ #define V4L2_PIX_FMT_NV12MT v4l2_fourcc('T', 'M', '1', '2') /* 12 Y/CbCr 4:2:0 64x32 tiles */ @@ -707,6 +735,11 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_FWHT v4l2_fourcc('F', 'W', 'H', 'T') /* Fast Walsh Hadamard Transform (vicodec) */ #define V4L2_PIX_FMT_FWHT_STATELESS v4l2_fourcc('S', 'F', 'W', 'H') /* Stateless FWHT (vicodec) */ #define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4') /* H264 parsed slices */ +#define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5') /* HEVC parsed slices */ +#define V4L2_PIX_FMT_AV1_FRAME v4l2_fourcc('A', 'V', '1', 'F') /* AV1 parsed frame */ +#define V4L2_PIX_FMT_SPK v4l2_fourcc('S', 'P', 'K', '0') /* Sorenson Spark */ +#define V4L2_PIX_FMT_RV30 v4l2_fourcc('R', 'V', '3', '0') /* RealVideo 8 */ +#define V4L2_PIX_FMT_RV40 v4l2_fourcc('R', 'V', '4', '0') /* RealVideo 9 & 10 */ /* Vendor-specific formats */ #define V4L2_PIX_FMT_CPIA1 v4l2_fourcc('C', 'P', 'I', 'A') /* cpia1 YUV */ @@ -740,11 +773,15 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_Z16 v4l2_fourcc('Z', '1', '6', ' ') /* Depth data 16-bit */ #define V4L2_PIX_FMT_MT21C v4l2_fourcc('M', 'T', '2', '1') /* Mediatek compressed block mode */ #define V4L2_PIX_FMT_MM21 v4l2_fourcc('M', 'M', '2', '1') /* Mediatek 8-bit block mode, two non-contiguous planes */ +#define V4L2_PIX_FMT_MT2110T v4l2_fourcc('M', 'T', '2', 'T') /* Mediatek 10-bit block tile mode */ +#define V4L2_PIX_FMT_MT2110R v4l2_fourcc('M', 'T', '2', 'R') /* Mediatek 10-bit block raster mode */ #define V4L2_PIX_FMT_INZI v4l2_fourcc('I', 'N', 'Z', 'I') /* Intel Planar Greyscale 10-bit and Depth 16-bit */ #define V4L2_PIX_FMT_CNF4 v4l2_fourcc('C', 'N', 'F', '4') /* Intel 4-bit packed depth confidence information */ #define V4L2_PIX_FMT_HI240 v4l2_fourcc('H', 'I', '2', '4') /* BTTV 8-bit dithered RGB */ #define V4L2_PIX_FMT_QC08C v4l2_fourcc('Q', '0', '8', 'C') /* Qualcomm 8-bit compressed */ #define V4L2_PIX_FMT_QC10C v4l2_fourcc('Q', '1', '0', 'C') /* Qualcomm 10-bit compressed */ +#define V4L2_PIX_FMT_AJPG v4l2_fourcc('A', 'J', 'P', 'G') /* Aspeed JPEG */ +#define V4L2_PIX_FMT_HEXTILE v4l2_fourcc('H', 'X', 'T', 'L') /* Hextile compressed */ /* 10bit raw packed, 32 bytes for every 25 pixels, last LSB 6 bits unused */ #define V4L2_PIX_FMT_IPU3_SBGGR10 v4l2_fourcc('i', 'p', '3', 'b') /* IPU3 packed 10-bit BGGR bayer */ @@ -752,6 +789,18 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_IPU3_SGRBG10 v4l2_fourcc('i', 'p', '3', 'G') /* IPU3 packed 10-bit GRBG bayer */ #define V4L2_PIX_FMT_IPU3_SRGGB10 v4l2_fourcc('i', 'p', '3', 'r') /* IPU3 packed 10-bit RGGB bayer */ +/* Raspberry Pi PiSP compressed formats. */ +#define V4L2_PIX_FMT_PISP_COMP1_RGGB v4l2_fourcc('P', 'C', '1', 'R') /* PiSP 8-bit mode 1 compressed RGGB bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_GRBG v4l2_fourcc('P', 'C', '1', 'G') /* PiSP 8-bit mode 1 compressed GRBG bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_GBRG v4l2_fourcc('P', 'C', '1', 'g') /* PiSP 8-bit mode 1 compressed GBRG bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_BGGR v4l2_fourcc('P', 'C', '1', 'B') /* PiSP 8-bit mode 1 compressed BGGR bayer */ +#define V4L2_PIX_FMT_PISP_COMP1_MONO v4l2_fourcc('P', 'C', '1', 'M') /* PiSP 8-bit mode 1 compressed monochrome */ +#define V4L2_PIX_FMT_PISP_COMP2_RGGB v4l2_fourcc('P', 'C', '2', 'R') /* PiSP 8-bit mode 2 compressed RGGB bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_GRBG v4l2_fourcc('P', 'C', '2', 'G') /* PiSP 8-bit mode 2 compressed GRBG bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_GBRG v4l2_fourcc('P', 'C', '2', 'g') /* PiSP 8-bit mode 2 compressed GBRG bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_BGGR v4l2_fourcc('P', 'C', '2', 'B') /* PiSP 8-bit mode 2 compressed BGGR bayer */ +#define V4L2_PIX_FMT_PISP_COMP2_MONO v4l2_fourcc('P', 'C', '2', 'M') /* PiSP 8-bit mode 2 compressed monochrome */ + /* SDR formats - used only for Software Defined Radio devices */ #define V4L2_SDR_FMT_CU8 v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */ #define V4L2_SDR_FMT_CU16LE v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */ @@ -780,6 +829,31 @@ struct v4l2_pix_format { /* Vendor specific - used for RK_ISP1 camera sub-system */ #define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */ #define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */ +#define V4L2_META_FMT_RK_ISP1_EXT_PARAMS v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */ + +/* Vendor specific - used for RaspberryPi PiSP */ +#define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */ + +/* The metadata format identifier for FE configuration buffers. */ +#define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C') + +/* The metadata format identifier for FE stats buffers. */ +#define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S') + +#define V4L2_META_FMT_MALI_C55_PARAMS v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */ +#define V4L2_META_FMT_MALI_C55_3A_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ + +/* + * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when + * adding new ones! + */ +#define V4L2_META_FMT_GENERIC_8 v4l2_fourcc('M', 'E', 'T', '8') /* Generic 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_10 v4l2_fourcc('M', 'C', '1', 'A') /* 10-bit CSI-2 packed 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_12 v4l2_fourcc('M', 'C', '1', 'C') /* 12-bit CSI-2 packed 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_14 v4l2_fourcc('M', 'C', '1', 'E') /* 14-bit CSI-2 packed 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_16 v4l2_fourcc('M', 'C', '1', 'G') /* 16-bit CSI-2 packed 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_20 v4l2_fourcc('M', 'C', '1', 'K') /* 20-bit CSI-2 packed 8-bit metadata */ +#define V4L2_META_FMT_GENERIC_CSI2_24 v4l2_fourcc('M', 'C', '1', 'O') /* 24-bit CSI-2 packed 8-bit metadata */ /* priv field value to indicates that subsequent fields are valid. */ #define V4L2_PIX_FMT_PRIV_MAGIC 0xfeedcafe @@ -811,6 +885,7 @@ struct v4l2_fmtdesc { #define V4L2_FMT_FLAG_CSC_YCBCR_ENC 0x0080 #define V4L2_FMT_FLAG_CSC_HSV_ENC V4L2_FMT_FLAG_CSC_YCBCR_ENC #define V4L2_FMT_FLAG_CSC_QUANTIZATION 0x0100 +#define V4L2_FMT_FLAG_META_LINE_BASED 0x0200 /* Frame Size and frame rate enumeration */ /* @@ -960,18 +1035,20 @@ struct v4l2_requestbuffers { #define V4L2_BUF_CAP_SUPPORTS_ORPHANED_BUFS (1 << 4) #define V4L2_BUF_CAP_SUPPORTS_M2M_HOLD_CAPTURE_BUF (1 << 5) #define V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS (1 << 6) +#define V4L2_BUF_CAP_SUPPORTS_MAX_NUM_BUFFERS (1 << 7) +#define V4L2_BUF_CAP_SUPPORTS_REMOVE_BUFS (1 << 8) /** * struct v4l2_plane - plane info for multi-planar buffers * @bytesused: number of bytes occupied by data in the plane (payload) * @length: size of this plane (NOT the payload) in bytes - * @mem_offset: when memory in the associated struct v4l2_buffer is + * @m.mem_offset: when memory in the associated struct v4l2_buffer is * V4L2_MEMORY_MMAP, equals the offset from the start of * the device memory for this plane (or is a "cookie" that * should be passed to mmap() called on the video node) - * @userptr: when memory is V4L2_MEMORY_USERPTR, a userspace pointer + * @m.userptr: when memory is V4L2_MEMORY_USERPTR, a userspace pointer * pointing to this plane - * @fd: when memory is V4L2_MEMORY_DMABUF, a userspace file + * @m.fd: when memory is V4L2_MEMORY_DMABUF, a userspace file * descriptor associated with this plane * @m: union of @mem_offset, @userptr and @fd * @data_offset: offset in the plane to the start of data; usually 0, @@ -1009,14 +1086,14 @@ struct v4l2_plane { * @sequence: sequence count of this frame * @memory: enum v4l2_memory; the method, in which the actual video data is * passed - * @offset: for non-multiplanar buffers with memory == V4L2_MEMORY_MMAP; + * @m.offset: for non-multiplanar buffers with memory == V4L2_MEMORY_MMAP; * offset from the start of the device memory for this plane, * (or a "cookie" that should be passed to mmap() as offset) - * @userptr: for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR; + * @m.userptr: for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR; * a userspace pointer pointing to this buffer - * @fd: for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF; + * @m.fd: for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF; * a userspace file descriptor associated with this buffer - * @planes: for multiplanar buffers; userspace pointer to the array of plane + * @m.planes: for multiplanar buffers; userspace pointer to the array of plane * info structs for this buffer * @m: union of @offset, @userptr, @planes and @fd * @length: size in bytes of the buffer (NOT its payload) for single-plane @@ -1550,7 +1627,8 @@ struct v4l2_bt_timings { ((bt)->width + V4L2_DV_BT_BLANKING_WIDTH(bt)) #define V4L2_DV_BT_BLANKING_HEIGHT(bt) \ ((bt)->vfrontporch + (bt)->vsync + (bt)->vbackporch + \ - (bt)->il_vfrontporch + (bt)->il_vsync + (bt)->il_vbackporch) + ((bt)->interlaced ? \ + ((bt)->il_vfrontporch + (bt)->il_vsync + (bt)->il_vbackporch) : 0)) #define V4L2_DV_BT_FRAME_HEIGHT(bt) \ ((bt)->height + V4L2_DV_BT_BLANKING_HEIGHT(bt)) @@ -1641,7 +1719,7 @@ struct v4l2_input { __u8 name[32]; /* Label */ __u32 type; /* Type of input */ __u32 audioset; /* Associated audios (bitfield) */ - __u32 tuner; /* enum v4l2_tuner_type */ + __u32 tuner; /* Tuner index */ v4l2_std_id std; __u32 status; __u32 capabilities; @@ -1728,6 +1806,8 @@ struct v4l2_ext_control { __u8 *p_u8; __u16 *p_u16; __u32 *p_u32; + __s32 *p_s32; + __s64 *p_s64; struct v4l2_area *p_area; struct v4l2_ctrl_h264_sps *p_h264_sps; struct v4l2_ctrl_h264_pps *p_h264_pps; @@ -1742,8 +1822,19 @@ struct v4l2_ext_control { struct v4l2_ctrl_mpeg2_quantisation *p_mpeg2_quantisation; struct v4l2_ctrl_vp9_compressed_hdr *p_vp9_compressed_hdr_probs; struct v4l2_ctrl_vp9_frame *p_vp9_frame; + struct v4l2_ctrl_hevc_sps *p_hevc_sps; + struct v4l2_ctrl_hevc_pps *p_hevc_pps; + struct v4l2_ctrl_hevc_slice_params *p_hevc_slice_params; + struct v4l2_ctrl_hevc_scaling_matrix *p_hevc_scaling_matrix; + struct v4l2_ctrl_hevc_decode_params *p_hevc_decode_params; + struct v4l2_ctrl_av1_sequence *p_av1_sequence; + struct v4l2_ctrl_av1_tile_group_entry *p_av1_tile_group_entry; + struct v4l2_ctrl_av1_frame *p_av1_frame; + struct v4l2_ctrl_av1_film_grain *p_av1_film_grain; + struct v4l2_ctrl_hdr10_cll_info *p_hdr10_cll_info; + struct v4l2_ctrl_hdr10_mastering_display *p_hdr10_mastering_display; void *ptr; - }; + } __attribute__ ((packed)); } __attribute__ ((packed)); struct v4l2_ext_controls { @@ -1805,6 +1896,17 @@ enum v4l2_ctrl_type { V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR = 0x0260, V4L2_CTRL_TYPE_VP9_FRAME = 0x0261, + + V4L2_CTRL_TYPE_HEVC_SPS = 0x0270, + V4L2_CTRL_TYPE_HEVC_PPS = 0x0271, + V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS = 0x0272, + V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX = 0x0273, + V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS = 0x0274, + + V4L2_CTRL_TYPE_AV1_SEQUENCE = 0x280, + V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281, + V4L2_CTRL_TYPE_AV1_FRAME = 0x282, + V4L2_CTRL_TYPE_AV1_FILM_GRAIN = 0x283, }; /* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */ @@ -1860,6 +1962,7 @@ struct v4l2_querymenu { #define V4L2_CTRL_FLAG_HAS_PAYLOAD 0x0100 #define V4L2_CTRL_FLAG_EXECUTE_ON_WRITE 0x0200 #define V4L2_CTRL_FLAG_MODIFY_LAYOUT 0x0400 +#define V4L2_CTRL_FLAG_DYNAMIC_ARRAY 0x0800 /* Query flags, to be ORed with the control ID */ #define V4L2_CTRL_FLAG_NEXT_CTRL 0x80000000 @@ -2301,23 +2404,32 @@ struct v4l2_sdr_format { * struct v4l2_meta_format - metadata format definition * @dataformat: little endian four character code (fourcc) * @buffersize: maximum size in bytes required for data + * @width: number of data units of data per line (valid for line + * based formats only, see format documentation) + * @height: number of lines of data per buffer (valid for line based + * formats only) + * @bytesperline: offset between the beginnings of two adjacent lines in + * bytes (valid for line based formats only) */ struct v4l2_meta_format { __u32 dataformat; __u32 buffersize; + __u32 width; + __u32 height; + __u32 bytesperline; } __attribute__ ((packed)); /** * struct v4l2_format - stream data format - * @type: enum v4l2_buf_type; type of the data stream - * @pix: definition of an image format - * @pix_mp: definition of a multiplanar image format - * @win: definition of an overlaid image - * @vbi: raw VBI capture or output parameters - * @sliced: sliced VBI capture or output parameters - * @raw_data: placeholder for future extensions and custom formats - * @fmt: union of @pix, @pix_mp, @win, @vbi, @sliced, @sdr, @meta - * and @raw_data + * @type: enum v4l2_buf_type; type of the data stream + * @fmt.pix: definition of an image format + * @fmt.pix_mp: definition of a multiplanar image format + * @fmt.win: definition of an overlaid image + * @fmt.vbi: raw VBI capture or output parameters + * @fmt.sliced: sliced VBI capture or output parameters + * @fmt.raw_data: placeholder for future extensions and custom formats + * @fmt: union of @pix, @pix_mp, @win, @vbi, @sliced, @sdr, + * @meta and @raw_data */ struct v4l2_format { __u32 type; @@ -2367,6 +2479,7 @@ struct v4l2_event_vsync { #define V4L2_EVENT_CTRL_CH_VALUE (1 << 0) #define V4L2_EVENT_CTRL_CH_FLAGS (1 << 1) #define V4L2_EVENT_CTRL_CH_RANGE (1 << 2) +#define V4L2_EVENT_CTRL_CH_DIMENSIONS (1 << 3) struct v4l2_event_ctrl { __u32 changes; @@ -2489,6 +2602,9 @@ struct v4l2_dbg_chip_info { * @flags: additional buffer management attributes (ignored unless the * queue has V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS capability * and configured for MMAP streaming I/O). + * @max_num_buffers: if V4L2_BUF_CAP_SUPPORTS_MAX_NUM_BUFFERS capability flag is set + * this field indicate the maximum possible number of buffers + * for this queue. * @reserved: future extensions */ struct v4l2_create_buffers { @@ -2498,7 +2614,22 @@ struct v4l2_create_buffers { struct v4l2_format format; __u32 capabilities; __u32 flags; - __u32 reserved[6]; + __u32 max_num_buffers; + __u32 reserved[5]; +}; + +/** + * struct v4l2_remove_buffers - VIDIOC_REMOVE_BUFS argument + * @index: the first buffer to be removed + * @count: number of buffers to removed + * @type: enum v4l2_buf_type + * @reserved: future extensions + */ +struct v4l2_remove_buffers { + __u32 index; + __u32 count; + __u32 type; + __u32 reserved[13]; }; /* @@ -2600,6 +2731,8 @@ struct v4l2_create_buffers { #define VIDIOC_DBG_G_CHIP_INFO _IOWR('V', 102, struct v4l2_dbg_chip_info) #define VIDIOC_QUERY_EXT_CTRL _IOWR('V', 103, struct v4l2_query_ext_ctrl) +#define VIDIOC_REMOVE_BUFS _IOWR('V', 104, struct v4l2_remove_buffers) + /* Reminder: when adding new ioctls please add support for them to drivers/media/v4l2-core/v4l2-compat-ioctl32.c as well! */ @@ -2609,5 +2742,10 @@ struct v4l2_create_buffers { /* Deprecated definitions kept for backwards compatibility */ #define V4L2_PIX_FMT_HM12 V4L2_PIX_FMT_NV12_16L16 #define V4L2_PIX_FMT_SUNXI_TILED_NV12 V4L2_PIX_FMT_NV12_32L32 +/* + * This capability was never implemented, anyone using this cap should drop it + * from their code. + */ +#define V4L2_CAP_ASYNCIO 0x02000000 #endif /* __LINUX_VIDEODEV2_H */ diff --git a/meson.build b/meson.build index e9a1c7e3..9ba5e2ca 100644 --- a/meson.build +++ b/meson.build @@ -1,8 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 project('libcamera', 'c', 'cpp', - meson_version : '>= 0.57', - version : '0.1.0', + meson_version : '>= 0.63', + version : '0.5.0', default_options : [ 'werror=true', 'warning_level=2', @@ -74,6 +74,10 @@ cc = meson.get_compiler('c') cxx = meson.get_compiler('cpp') config_h = configuration_data() +if cc.has_header_symbol('fcntl.h', 'F_ADD_SEALS', prefix : '#define _GNU_SOURCE') + config_h.set('HAVE_FILE_SEALS', 1) +endif + if cc.has_header_symbol('unistd.h', 'issetugid') config_h.set('HAVE_ISSETUGID', 1) endif @@ -82,17 +86,35 @@ if cc.has_header_symbol('locale.h', 'locale_t', prefix : '#define _GNU_SOURCE') config_h.set('HAVE_LOCALE_T', 1) endif +if cc.has_header_symbol('sys/mman.h', 'memfd_create', prefix : '#define _GNU_SOURCE') + config_h.set('HAVE_MEMFD_CREATE', 1) +endif + +ioctl_posix_test = ''' +#include <sys/ioctl.h> +int ioctl (int, int, ...); +''' + +if cc.compiles(ioctl_posix_test) + config_h.set('HAVE_POSIX_IOCTL', 1) +endif + if cc.has_header_symbol('stdlib.h', 'secure_getenv', prefix : '#define _GNU_SOURCE') config_h.set('HAVE_SECURE_GETENV', 1) endif common_arguments = [ + '-Wmissing-declarations', '-Wshadow', '-include', meson.current_build_dir() / 'config.h', ] c_arguments = [] -cpp_arguments = [] +cpp_arguments = [ + '-Wnon-virtual-dtor', +] + +cxx_stdlib = 'libstdc++' if cc.get_id() == 'clang' if cc.version().version_compare('<9') @@ -119,6 +141,7 @@ if cc.get_id() == 'clang' cpp_arguments += [ '-stdlib=libc++', ] + cxx_stdlib = 'libc++' endif cpp_arguments += [ @@ -128,16 +151,8 @@ if cc.get_id() == 'clang' endif if cc.get_id() == 'gcc' - if cc.version().version_compare('<8') - error('gcc version is too old, libcamera requires 8.0 or newer') - endif - - # On gcc 8, the file system library is provided in a separate static - # library. if cc.version().version_compare('<9') - cpp_arguments += [ - '-lstdc++fs', - ] + error('gcc version is too old, libcamera requires 9.0 or newer') endif # gcc 13 implements the C++23 version of automatic move from local @@ -191,30 +206,35 @@ liblttng = dependency('lttng-ust', required : get_option('tracing')) # Pipeline handlers # -pipelines = get_option('pipelines') +wanted_pipelines = get_option('pipelines') arch_arm = ['arm', 'aarch64'] arch_x86 = ['x86', 'x86_64'] pipelines_support = { 'imx8-isi': arch_arm, 'ipu3': arch_x86, + 'mali-c55': arch_arm, 'rkisp1': arch_arm, + 'rpi/pisp': arch_arm, 'rpi/vc4': arch_arm, - 'simple': arch_arm, + 'simple': ['any'], 'uvcvideo': ['any'], 'vimc': ['test'], + 'virtual': ['test'], } -if pipelines.contains('all') +if wanted_pipelines.contains('all') pipelines = pipelines_support.keys() -elif pipelines.contains('auto') +elif wanted_pipelines.contains('auto') host_cpu = host_machine.cpu_family() pipelines = [] foreach pipeline, archs : pipelines_support - if host_cpu in archs or 'any' in archs + if pipeline in wanted_pipelines or host_cpu in archs or 'any' in archs pipelines += pipeline endif endforeach +else + pipelines = wanted_pipelines endif # Tests require the vimc pipeline handler, include it automatically when tests @@ -267,6 +287,8 @@ py_mod.find_installation('python3', modules : py_modules) summary({ 'Enabled pipelines': pipelines, 'Enabled IPA modules': enabled_ipa_names, + 'Controls files': controls_files_names, + 'Properties files': properties_files_names, 'Hotplug support': libudev.found(), 'Tracing support': tracing_enabled, 'Android support': android_enabled, diff --git a/meson_options.txt b/meson_options.txt index fad928af..2104469e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -18,8 +18,14 @@ option('cam', option('documentation', type : 'feature', + value : 'auto', description : 'Generate the project documentation') +option('doc_werror', + type : 'boolean', + value : false, + description : 'Treat documentation warnings as errors') + option('gstreamer', type : 'feature', value : 'auto', @@ -27,7 +33,8 @@ option('gstreamer', option('ipas', type : 'array', - choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'], + choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple', + 'vimc'], description : 'Select which IPA modules to build') option('lc-compliance', @@ -43,14 +50,22 @@ option('pipelines', 'auto', 'imx8-isi', 'ipu3', + 'mali-c55', 'rkisp1', + 'rpi/pisp', 'rpi/vc4', 'simple', 'uvcvideo', - 'vimc' + 'vimc', + 'virtual' ], description : 'Select which pipeline handlers to build. If this is set to "auto", all the pipelines applicable to the target architecture will be built. If this is set to "all", all the pipelines will be built. If both are selected then "all" will take precedence.') +option('pycamera', + type : 'feature', + value : 'auto', + description : 'Enable libcamera Python bindings (experimental)') + option('qcam', type : 'feature', value : 'auto', @@ -72,11 +87,7 @@ option('udev', description : 'Enable udev support for hotplug') option('v4l2', - type : 'boolean', - value : false, - description : 'Compile the V4L2 compatibility layer') - -option('pycamera', type : 'feature', - value : 'disabled', - description : 'Enable libcamera Python bindings (experimental)') + value : 'auto', + description : 'Compile the V4L2 compatibility layer', + deprecated : {'true': 'enabled', 'false': 'disabled'}) diff --git a/src/android/camera3_hal.cpp b/src/android/camera3_hal.cpp index da836bae..a5ad2374 100644 --- a/src/android/camera3_hal.cpp +++ b/src/android/camera3_hal.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera3_hal.cpp - Android Camera HALv3 module + * Android Camera HALv3 module */ #include <hardware/camera_common.h> diff --git a/src/android/camera_buffer.h b/src/android/camera_buffer.h index b4531c80..96669962 100644 --- a/src/android/camera_buffer.h +++ b/src/android/camera_buffer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_buffer.h - Frame buffer handling interface definition + * Frame buffer handling interface definition */ #pragma once diff --git a/src/android/camera_capabilities.cpp b/src/android/camera_capabilities.cpp index 1bfeaea4..b161bc6b 100644 --- a/src/android/camera_capabilities.cpp +++ b/src/android/camera_capabilities.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_capabilities.cpp - Camera static properties manager + * Camera static properties manager */ #include "camera_capabilities.h" @@ -11,6 +11,7 @@ #include <array> #include <cmath> #include <map> +#include <stdint.h> #include <type_traits> #include <hardware/camera3.h> @@ -1081,7 +1082,7 @@ int CameraCapabilities::initializeStaticMetadata() } { - const Span<const Rectangle> &rects = + const Span<const Rectangle> rects = properties.get(properties::PixelArrayActiveAreas).value_or(Span<const Rectangle>{}); std::vector<int32_t> data{ static_cast<int32_t>(rects[0].x), @@ -1176,11 +1177,46 @@ int CameraCapabilities::initializeStaticMetadata() maxFrameDuration_); /* Statistics static metadata. */ - uint8_t faceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF; - staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, - faceDetectMode); - int32_t maxFaceCount = 0; + auto iter = camera_->controls().find(controls::draft::FaceDetectMode.id()); + if (iter != camera_->controls().end()) { + const ControlInfo &faceDetectCtrlInfo = iter->second; + std::vector<uint8_t> faceDetectModes; + bool hasFaceDetection = false; + + for (const auto &value : faceDetectCtrlInfo.values()) { + int32_t mode = value.get<int32_t>(); + uint8_t androidMode = 0; + + switch (mode) { + case controls::draft::FaceDetectModeOff: + androidMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF; + break; + case controls::draft::FaceDetectModeSimple: + androidMode = ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE; + hasFaceDetection = true; + break; + default: + LOG(HAL, Fatal) << "Received invalid face detect mode: " << mode; + } + faceDetectModes.push_back(androidMode); + } + if (hasFaceDetection) { + /* + * \todo Create new libcamera controls to query max + * possible faces detected. + */ + maxFaceCount = 10; + staticMetadata_->addEntry( + ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, + faceDetectModes.data(), faceDetectModes.size()); + } + } else { + uint8_t faceDetectMode = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF; + staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, + faceDetectMode); + } + staticMetadata_->addEntry(ANDROID_STATISTICS_INFO_MAX_FACE_COUNT, maxFaceCount); diff --git a/src/android/camera_capabilities.h b/src/android/camera_capabilities.h index 6f66f221..56ac1efe 100644 --- a/src/android/camera_capabilities.h +++ b/src/android/camera_capabilities.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_capabilities.h - Camera static properties manager + * Camera static properties manager */ #pragma once diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 25cedd44..a038131a 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_device.cpp - libcamera Android Camera Device + * libcamera Android Camera Device */ #include "camera_device.h" @@ -22,6 +22,7 @@ #include <libcamera/controls.h> #include <libcamera/fence.h> #include <libcamera/formats.h> +#include <libcamera/geometry.h> #include <libcamera/property_ids.h> #include "system/graphics.h" @@ -433,8 +434,6 @@ void CameraDevice::flush() void CameraDevice::stop() { MutexLocker stateLock(stateMutex_); - if (state_ == State::Stopped) - return; camera_->stop(); @@ -815,6 +814,11 @@ int CameraDevice::processControls(Camera3RequestDescriptor *descriptor) controls.set(controls::ScalerCrop, cropRegion); } + if (settings.getEntry(ANDROID_STATISTICS_FACE_DETECT_MODE, &entry)) { + const uint8_t *data = entry.data.u8; + controls.set(controls::draft::FaceDetectMode, data[0]); + } + if (settings.getEntry(ANDROID_SENSOR_TEST_PATTERN_MODE, &entry)) { const int32_t data = *entry.data.i32; int32_t testPatternMode = controls::draft::TestPatternModeOff; @@ -952,8 +956,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques */ if (camera3Request->settings) lastSettings_ = camera3Request->settings; - else - descriptor->settings_ = lastSettings_; + + descriptor->settings_ = lastSettings_; LOG(HAL, Debug) << "Queueing request " << descriptor->request_->cookie() << " with " << descriptor->buffers_.size() << " streams"; @@ -1108,6 +1112,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques } if (state_ == State::Stopped) { + lastSettings_ = {}; + ret = camera_->start(); if (ret) { LOG(HAL, Error) << "Failed to start camera"; @@ -1540,8 +1546,9 @@ CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) cons value32 = ANDROID_SENSOR_TEST_PATTERN_MODE_OFF; resultMetadata->addEntry(ANDROID_SENSOR_TEST_PATTERN_MODE, value32); - value = ANDROID_STATISTICS_FACE_DETECT_MODE_OFF; - resultMetadata->addEntry(ANDROID_STATISTICS_FACE_DETECT_MODE, value); + if (settings.getEntry(ANDROID_STATISTICS_FACE_DETECT_MODE, &entry)) + resultMetadata->addEntry(ANDROID_STATISTICS_FACE_DETECT_MODE, + entry.data.u8, 1); value = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF; resultMetadata->addEntry(ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, @@ -1580,6 +1587,61 @@ CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) cons resultMetadata->addEntry(ANDROID_SENSOR_FRAME_DURATION, *frameDuration * 1000); + const auto &faceDetectRectangles = + metadata.get(controls::draft::FaceDetectFaceRectangles); + if (faceDetectRectangles) { + std::vector<int32_t> flatRectangles; + for (const Rectangle &rect : *faceDetectRectangles) { + flatRectangles.push_back(rect.x); + flatRectangles.push_back(rect.y); + flatRectangles.push_back(rect.x + rect.width); + flatRectangles.push_back(rect.y + rect.height); + } + resultMetadata->addEntry( + ANDROID_STATISTICS_FACE_RECTANGLES, flatRectangles); + } + + const auto &faceDetectFaceScores = + metadata.get(controls::draft::FaceDetectFaceScores); + if (faceDetectRectangles && faceDetectFaceScores) { + if (faceDetectFaceScores->size() != faceDetectRectangles->size()) { + LOG(HAL, Error) << "Pipeline returned wrong number of face scores; " + << "Expected: " << faceDetectRectangles->size() + << ", got: " << faceDetectFaceScores->size(); + } + resultMetadata->addEntry(ANDROID_STATISTICS_FACE_SCORES, + *faceDetectFaceScores); + } + + const auto &faceDetectFaceLandmarks = + metadata.get(controls::draft::FaceDetectFaceLandmarks); + if (faceDetectRectangles && faceDetectFaceLandmarks) { + size_t expectedLandmarks = faceDetectRectangles->size() * 3; + if (faceDetectFaceLandmarks->size() != expectedLandmarks) { + LOG(HAL, Error) << "Pipeline returned wrong number of face landmarks; " + << "Expected: " << expectedLandmarks + << ", got: " << faceDetectFaceLandmarks->size(); + } + + std::vector<int32_t> androidLandmarks; + for (const Point &landmark : *faceDetectFaceLandmarks) { + androidLandmarks.push_back(landmark.x); + androidLandmarks.push_back(landmark.y); + } + resultMetadata->addEntry( + ANDROID_STATISTICS_FACE_LANDMARKS, androidLandmarks); + } + + const auto &faceDetectFaceIds = metadata.get(controls::draft::FaceDetectFaceIds); + if (faceDetectRectangles && faceDetectFaceIds) { + if (faceDetectFaceIds->size() != faceDetectRectangles->size()) { + LOG(HAL, Error) << "Pipeline returned wrong number of face ids; " + << "Expected: " << faceDetectRectangles->size() + << ", got: " << faceDetectFaceIds->size(); + } + resultMetadata->addEntry(ANDROID_STATISTICS_FACE_IDS, *faceDetectFaceIds); + } + const auto &scalerCrop = metadata.get(controls::ScalerCrop); if (scalerCrop) { const Rectangle &crop = *scalerCrop; diff --git a/src/android/camera_device.h b/src/android/camera_device.h index 43ee0159..194ca303 100644 --- a/src/android/camera_device.h +++ b/src/android/camera_device.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_device.h - libcamera Android Camera Device + * libcamera Android Camera Device */ #pragma once diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp index 0e7cde63..7ef451ef 100644 --- a/src/android/camera_hal_config.cpp +++ b/src/android/camera_hal_config.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_hal_config.cpp - Camera HAL configuration file manager + * Camera HAL configuration file manager */ #include "camera_hal_config.h" diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h index 9df554f9..a4bedb6e 100644 --- a/src/android/camera_hal_config.h +++ b/src/android/camera_hal_config.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_hal_config.h - Camera HAL configuration file manager + * Camera HAL configuration file manager */ #pragma once diff --git a/src/android/camera_hal_manager.cpp b/src/android/camera_hal_manager.cpp index a86e23d4..7500c749 100644 --- a/src/android/camera_hal_manager.cpp +++ b/src/android/camera_hal_manager.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_hal_manager.cpp - libcamera Android Camera Manager + * libcamera Android Camera Manager */ #include "camera_hal_manager.h" diff --git a/src/android/camera_hal_manager.h b/src/android/camera_hal_manager.h index a5f8b933..836a8daf 100644 --- a/src/android/camera_hal_manager.h +++ b/src/android/camera_hal_manager.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_hal_manager.h - libcamera Android Camera Manager + * libcamera Android Camera Manager */ #pragma once diff --git a/src/android/camera_metadata.cpp b/src/android/camera_metadata.cpp index b3e515d2..99f033f9 100644 --- a/src/android/camera_metadata.cpp +++ b/src/android/camera_metadata.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_metadata.cpp - libcamera Android Camera Metadata Helper + * libcamera Android Camera Metadata Helper */ #include "camera_metadata.h" diff --git a/src/android/camera_metadata.h b/src/android/camera_metadata.h index 0c31ec6b..474f280c 100644 --- a/src/android/camera_metadata.h +++ b/src/android/camera_metadata.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_metadata.h - libcamera Android Camera Metadata Helper + * libcamera Android Camera Metadata Helper */ #pragma once diff --git a/src/android/camera_ops.cpp b/src/android/camera_ops.cpp index 8a3cfa17..ecaac5a3 100644 --- a/src/android/camera_ops.cpp +++ b/src/android/camera_ops.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_ops.h - Android Camera HAL Operations + * Android Camera HAL Operations */ #include "camera_ops.h" diff --git a/src/android/camera_ops.h b/src/android/camera_ops.h index b501bb7e..750dc945 100644 --- a/src/android/camera_ops.h +++ b/src/android/camera_ops.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_ops.h - Android Camera HAL Operations + * Android Camera HAL Operations */ #pragma once diff --git a/src/android/camera_request.cpp b/src/android/camera_request.cpp index 6c87adba..0d45960d 100644 --- a/src/android/camera_request.cpp +++ b/src/android/camera_request.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Google Inc. * - * camera_request.cpp - libcamera Android Camera Request Descriptor + * libcamera Android Camera Request Descriptor */ #include "camera_request.h" diff --git a/src/android/camera_request.h b/src/android/camera_request.h index 20aba79d..5b479180 100644 --- a/src/android/camera_request.h +++ b/src/android/camera_request.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Google Inc. * - * camera_request.h - libcamera Android Camera Request Descriptor + * libcamera Android Camera Request Descriptor */ #pragma once diff --git a/src/android/camera_stream.cpp b/src/android/camera_stream.cpp index 045e6006..1d68540d 100644 --- a/src/android/camera_stream.cpp +++ b/src/android/camera_stream.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * camera_stream.cpp - Camera HAL stream + * Camera HAL stream */ #include "camera_stream.h" diff --git a/src/android/camera_stream.h b/src/android/camera_stream.h index 4c5078b2..395552da 100644 --- a/src/android/camera_stream.h +++ b/src/android/camera_stream.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * camera_stream.h - Camera HAL stream + * Camera HAL stream */ #pragma once diff --git a/src/android/cros/camera3_hal.cpp b/src/android/cros/camera3_hal.cpp index 71acb441..6010a5ad 100644 --- a/src/android/cros/camera3_hal.cpp +++ b/src/android/cros/camera3_hal.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera3_hal.cpp - cros-specific components of Android Camera HALv3 module + * cros-specific components of Android Camera HALv3 module */ #include <cros-camera/cros_camera_hal.h> diff --git a/src/android/cros_mojo_token.h b/src/android/cros_mojo_token.h index 043c752a..d0baa80f 100644 --- a/src/android/cros_mojo_token.h +++ b/src/android/cros_mojo_token.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * cros_mojo_token.h - cros-specific mojo token + * cros-specific mojo token */ #pragma once diff --git a/src/android/frame_buffer_allocator.h b/src/android/frame_buffer_allocator.h index e5c94922..3e68641c 100644 --- a/src/android/frame_buffer_allocator.h +++ b/src/android/frame_buffer_allocator.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * frame_buffer_allocator.h - Interface definition to allocate Frame buffer in + * Interface definition to allocate Frame buffer in * platform dependent way. */ #ifndef __ANDROID_FRAME_BUFFER_ALLOCATOR_H__ diff --git a/src/android/hal_framebuffer.cpp b/src/android/hal_framebuffer.cpp index 3f3d1ed1..d4899f45 100644 --- a/src/android/hal_framebuffer.cpp +++ b/src/android/hal_framebuffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * hal_framebuffer.cpp - HAL Frame Buffer Handling + * HAL Frame Buffer Handling */ #include "hal_framebuffer.h" diff --git a/src/android/hal_framebuffer.h b/src/android/hal_framebuffer.h index dc96a7e1..cea49e2d 100644 --- a/src/android/hal_framebuffer.h +++ b/src/android/hal_framebuffer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * hal_framebuffer.h - HAL Frame Buffer Handling + * HAL Frame Buffer Handling */ #pragma once diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h index 31f26895..ed033c19 100644 --- a/src/android/jpeg/encoder.h +++ b/src/android/jpeg/encoder.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * encoder.h - Image encoding interface + * Image encoding interface */ #pragma once diff --git a/src/android/jpeg/encoder_jea.cpp b/src/android/jpeg/encoder_jea.cpp index 7880a6bd..25dc4317 100644 --- a/src/android/jpeg/encoder_jea.cpp +++ b/src/android/jpeg/encoder_jea.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * encoder_jea.cpp - JPEG encoding using CrOS JEA + * JPEG encoding using CrOS JEA */ #include "encoder_jea.h" diff --git a/src/android/jpeg/encoder_jea.h b/src/android/jpeg/encoder_jea.h index ffe9df27..91115d2e 100644 --- a/src/android/jpeg/encoder_jea.h +++ b/src/android/jpeg/encoder_jea.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * encoder_jea.h - JPEG encoding using CrOS JEA + * JPEG encoding using CrOS JEA */ #pragma once diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp index f4e8dfad..cb242b5e 100644 --- a/src/android/jpeg/encoder_libjpeg.cpp +++ b/src/android/jpeg/encoder_libjpeg.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * encoder_libjpeg.cpp - JPEG encoding using libjpeg native API + * JPEG encoding using libjpeg native API */ #include "encoder_libjpeg.h" @@ -125,7 +125,7 @@ void EncoderLibJpeg::compressRGB(const std::vector<Span<uint8_t>> &planes) */ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes) { - uint8_t tmprowbuf[compress_.image_width * 3]; + std::vector<uint8_t> tmprowbuf(compress_.image_width * 3); /* * \todo Use the raw api, and only unpack the cb/cr samples to new line @@ -149,10 +149,10 @@ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes) const unsigned char *src_c = planes[1].data(); JSAMPROW row_pointer[1]; - row_pointer[0] = &tmprowbuf[0]; + row_pointer[0] = tmprowbuf.data(); for (unsigned int y = 0; y < compress_.image_height; y++) { - unsigned char *dst = &tmprowbuf[0]; + unsigned char *dst = tmprowbuf.data(); const unsigned char *src_y = src + y * y_stride; const unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos; diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h index 146a6a72..4ac85c22 100644 --- a/src/android/jpeg/encoder_libjpeg.h +++ b/src/android/jpeg/encoder_libjpeg.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * encoder_libjpeg.h - JPEG encoding using libjpeg + * JPEG encoding using libjpeg */ #pragma once diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 6b1d0f1f..b8c871df 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * exif.cpp - EXIF tag creation using libexif + * EXIF tag creation using libexif */ #include "exif.h" diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index e68716f3..446d53f3 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * exif.h - EXIF tag creator using libexif + * EXIF tag creator using libexif */ #pragma once diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index 40261652..89b8a401 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * post_processor_jpeg.cpp - JPEG Post Processor + * JPEG Post Processor */ #include "post_processor_jpeg.h" diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h index 98309b01..6fe21457 100644 --- a/src/android/jpeg/post_processor_jpeg.h +++ b/src/android/jpeg/post_processor_jpeg.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * post_processor_jpeg.h - JPEG Post Processor + * JPEG Post Processor */ #pragma once diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp index 41c71c76..adafc468 100644 --- a/src/android/jpeg/thumbnailer.cpp +++ b/src/android/jpeg/thumbnailer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * thumbnailer.cpp - Simple image thumbnailer + * Simple image thumbnailer */ #include "thumbnailer.h" diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h index d933cf0e..1b836e59 100644 --- a/src/android/jpeg/thumbnailer.h +++ b/src/android/jpeg/thumbnailer.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * thumbnailer.h - Simple image thumbnailer + * Simple image thumbnailer */ #pragma once diff --git a/src/android/meson.build b/src/android/meson.build index 68646120..7b226a4b 100644 --- a/src/android/meson.build +++ b/src/android/meson.build @@ -4,6 +4,7 @@ android_deps = [ dependency('libexif', required : get_option('android')), dependency('libjpeg', required : get_option('android')), libcamera_private, + libyuv_dep, ] android_enabled = true @@ -15,27 +16,6 @@ foreach dep : android_deps endif endforeach -libyuv_dep = dependency('libyuv', required : false) - -# Fallback to a subproject if libyuv isn't found, as it's typically not -# provided by distributions. -if not libyuv_dep.found() - cmake = import('cmake') - - libyuv_vars = cmake.subproject_options() - libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'}) - libyuv_vars.set_override_option('cpp_std', 'c++17') - libyuv_vars.append_compile_args('cpp', - '-Wno-sign-compare', - '-Wno-unused-variable', - '-Wno-unused-parameter') - libyuv_vars.append_link_args('-ljpeg') - libyuv = cmake.subproject('libyuv', options : libyuv_vars) - libyuv_dep = libyuv.dependency('yuv') -endif - -android_deps += [libyuv_dep] - android_hal_sources = files([ 'camera3_hal.cpp', 'camera_capabilities.cpp', diff --git a/src/android/mm/cros_camera_buffer.cpp b/src/android/mm/cros_camera_buffer.cpp index 2ac3dc4a..e2a44a2a 100644 --- a/src/android/mm/cros_camera_buffer.cpp +++ b/src/android/mm/cros_camera_buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * cros_camera_buffer.cpp - Chromium OS buffer backend using CameraBufferManager + * Chromium OS buffer backend using CameraBufferManager */ #include "../camera_buffer.h" diff --git a/src/android/mm/cros_frame_buffer_allocator.cpp b/src/android/mm/cros_frame_buffer_allocator.cpp index 0a5c59f2..264c0d48 100644 --- a/src/android/mm/cros_frame_buffer_allocator.cpp +++ b/src/android/mm/cros_frame_buffer_allocator.cpp @@ -2,8 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * cros_frame_buffer.cpp - Allocate FrameBuffer for Chromium OS using - * CameraBufferManager + * Allocate FrameBuffer for Chromium OS using CameraBufferManager */ #include <memory> diff --git a/src/android/mm/generic_camera_buffer.cpp b/src/android/mm/generic_camera_buffer.cpp index 1bd7090d..0ffcb445 100644 --- a/src/android/mm/generic_camera_buffer.cpp +++ b/src/android/mm/generic_camera_buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * generic_camera_buffer.cpp - Generic Android frame buffer backend + * Generic Android frame buffer backend */ #include "../camera_buffer.h" diff --git a/src/android/mm/generic_frame_buffer_allocator.cpp b/src/android/mm/generic_frame_buffer_allocator.cpp index 7ecef2c6..79625a9a 100644 --- a/src/android/mm/generic_frame_buffer_allocator.cpp +++ b/src/android/mm/generic_frame_buffer_allocator.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * generic_camera_buffer.cpp - Allocate FrameBuffer using gralloc API + * Allocate FrameBuffer using gralloc API */ #include <dlfcn.h> diff --git a/src/android/mm/libhardware_stub.c b/src/android/mm/libhardware_stub.c index 00f15cd9..28faa638 100644 --- a/src/android/mm/libhardware_stub.c +++ b/src/android/mm/libhardware_stub.c @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Ideas on Board * - * libhardware_stub.c - Android libhardware stub for test compilation + * Android libhardware stub for test compilation */ #include <errno.h> diff --git a/src/android/post_processor.h b/src/android/post_processor.h index 1a205b05..b504a379 100644 --- a/src/android/post_processor.h +++ b/src/android/post_processor.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * post_processor.h - CameraStream Post Processing Interface + * CameraStream Post Processing Interface */ #pragma once diff --git a/src/android/yuv/post_processor_yuv.cpp b/src/android/yuv/post_processor_yuv.cpp index ed44e6fe..c998807b 100644 --- a/src/android/yuv/post_processor_yuv.cpp +++ b/src/android/yuv/post_processor_yuv.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * post_processor_yuv.cpp - Post Processor using libyuv + * Post Processor using libyuv */ #include "post_processor_yuv.h" diff --git a/src/android/yuv/post_processor_yuv.h b/src/android/yuv/post_processor_yuv.h index a7ac17c5..ed7bb1fb 100644 --- a/src/android/yuv/post_processor_yuv.h +++ b/src/android/yuv/post_processor_yuv.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * post_processor_yuv.h - Post Processor using libyuv + * Post Processor using libyuv */ #pragma once diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 8447f932..f63fcb22 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -2,12 +2,15 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_session.cpp - Camera capture session + * Camera capture session */ +#include "camera_session.h" + #include <iomanip> #include <iostream> #include <limits.h> +#include <optional> #include <sstream> #include <libcamera/control_ids.h> @@ -16,7 +19,6 @@ #include "../common/event_loop.h" #include "../common/stream_options.h" -#include "camera_session.h" #include "capture_script.h" #include "file_sink.h" #ifdef HAVE_KMS @@ -39,9 +41,14 @@ CameraSession::CameraSession(CameraManager *cm, { char *endptr; unsigned long index = strtoul(cameraId.c_str(), &endptr, 10); - if (*endptr == '\0' && index > 0 && index <= cm->cameras().size()) - camera_ = cm->cameras()[index - 1]; - else + + if (*endptr == '\0' && index > 0) { + auto cameras = cm->cameras(); + if (index <= cameras.size()) + camera_ = cameras[index - 1]; + } + + if (!camera_) camera_ = cm->get(cameraId); if (!camera_) { @@ -55,11 +62,32 @@ CameraSession::CameraSession(CameraManager *cm, return; } - std::vector<StreamRole> roles = StreamKeyValueParser::roles(options_[OptStream]); + std::vector<StreamRole> roles = + StreamKeyValueParser::roles(options_[OptStream]); + std::vector<std::vector<StreamRole>> tryRoles; + if (!roles.empty()) { + /* + * If the roles are explicitly specified then there's no need + * to try other roles + */ + tryRoles.push_back(roles); + } else { + tryRoles.push_back({ StreamRole::Viewfinder }); + tryRoles.push_back({ StreamRole::Raw }); + } + + std::unique_ptr<CameraConfiguration> config; + bool valid = false; + for (std::vector<StreamRole> &rolesIt : tryRoles) { + config = camera_->generateConfiguration(rolesIt); + if (config && config->size() == rolesIt.size()) { + roles = rolesIt; + valid = true; + break; + } + } - std::unique_ptr<CameraConfiguration> config = - camera_->generateConfiguration(roles); - if (!config || config->size() != roles.size()) { + if (!valid) { std::cerr << "Failed to get default stream configuration" << std::endl; return; @@ -154,8 +182,51 @@ CameraSession::~CameraSession() void CameraSession::listControls() const { for (const auto &[id, info] : camera_->controls()) { - std::cout << "Control: " << id->name() << ": " - << info.toString() << std::endl; + std::stringstream io; + io << "[" + << (id->isInput() ? "in" : " ") + << (id->isOutput() ? "out" : " ") + << "] "; + + if (info.values().empty()) { + std::cout << "Control: " << io.str() + << id->vendor() << "::" << id->name() << ": " + << info.toString() << std::endl; + } else { + std::cout << "Control: " << io.str() + << id->vendor() << "::" << id->name() << ":" + << std::endl; + + std::optional<int32_t> def; + if (!info.def().isNone()) + def = info.def().get<int32_t>(); + + for (const auto &value : info.values()) { + int32_t val = value.get<int32_t>(); + const auto &it = id->enumerators().find(val); + + std::cout << " - "; + if (it == id->enumerators().end()) + std::cout << "UNKNOWN"; + else + std::cout << it->second; + + std::cout << " (" << val << ")" + << (val == def ? " [default]" : "") + << std::endl; + } + } + + if (id->isArray()) { + std::size_t size = id->size(); + + std::cout << " Size: "; + if (size == std::numeric_limits<std::size_t>::max()) + std::cout << "n"; + else + std::cout << std::to_string(size); + std::cout << std::endl; + } } } @@ -225,11 +296,16 @@ int CameraSession::start() #endif if (options_.isSet(OptFile)) { - if (!options_[OptFile].toString().empty()) - sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_, - options_[OptFile]); - else - sink_ = std::make_unique<FileSink>(camera_.get(), streamNames_); + std::unique_ptr<FileSink> sink = + std::make_unique<FileSink>(camera_.get(), streamNames_); + + if (!options_[OptFile].toString().empty()) { + ret = sink->setFilePattern(options_[OptFile]); + if (ret) + return ret; + } + + sink_ = std::move(sink); } if (sink_) { @@ -377,7 +453,7 @@ void CameraSession::requestComplete(Request *request) * Defer processing of the completed request to the event loop, to avoid * blocking the camera manager thread. */ - EventLoop::instance()->callLater([=]() { processRequest(request); }); + EventLoop::instance()->callLater([this, request]() { processRequest(request); }); } void CameraSession::processRequest(Request *request) diff --git a/src/apps/cam/camera_session.h b/src/apps/cam/camera_session.h index 0bab519f..4442fd9b 100644 --- a/src/apps/cam/camera_session.h +++ b/src/apps/cam/camera_session.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_session.h - Camera capture session + * Camera capture session */ #pragma once diff --git a/src/apps/cam/capture_script.cpp b/src/apps/cam/capture_script.cpp index 062a7258..fdf82efc 100644 --- a/src/apps/cam/capture_script.cpp +++ b/src/apps/cam/capture_script.cpp @@ -2,12 +2,13 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * capture_script.cpp - Capture session configuration script + * Capture session configuration script */ #include "capture_script.h" #include <iostream> +#include <memory> #include <stdio.h> #include <stdlib.h> @@ -351,7 +352,10 @@ ControlValue CaptureScript::parseRectangles() } ControlValue controlValue; - controlValue.set(Span<const Rectangle>(rectangles)); + if (rectangles.size() == 1) + controlValue.set(rectangles.at(0)); + else + controlValue.set(Span<const Rectangle>(rectangles)); return controlValue; } @@ -518,45 +522,22 @@ ControlValue CaptureScript::parseArrayControl(const ControlId *id, case ControlTypeNone: break; case ControlTypeBool: { - /* - * This is unpleasant, but we cannot use an std::vector<> as its - * boolean type overload does not allow to access the raw data, - * as boolean values are stored in a bitmask for efficiency. - * - * As we need a contiguous memory region to wrap in a Span<>, - * use an array instead but be strict about not overflowing it - * by limiting the number of controls we can store. - * - * Be loud but do not fail, as the issue would present at - * runtime and it's not fatal. - */ - static constexpr unsigned int kMaxNumBooleanControls = 1024; - std::array<bool, kMaxNumBooleanControls> values; - unsigned int idx = 0; + auto values = std::make_unique<bool[]>(repr.size()); - for (const std::string &s : repr) { - bool val; + for (std::size_t i = 0; i < repr.size(); i++) { + const auto &s = repr[i]; if (s == "true") { - val = true; + values[i] = true; } else if (s == "false") { - val = false; + values[i] = false; } else { unpackFailure(id, s); return value; } - - if (idx == kMaxNumBooleanControls) { - std::cerr << "Cannot parse more than " - << kMaxNumBooleanControls - << " boolean controls" << std::endl; - break; - } - - values[idx++] = val; } - value = Span<bool>(values.data(), idx); + value = Span<bool>(values.get(), repr.size()); break; } case ControlTypeByte: { @@ -597,10 +578,6 @@ ControlValue CaptureScript::parseArrayControl(const ControlId *id, value = Span<const float>(values.data(), values.size()); break; } - case ControlTypeString: { - value = Span<const std::string>(repr.data(), repr.size()); - break; - } default: std::cerr << "Unsupported control type" << std::endl; break; diff --git a/src/apps/cam/capture_script.h b/src/apps/cam/capture_script.h index 40042c03..294b9203 100644 --- a/src/apps/cam/capture_script.h +++ b/src/apps/cam/capture_script.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * capture_script.h - Capture session configuration script + * Capture session configuration script */ #pragma once diff --git a/src/apps/cam/drm.cpp b/src/apps/cam/drm.cpp index 8779a713..47bbb6b0 100644 --- a/src/apps/cam/drm.cpp +++ b/src/apps/cam/drm.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * drm.cpp - DRM/KMS Helpers + * DRM/KMS Helpers */ #include "drm.h" diff --git a/src/apps/cam/drm.h b/src/apps/cam/drm.h index ebaea04d..1ba83b6e 100644 --- a/src/apps/cam/drm.h +++ b/src/apps/cam/drm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * drm.h - DRM/KMS Helpers + * DRM/KMS Helpers */ #pragma once diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp index dca350c4..65794a2f 100644 --- a/src/apps/cam/file_sink.cpp +++ b/src/apps/cam/file_sink.cpp @@ -2,9 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * file_sink.cpp - File Sink + * File Sink */ +#include "file_sink.h" + +#include <array> #include <assert.h> #include <fcntl.h> #include <iomanip> @@ -12,24 +15,24 @@ #include <sstream> #include <string.h> #include <unistd.h> +#include <utility> #include <libcamera/camera.h> #include "../common/dng_writer.h" #include "../common/image.h" - -#include "file_sink.h" +#include "../common/ppm_writer.h" using namespace libcamera; FileSink::FileSink([[maybe_unused]] const libcamera::Camera *camera, - const std::map<const libcamera::Stream *, std::string> &streamNames, - const std::string &pattern) + const std::map<const libcamera::Stream *, std::string> &streamNames) : #ifdef HAVE_TIFF camera_(camera), #endif - streamNames_(streamNames), pattern_(pattern) + pattern_(kDefaultFilePattern), fileType_(FileType::Binary), + streamNames_(streamNames) { } @@ -37,6 +40,41 @@ FileSink::~FileSink() { } +int FileSink::setFilePattern(const std::string &pattern) +{ + static const std::array<std::pair<std::string, FileType>, 2> types{{ + { ".dng", FileType::Dng }, + { ".ppm", FileType::Ppm }, + }}; + + pattern_ = pattern; + + if (pattern_.empty() || pattern_.back() == '/') + pattern_ += kDefaultFilePattern; + + fileType_ = FileType::Binary; + + for (const auto &type : types) { + if (pattern_.size() < type.first.size()) + continue; + + if (pattern_.find(type.first, pattern_.size() - type.first.size()) != + std::string::npos) { + fileType_ = type.second; + break; + } + } + +#ifndef HAVE_TIFF + if (fileType_ == FileType::Dng) { + std::cerr << "DNG support not available" << std::endl; + return -EINVAL; + } +#endif /* HAVE_TIFF */ + + return 0; +} + int FileSink::configure(const libcamera::CameraConfiguration &config) { int ret = FrameSink::configure(config); @@ -66,20 +104,10 @@ bool FileSink::processRequest(Request *request) void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer, [[maybe_unused]] const ControlList &metadata) { - std::string filename; + std::string filename = pattern_; size_t pos; int fd, ret = 0; - if (!pattern_.empty()) - filename = pattern_; - -#ifdef HAVE_TIFF - bool dng = filename.find(".dng", filename.size() - 4) != std::string::npos; -#endif /* HAVE_TIFF */ - - if (filename.empty() || filename.back() == '/') - filename += "frame-#.bin"; - pos = filename.find_first_of('#'); if (pos != std::string::npos) { std::stringstream ss; @@ -91,7 +119,7 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer, Image *image = mappedBuffers_[buffer].get(); #ifdef HAVE_TIFF - if (dng) { + if (fileType_ == FileType::Dng) { ret = DNGWriter::write(filename.c_str(), camera_, stream->configuration(), metadata, buffer, image->data(0).data()); @@ -102,6 +130,15 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer, return; } #endif /* HAVE_TIFF */ + if (fileType_ == FileType::Ppm) { + ret = PPMWriter::write(filename.c_str(), stream->configuration(), + image->data(0)); + if (ret < 0) + std::cerr << "failed to write PPM file `" << filename + << "'" << std::endl; + + return; + } fd = open(filename.c_str(), O_CREAT | O_WRONLY | (pos == std::string::npos ? O_APPEND : O_TRUNC), diff --git a/src/apps/cam/file_sink.h b/src/apps/cam/file_sink.h index 300edf8d..26cd61b3 100644 --- a/src/apps/cam/file_sink.h +++ b/src/apps/cam/file_sink.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * file_sink.h - File Sink + * File Sink */ #pragma once @@ -11,6 +11,7 @@ #include <memory> #include <string> +#include <libcamera/controls.h> #include <libcamera/stream.h> #include "frame_sink.h" @@ -21,10 +22,11 @@ class FileSink : public FrameSink { public: FileSink(const libcamera::Camera *camera, - const std::map<const libcamera::Stream *, std::string> &streamNames, - const std::string &pattern = ""); + const std::map<const libcamera::Stream *, std::string> &streamNames); ~FileSink(); + int setFilePattern(const std::string &pattern); + int configure(const libcamera::CameraConfiguration &config) override; void mapBuffer(libcamera::FrameBuffer *buffer) override; @@ -32,6 +34,14 @@ public: bool processRequest(libcamera::Request *request) override; private: + static constexpr const char *kDefaultFilePattern = "frame-#.bin"; + + enum class FileType { + Binary, + Dng, + Ppm, + }; + void writeBuffer(const libcamera::Stream *stream, libcamera::FrameBuffer *buffer, const libcamera::ControlList &metadata); @@ -39,7 +49,10 @@ private: #ifdef HAVE_TIFF const libcamera::Camera *camera_; #endif - std::map<const libcamera::Stream *, std::string> streamNames_; + std::string pattern_; + FileType fileType_; + + std::map<const libcamera::Stream *, std::string> streamNames_; std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>> mappedBuffers_; }; diff --git a/src/apps/cam/frame_sink.cpp b/src/apps/cam/frame_sink.cpp index af21d575..68d6f2c1 100644 --- a/src/apps/cam/frame_sink.cpp +++ b/src/apps/cam/frame_sink.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * frame_sink.cpp - Base Frame Sink Class + * Base Frame Sink Class */ #include "frame_sink.h" diff --git a/src/apps/cam/frame_sink.h b/src/apps/cam/frame_sink.h index ca4347cb..11105c6c 100644 --- a/src/apps/cam/frame_sink.h +++ b/src/apps/cam/frame_sink.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * frame_sink.h - Base Frame Sink Class + * Base Frame Sink Class */ #pragma once diff --git a/src/apps/cam/kms_sink.cpp b/src/apps/cam/kms_sink.cpp index 6991c3fa..672c985a 100644 --- a/src/apps/cam/kms_sink.cpp +++ b/src/apps/cam/kms_sink.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * kms_sink.cpp - KMS Sink + * KMS Sink */ #include "kms_sink.h" diff --git a/src/apps/cam/kms_sink.h b/src/apps/cam/kms_sink.h index e2c618a1..4b7b4c26 100644 --- a/src/apps/cam/kms_sink.h +++ b/src/apps/cam/kms_sink.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * kms_sink.h - KMS Sink + * KMS Sink */ #pragma once diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp index 179cc376..fa266eca 100644 --- a/src/apps/cam/main.cpp +++ b/src/apps/cam/main.cpp @@ -2,9 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * main.cpp - cam - The libcamera swiss army knife + * cam - The libcamera swiss army knife */ +#include "main.h" + #include <atomic> #include <iomanip> #include <iostream> @@ -19,7 +21,6 @@ #include "../common/stream_options.h" #include "camera_session.h" -#include "main.h" using namespace libcamera; @@ -154,6 +155,8 @@ int CamApp::parseOptions(int argc, char *argv[]) "If the file name ends with '.dng', then the frame will be written to\n" "the output file(s) in DNG format.\n" #endif + "If the file name ends with '.ppm', then the frame will be written to\n" + "the output file(s) in PPM format.\n" "The default file name is 'frame-#.bin'.", "file", ArgumentOptional, "filename", false, OptCamera); @@ -342,12 +345,16 @@ std::string CamApp::cameraName(const Camera *camera) return name; } +namespace { + void signalHandler([[maybe_unused]] int signal) { std::cout << "Exiting" << std::endl; CamApp::instance()->quit(); } +} /* namespace */ + int main(int argc, char **argv) { CamApp app; diff --git a/src/apps/cam/main.h b/src/apps/cam/main.h index 4aa959b3..64e6a20e 100644 --- a/src/apps/cam/main.h +++ b/src/apps/cam/main.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * main.h - Cam application + * Cam application */ #pragma once diff --git a/src/apps/cam/meson.build b/src/apps/cam/meson.build index 48c834ac..2833c86e 100644 --- a/src/apps/cam/meson.build +++ b/src/apps/cam/meson.build @@ -34,6 +34,7 @@ if libsdl2.found() cam_sources += files([ 'sdl_sink.cpp', 'sdl_texture.cpp', + 'sdl_texture_1plane.cpp', 'sdl_texture_yuv.cpp', ]) @@ -58,4 +59,5 @@ cam = executable('cam', cam_sources, libyaml, ], cpp_args : cam_cpp_args, - install : true) + install : true, + install_tag : 'bin') diff --git a/src/apps/cam/sdl_sink.cpp b/src/apps/cam/sdl_sink.cpp index a2f4abc1..30cfdf0a 100644 --- a/src/apps/cam/sdl_sink.cpp +++ b/src/apps/cam/sdl_sink.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_sink.h - SDL Sink + * SDL Sink */ #include "sdl_sink.h" @@ -11,6 +11,7 @@ #include <fcntl.h> #include <iomanip> #include <iostream> +#include <optional> #include <signal.h> #include <sstream> #include <string.h> @@ -22,6 +23,7 @@ #include "../common/event_loop.h" #include "../common/image.h" +#include "sdl_texture_1plane.h" #ifdef HAVE_LIBJPEG #include "sdl_texture_mjpg.h" #endif @@ -31,6 +33,46 @@ using namespace libcamera; using namespace std::chrono_literals; +namespace { + +std::optional<SDL_PixelFormatEnum> singlePlaneFormatToSDL(const libcamera::PixelFormat &f) +{ + switch (f) { + case libcamera::formats::RGB888: + return SDL_PIXELFORMAT_BGR24; + case libcamera::formats::BGR888: + return SDL_PIXELFORMAT_RGB24; + case libcamera::formats::RGBA8888: + return SDL_PIXELFORMAT_ABGR32; + case libcamera::formats::ARGB8888: + return SDL_PIXELFORMAT_BGRA32; + case libcamera::formats::BGRA8888: + return SDL_PIXELFORMAT_ARGB32; + case libcamera::formats::ABGR8888: + return SDL_PIXELFORMAT_RGBA32; +#if SDL_VERSION_ATLEAST(2, 29, 1) + case libcamera::formats::RGBX8888: + return SDL_PIXELFORMAT_XBGR32; + case libcamera::formats::XRGB8888: + return SDL_PIXELFORMAT_BGRX32; + case libcamera::formats::BGRX8888: + return SDL_PIXELFORMAT_XRGB32; + case libcamera::formats::XBGR8888: + return SDL_PIXELFORMAT_RGBX32; +#endif + case libcamera::formats::YUYV: + return SDL_PIXELFORMAT_YUY2; + case libcamera::formats::UYVY: + return SDL_PIXELFORMAT_UYVY; + case libcamera::formats::YVYU: + return SDL_PIXELFORMAT_YVYU; + } + + return {}; +} + +} /* namespace */ + SDLSink::SDLSink() : window_(nullptr), renderer_(nullptr), rect_({}), init_(false) @@ -62,25 +104,20 @@ int SDLSink::configure(const libcamera::CameraConfiguration &config) rect_.w = cfg.size.width; rect_.h = cfg.size.height; - switch (cfg.pixelFormat) { + if (auto sdlFormat = singlePlaneFormatToSDL(cfg.pixelFormat)) + texture_ = std::make_unique<SDLTexture1Plane>(rect_, *sdlFormat, cfg.stride); #ifdef HAVE_LIBJPEG - case libcamera::formats::MJPEG: + else if (cfg.pixelFormat == libcamera::formats::MJPEG) texture_ = std::make_unique<SDLTextureMJPG>(rect_); - break; #endif #if SDL_VERSION_ATLEAST(2, 0, 16) - case libcamera::formats::NV12: + else if (cfg.pixelFormat == libcamera::formats::NV12) texture_ = std::make_unique<SDLTextureNV12>(rect_, cfg.stride); - break; #endif - case libcamera::formats::YUYV: - texture_ = std::make_unique<SDLTextureYUYV>(rect_, cfg.stride); - break; - default: - std::cerr << "Unsupported pixel format " - << cfg.pixelFormat.toString() << std::endl; + else { + std::cerr << "Unsupported pixel format " << cfg.pixelFormat << std::endl; return -EINVAL; - }; + } return 0; } diff --git a/src/apps/cam/sdl_sink.h b/src/apps/cam/sdl_sink.h index 6c19c663..18ec7fbe 100644 --- a/src/apps/cam/sdl_sink.h +++ b/src/apps/cam/sdl_sink.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_sink.h - SDL Sink + * SDL Sink */ #pragma once diff --git a/src/apps/cam/sdl_texture.cpp b/src/apps/cam/sdl_texture.cpp index e9040bc5..e52c4a3a 100644 --- a/src/apps/cam/sdl_texture.cpp +++ b/src/apps/cam/sdl_texture.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture.cpp - SDL Texture + * SDL Texture */ #include "sdl_texture.h" diff --git a/src/apps/cam/sdl_texture.h b/src/apps/cam/sdl_texture.h index 3993dd46..39e1c7b3 100644 --- a/src/apps/cam/sdl_texture.h +++ b/src/apps/cam/sdl_texture.h @@ -2,12 +2,12 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture.h - SDL Texture + * SDL Texture */ #pragma once -#include <vector> +#include <libcamera/base/span.h> #include <SDL2/SDL.h> @@ -19,7 +19,7 @@ public: SDLTexture(const SDL_Rect &rect, uint32_t pixelFormat, const int stride); virtual ~SDLTexture(); int create(SDL_Renderer *renderer); - virtual void update(const std::vector<libcamera::Span<const uint8_t>> &data) = 0; + virtual void update(libcamera::Span<const libcamera::Span<const uint8_t>> data) = 0; SDL_Texture *get() const { return ptr_; } protected: diff --git a/src/apps/cam/sdl_texture_1plane.cpp b/src/apps/cam/sdl_texture_1plane.cpp new file mode 100644 index 00000000..b97015bc --- /dev/null +++ b/src/apps/cam/sdl_texture_1plane.cpp @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + * + * SDL single plane textures + */ + +#include "sdl_texture_1plane.h" + +#include <assert.h> + +void SDLTexture1Plane::update(libcamera::Span<const libcamera::Span<const uint8_t>> data) +{ + assert(data.size() == 1); + assert(data[0].size_bytes() == std::size_t(rect_.h) * std::size_t(stride_)); + SDL_UpdateTexture(ptr_, nullptr, data[0].data(), stride_); +} diff --git a/src/apps/cam/sdl_texture_1plane.h b/src/apps/cam/sdl_texture_1plane.h new file mode 100644 index 00000000..795e1fa4 --- /dev/null +++ b/src/apps/cam/sdl_texture_1plane.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + * + * SDL single plane textures + */ + +#pragma once + +#include "sdl_texture.h" + +class SDLTexture1Plane final : public SDLTexture +{ +public: + using SDLTexture::SDLTexture; + + void update(libcamera::Span<const libcamera::Span<const uint8_t>> data) override; +}; diff --git a/src/apps/cam/sdl_texture_mjpg.cpp b/src/apps/cam/sdl_texture_mjpg.cpp index da958e03..ca49a114 100644 --- a/src/apps/cam/sdl_texture_mjpg.cpp +++ b/src/apps/cam/sdl_texture_mjpg.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture_mjpg.cpp - SDL Texture MJPG + * SDL Texture MJPG */ #include "sdl_texture_mjpg.h" @@ -76,7 +76,7 @@ int SDLTextureMJPG::decompress(Span<const uint8_t> data) return 0; } -void SDLTextureMJPG::update(const std::vector<libcamera::Span<const uint8_t>> &data) +void SDLTextureMJPG::update(libcamera::Span<const libcamera::Span<const uint8_t>> data) { decompress(data[0]); SDL_UpdateTexture(ptr_, nullptr, rgb_.get(), stride_); diff --git a/src/apps/cam/sdl_texture_mjpg.h b/src/apps/cam/sdl_texture_mjpg.h index 814ca79a..be8a55fe 100644 --- a/src/apps/cam/sdl_texture_mjpg.h +++ b/src/apps/cam/sdl_texture_mjpg.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture_mjpg.h - SDL Texture MJPG + * SDL Texture MJPG */ #pragma once @@ -14,7 +14,7 @@ class SDLTextureMJPG : public SDLTexture public: SDLTextureMJPG(const SDL_Rect &rect); - void update(const std::vector<libcamera::Span<const uint8_t>> &data) override; + void update(libcamera::Span<const libcamera::Span<const uint8_t>> data) override; private: int decompress(libcamera::Span<const uint8_t> data); diff --git a/src/apps/cam/sdl_texture_yuv.cpp b/src/apps/cam/sdl_texture_yuv.cpp index b29c3b93..bed297d2 100644 --- a/src/apps/cam/sdl_texture_yuv.cpp +++ b/src/apps/cam/sdl_texture_yuv.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture_yuv.cpp - SDL YUV Textures + * SDL YUV Textures */ #include "sdl_texture_yuv.h" @@ -15,19 +15,9 @@ SDLTextureNV12::SDLTextureNV12(const SDL_Rect &rect, unsigned int stride) { } -void SDLTextureNV12::update(const std::vector<libcamera::Span<const uint8_t>> &data) +void SDLTextureNV12::update(libcamera::Span<const libcamera::Span<const uint8_t>> data) { - SDL_UpdateNVTexture(ptr_, &rect_, data[0].data(), stride_, + SDL_UpdateNVTexture(ptr_, nullptr, data[0].data(), stride_, data[1].data(), stride_); } #endif - -SDLTextureYUYV::SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride) - : SDLTexture(rect, SDL_PIXELFORMAT_YUY2, stride) -{ -} - -void SDLTextureYUYV::update(const std::vector<libcamera::Span<const uint8_t>> &data) -{ - SDL_UpdateTexture(ptr_, &rect_, data[0].data(), stride_); -} diff --git a/src/apps/cam/sdl_texture_yuv.h b/src/apps/cam/sdl_texture_yuv.h index 310e4e50..c271f901 100644 --- a/src/apps/cam/sdl_texture_yuv.h +++ b/src/apps/cam/sdl_texture_yuv.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy * - * sdl_texture_yuv.h - SDL YUV Textures + * SDL YUV Textures */ #pragma once @@ -14,13 +14,6 @@ class SDLTextureNV12 : public SDLTexture { public: SDLTextureNV12(const SDL_Rect &rect, unsigned int stride); - void update(const std::vector<libcamera::Span<const uint8_t>> &data) override; + void update(libcamera::Span<const libcamera::Span<const uint8_t>> data) override; }; #endif - -class SDLTextureYUYV : public SDLTexture -{ -public: - SDLTextureYUYV(const SDL_Rect &rect, unsigned int stride); - void update(const std::vector<libcamera::Span<const uint8_t>> &data) override; -}; diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp index c945edce..ac461951 100644 --- a/src/apps/common/dng_writer.cpp +++ b/src/apps/common/dng_writer.cpp @@ -2,14 +2,16 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * dng_writer.cpp - DNG writer + * DNG writer */ #include "dng_writer.h" #include <algorithm> +#include <endian.h> #include <iostream> #include <map> +#include <vector> #include <tiffio.h> @@ -126,7 +128,9 @@ struct Matrix3d { float m[9]; }; -void packScanlineSBGGR8(void *output, const void *input, unsigned int width) +namespace { + +void packScanlineRaw8(void *output, const void *input, unsigned int width) { const uint8_t *in = static_cast<const uint8_t *>(input); uint8_t *out = static_cast<uint8_t *>(output); @@ -134,7 +138,67 @@ void packScanlineSBGGR8(void *output, const void *input, unsigned int width) std::copy(in, in + width, out); } -void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) +void packScanlineRaw10(void *output, const void *input, unsigned int width) +{ + const uint8_t *in = static_cast<const uint8_t *>(input); + uint8_t *out = static_cast<uint8_t *>(output); + + for (unsigned int i = 0; i < width; i += 4) { + *out++ = in[1] << 6 | in[0] >> 2; + *out++ = in[0] << 6 | (in[3] & 0x03) << 4 | in[2] >> 4; + *out++ = in[2] << 4 | (in[5] & 0x03) << 2 | in[4] >> 6; + *out++ = in[4] << 2 | (in[7] & 0x03) << 0; + *out++ = in[6]; + in += 8; + } +} + +void packScanlineRaw12(void *output, const void *input, unsigned int width) +{ + const uint8_t *in = static_cast<const uint8_t *>(input); + uint8_t *out = static_cast<uint8_t *>(output); + + for (unsigned int i = 0; i < width; i += 2) { + *out++ = in[1] << 4 | in[0] >> 4; + *out++ = in[0] << 4 | (in[3] & 0x0f); + *out++ = in[2]; + in += 4; + } +} + +void packScanlineRaw16(void *output, const void *input, unsigned int width) +{ + const uint16_t *in = static_cast<const uint16_t *>(input); + uint16_t *out = static_cast<uint16_t *>(output); + + std::copy(in, in + width, out); +} + +/* Thumbnail function for raw data with each pixel aligned to 16bit. */ +void thumbScanlineRaw(const FormatInfo &info, void *output, const void *input, + unsigned int width, unsigned int stride) +{ + const uint16_t *in = static_cast<const uint16_t *>(input); + const uint16_t *in2 = static_cast<const uint16_t *>(input) + stride / 2; + uint8_t *out = static_cast<uint8_t *>(output); + + /* Shift down to 8. */ + unsigned int shift = info.bitsPerSample - 8; + + /* Simple averaging that produces greyscale RGB values. */ + for (unsigned int x = 0; x < width; x++) { + uint16_t value = (le16toh(in[0]) + le16toh(in[1]) + + le16toh(in2[0]) + le16toh(in2[1])) >> 2; + value = value >> shift; + *out++ = value; + *out++ = value; + *out++ = value; + in += 16; + in2 += 16; + } +} + +void packScanlineRaw10_CSI2P(void *output, const void *input, unsigned int width) { const uint8_t *in = static_cast<const uint8_t *>(input); uint8_t *out = static_cast<uint8_t *>(output); @@ -150,7 +214,7 @@ void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) } } -void packScanlineSBGGR12P(void *output, const void *input, unsigned int width) +void packScanlineRaw12_CSI2P(void *output, const void *input, unsigned int width) { const uint8_t *in = static_cast<const uint8_t *>(input); uint8_t *out = static_cast<uint8_t *>(output); @@ -164,7 +228,7 @@ void packScanlineSBGGR12P(void *output, const void *input, unsigned int width) } } -void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output, +void thumbScanlineRaw_CSI2P(const FormatInfo &info, void *output, const void *input, unsigned int width, unsigned int stride) { @@ -248,6 +312,7 @@ void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output, uint16_t val1, val2, val3, val4; switch (pixelInBlock % 4) { + default: case 0: val1 = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6; val2 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4; @@ -281,78 +346,150 @@ void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output, } } -static const std::map<PixelFormat, FormatInfo> formatInfo = { +const std::map<PixelFormat, FormatInfo> formatInfo = { { formats::SBGGR8, { .bitsPerSample = 8, .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, - .packScanline = packScanlineSBGGR8, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw8, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGBRG8, { .bitsPerSample = 8, .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, - .packScanline = packScanlineSBGGR8, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw8, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGRBG8, { .bitsPerSample = 8, .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, - .packScanline = packScanlineSBGGR8, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw8, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SRGGB8, { .bitsPerSample = 8, .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, - .packScanline = packScanlineSBGGR8, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw8, + .thumbScanline = thumbScanlineRaw_CSI2P, + } }, + { formats::SBGGR10, { + .bitsPerSample = 10, + .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, + .packScanline = packScanlineRaw10, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGBRG10, { + .bitsPerSample = 10, + .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, + .packScanline = packScanlineRaw10, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGRBG10, { + .bitsPerSample = 10, + .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, + .packScanline = packScanlineRaw10, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SRGGB10, { + .bitsPerSample = 10, + .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, + .packScanline = packScanlineRaw10, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SBGGR12, { + .bitsPerSample = 12, + .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, + .packScanline = packScanlineRaw12, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGBRG12, { + .bitsPerSample = 12, + .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, + .packScanline = packScanlineRaw12, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGRBG12, { + .bitsPerSample = 12, + .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, + .packScanline = packScanlineRaw12, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SRGGB12, { + .bitsPerSample = 12, + .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, + .packScanline = packScanlineRaw12, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SBGGR16, { + .bitsPerSample = 16, + .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, + .packScanline = packScanlineRaw16, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGBRG16, { + .bitsPerSample = 16, + .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, + .packScanline = packScanlineRaw16, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SGRBG16, { + .bitsPerSample = 16, + .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, + .packScanline = packScanlineRaw16, + .thumbScanline = thumbScanlineRaw, + } }, + { formats::SRGGB16, { + .bitsPerSample = 16, + .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, + .packScanline = packScanlineRaw16, + .thumbScanline = thumbScanlineRaw, } }, { formats::SBGGR10_CSI2P, { .bitsPerSample = 10, .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, - .packScanline = packScanlineSBGGR10P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw10_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGBRG10_CSI2P, { .bitsPerSample = 10, .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, - .packScanline = packScanlineSBGGR10P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw10_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGRBG10_CSI2P, { .bitsPerSample = 10, .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, - .packScanline = packScanlineSBGGR10P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw10_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SRGGB10_CSI2P, { .bitsPerSample = 10, .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, - .packScanline = packScanlineSBGGR10P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw10_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SBGGR12_CSI2P, { .bitsPerSample = 12, .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, - .packScanline = packScanlineSBGGR12P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw12_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGBRG12_CSI2P, { .bitsPerSample = 12, .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, - .packScanline = packScanlineSBGGR12P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw12_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SGRBG12_CSI2P, { .bitsPerSample = 12, .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, - .packScanline = packScanlineSBGGR12P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw12_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SRGGB12_CSI2P, { .bitsPerSample = 12, .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, - .packScanline = packScanlineSBGGR12P, - .thumbScanline = thumbScanlineSBGGRxxP, + .packScanline = packScanlineRaw12_CSI2P, + .thumbScanline = thumbScanlineRaw_CSI2P, } }, { formats::SBGGR10_IPU3, { .bitsPerSample = 16, @@ -380,6 +517,8 @@ static const std::map<PixelFormat, FormatInfo> formatInfo = { } }, }; +} /* namespace */ + int DNGWriter::write(const char *filename, const Camera *camera, const StreamConfiguration &config, const ControlList &metadata, @@ -406,7 +545,7 @@ int DNGWriter::write(const char *filename, const Camera *camera, * or a thumbnail scanline. The latter will always be much smaller than * the former as we downscale by 16 in both directions. */ - uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8]; + std::vector<uint8_t> scanline((config.size.width * info->bitsPerSample + 7) / 8); toff_t rawIFDOffset = 0; toff_t exifIFDOffset = 0; @@ -506,10 +645,10 @@ int DNGWriter::write(const char *filename, const Camera *camera, /* Write the thumbnail. */ const uint8_t *row = static_cast<const uint8_t *>(data); for (unsigned int y = 0; y < config.size.height / 16; y++) { - info->thumbScanline(*info, &scanline, row, + info->thumbScanline(*info, scanline.data(), row, config.size.width / 16, config.stride); - if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) { + if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) { std::cerr << "Failed to write thumbnail scanline" << std::endl; TIFFClose(tif); @@ -521,6 +660,23 @@ int DNGWriter::write(const char *filename, const Camera *camera, TIFFWriteDirectory(tif); + /* + * Workaround for a bug introduced in libtiff version 4.5.1 and no fix + * released. In these versions the CFA* tags were missing in the field + * info. + * Introduced by: https://gitlab.com/libtiff/libtiff/-/commit/738e04099b13192bb1f654e74e9b5829313f3161 + * Fixed by: https://gitlab.com/libtiff/libtiff/-/commit/49856998c3d82e65444b47bb4fb11b7830a0c2be + */ + if (!TIFFFindField(tif, TIFFTAG_CFAREPEATPATTERNDIM, TIFF_ANY)) { + static const TIFFFieldInfo infos[] = { + { TIFFTAG_CFAREPEATPATTERNDIM, 2, 2, TIFF_SHORT, FIELD_CUSTOM, + 1, 0, const_cast<char *>("CFARepeatPatternDim") }, + { TIFFTAG_CFAPATTERN, -1, -1, TIFF_BYTE, FIELD_CUSTOM, + 1, 1, const_cast<char *>("CFAPattern") }, + }; + TIFFMergeFieldInfo(tif, infos, 2); + } + /* Create a new IFD for the RAW image. */ const uint16_t cfaRepeatPatternDim[] = { 2, 2 }; const uint8_t cfaPlaneColor[] = { @@ -592,9 +748,9 @@ int DNGWriter::write(const char *filename, const Camera *camera, /* Write RAW content. */ row = static_cast<const uint8_t *>(data); for (unsigned int y = 0; y < config.size.height; y++) { - info->packScanline(&scanline, row, config.size.width); + info->packScanline(scanline.data(), row, config.size.width); - if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) { + if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) { std::cerr << "Failed to write RAW scanline" << std::endl; TIFFClose(tif); diff --git a/src/apps/common/dng_writer.h b/src/apps/common/dng_writer.h index 38f38f62..aaa8a852 100644 --- a/src/apps/common/dng_writer.h +++ b/src/apps/common/dng_writer.h @@ -2,13 +2,12 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * dng_writer.h - DNG writer + * DNG writer */ #pragma once #ifdef HAVE_TIFF -#define HAVE_DNG #include <libcamera/camera.h> #include <libcamera/controls.h> diff --git a/src/apps/common/event_loop.cpp b/src/apps/common/event_loop.cpp index cb83845c..b6230f4b 100644 --- a/src/apps/common/event_loop.cpp +++ b/src/apps/common/event_loop.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event_loop.cpp - cam - Event loop + * cam - Event loop */ #include "event_loop.h" @@ -21,12 +21,35 @@ EventLoop::EventLoop() evthread_use_pthreads(); base_ = event_base_new(); instance_ = this; + + callsTrigger_ = event_new(base_, -1, EV_PERSIST, [](evutil_socket_t, short, void *closure) { + auto *self = static_cast<EventLoop *>(closure); + + for (;;) { + std::function<void()> call; + + { + std::lock_guard locker(self->lock_); + if (self->calls_.empty()) + break; + + call = std::move(self->calls_.front()); + self->calls_.pop_front(); + } + + call(); + } + }, this); + assert(callsTrigger_); + event_add(callsTrigger_, nullptr); } EventLoop::~EventLoop() { instance_ = nullptr; + event_free(callsTrigger_); + events_.clear(); event_base_free(base_); libevent_global_shutdown(); @@ -50,20 +73,20 @@ void EventLoop::exit(int code) event_base_loopbreak(base_); } -void EventLoop::callLater(const std::function<void()> &func) +void EventLoop::callLater(std::function<void()> &&func) { { std::unique_lock<std::mutex> locker(lock_); - calls_.push_back(func); + calls_.push_back(std::move(func)); } - event_base_once(base_, -1, EV_TIMEOUT, dispatchCallback, this, nullptr); + event_active(callsTrigger_, 0, 0); } void EventLoop::addFdEvent(int fd, EventType type, - const std::function<void()> &callback) + std::function<void()> &&callback) { - std::unique_ptr<Event> event = std::make_unique<Event>(callback); + std::unique_ptr<Event> event = std::make_unique<Event>(std::move(callback)); short events = (type & Read ? EV_READ : 0) | (type & Write ? EV_WRITE : 0) | EV_PERSIST; @@ -85,9 +108,9 @@ void EventLoop::addFdEvent(int fd, EventType type, } void EventLoop::addTimerEvent(const std::chrono::microseconds period, - const std::function<void()> &callback) + std::function<void()> &&callback) { - std::unique_ptr<Event> event = std::make_unique<Event>(callback); + std::unique_ptr<Event> event = std::make_unique<Event>(std::move(callback)); event->event_ = event_new(base_, -1, EV_PERSIST, &EventLoop::Event::dispatch, event.get()); if (!event->event_) { @@ -108,31 +131,8 @@ void EventLoop::addTimerEvent(const std::chrono::microseconds period, events_.push_back(std::move(event)); } -void EventLoop::dispatchCallback([[maybe_unused]] evutil_socket_t fd, - [[maybe_unused]] short flags, void *param) -{ - EventLoop *loop = static_cast<EventLoop *>(param); - loop->dispatchCall(); -} - -void EventLoop::dispatchCall() -{ - std::function<void()> call; - - { - std::unique_lock<std::mutex> locker(lock_); - if (calls_.empty()) - return; - - call = calls_.front(); - calls_.pop_front(); - } - - call(); -} - -EventLoop::Event::Event(const std::function<void()> &callback) - : callback_(callback), event_(nullptr) +EventLoop::Event::Event(std::function<void()> &&callback) + : callback_(std::move(callback)), event_(nullptr) { } diff --git a/src/apps/common/event_loop.h b/src/apps/common/event_loop.h index ef79e8e5..d8b6df2f 100644 --- a/src/apps/common/event_loop.h +++ b/src/apps/common/event_loop.h @@ -2,17 +2,20 @@ /* * Copyright (C) 2019, Google Inc. * - * event_loop.h - cam - Event loop + * cam - Event loop */ #pragma once #include <chrono> +#include <deque> #include <functional> #include <list> #include <memory> #include <mutex> +#include <libcamera/base/class.h> + #include <event2/util.h> struct event_base; @@ -33,18 +36,20 @@ public: int exec(); void exit(int code = 0); - void callLater(const std::function<void()> &func); + void callLater(std::function<void()> &&func); void addFdEvent(int fd, EventType type, - const std::function<void()> &handler); + std::function<void()> &&handler); - using duration = std::chrono::steady_clock::duration; void addTimerEvent(const std::chrono::microseconds period, - const std::function<void()> &handler); + std::function<void()> &&handler); private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(EventLoop) + struct Event { - Event(const std::function<void()> &callback); + Event(std::function<void()> &&callback); + LIBCAMERA_DISABLE_COPY_AND_MOVE(Event) ~Event(); static void dispatch(int fd, short events, void *arg); @@ -58,11 +63,9 @@ private: struct event_base *base_; int exitCode_; - std::list<std::function<void()>> calls_; + std::deque<std::function<void()>> calls_; + struct event *callsTrigger_ = nullptr; + std::list<std::unique_ptr<Event>> events_; std::mutex lock_; - - static void dispatchCallback(evutil_socket_t fd, short flags, - void *param); - void dispatchCall(); }; diff --git a/src/apps/common/image.cpp b/src/apps/common/image.cpp index fe2cc6da..a2a0f58f 100644 --- a/src/apps/common/image.cpp +++ b/src/apps/common/image.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * image.cpp - Multi-planar image with access to pixel data + * Multi-planar image with access to pixel data */ #include "image.h" diff --git a/src/apps/common/image.h b/src/apps/common/image.h index 7953b177..e47e446b 100644 --- a/src/apps/common/image.h +++ b/src/apps/common/image.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * image.h - Multi-planar image with access to pixel data + * Multi-planar image with access to pixel data */ #pragma once diff --git a/src/apps/common/meson.build b/src/apps/common/meson.build index 479326cd..5b683390 100644 --- a/src/apps/common/meson.build +++ b/src/apps/common/meson.build @@ -3,6 +3,7 @@ apps_sources = files([ 'image.cpp', 'options.cpp', + 'ppm_writer.cpp', 'stream_options.cpp', ]) diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp index 4f7e8691..cae193cc 100644 --- a/src/apps/common/options.cpp +++ b/src/apps/common/options.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * options.cpp - cam - Options parsing + * cam - Options parsing */ #include <assert.h> @@ -10,6 +10,7 @@ #include <iomanip> #include <iostream> #include <string.h> +#include <vector> #include "options.h" @@ -879,8 +880,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) * Allocate short and long options arrays large enough to contain all * options. */ - char shortOptions[optionsMap_.size() * 3 + 2]; - struct option longOptions[optionsMap_.size() + 1]; + std::vector<char> shortOptions(optionsMap_.size() * 3 + 2); + std::vector<struct option> longOptions(optionsMap_.size() + 1); unsigned int ids = 0; unsigned int idl = 0; @@ -922,7 +923,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) opterr = 0; while (true) { - int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr); + int c = getopt_long(argc, argv, shortOptions.data(), + longOptions.data(), nullptr); if (c == -1) break; @@ -1038,7 +1040,7 @@ void OptionsParser::usageOptions(const std::list<Option> &options, std::cerr << std::setw(indent) << argument; - for (const char *help = option.help, *end = help; end; ) { + for (const char *help = option.help, *end = help; end;) { end = strchr(help, '\n'); if (end) { std::cerr << std::string(help, end - help + 1); diff --git a/src/apps/common/options.h b/src/apps/common/options.h index 4ddd4987..9771aa7a 100644 --- a/src/apps/common/options.h +++ b/src/apps/common/options.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * options.h - cam - Options parsing + * cam - Options parsing */ #pragma once diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp new file mode 100644 index 00000000..368de8bf --- /dev/null +++ b/src/apps/common/ppm_writer.cpp @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + * + * PPM writer + */ + +#include "ppm_writer.h" + +#include <errno.h> +#include <fstream> +#include <iostream> + +#include <libcamera/formats.h> +#include <libcamera/pixel_format.h> + +using namespace libcamera; + +int PPMWriter::write(const char *filename, + const StreamConfiguration &config, + const Span<uint8_t> &data) +{ + if (config.pixelFormat != formats::BGR888) { + std::cerr << "Only BGR888 output pixel format is supported (" + << config.pixelFormat << " requested)" << std::endl; + return -EINVAL; + } + + std::ofstream output(filename, std::ios::binary); + if (!output) { + std::cerr << "Failed to open ppm file: " << filename << std::endl; + return -EIO; + } + + output << "P6" << std::endl + << config.size.width << " " << config.size.height << std::endl + << "255" << std::endl; + if (!output) { + std::cerr << "Failed to write the file header" << std::endl; + return -EIO; + } + + const unsigned int rowLength = config.size.width * 3; + const char *row = reinterpret_cast<const char *>(data.data()); + for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) { + output.write(row, rowLength); + if (!output) { + std::cerr << "Failed to write image data at row " << y << std::endl; + return -EIO; + } + } + + return 0; +} diff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h new file mode 100644 index 00000000..8c8d2e15 --- /dev/null +++ b/src/apps/common/ppm_writer.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat, Inc. + * + * PPM writer + */ + +#pragma once + +#include <libcamera/base/span.h> + +#include <libcamera/stream.h> + +class PPMWriter +{ +public: + static int write(const char *filename, + const libcamera::StreamConfiguration &config, + const libcamera::Span<uint8_t> &data); +}; diff --git a/src/apps/common/stream_options.cpp b/src/apps/common/stream_options.cpp index 663b979a..288f8653 100644 --- a/src/apps/common/stream_options.cpp +++ b/src/apps/common/stream_options.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * stream_options.cpp - Helper to parse options for streams + * Helper to parse options for streams */ #include "stream_options.h" @@ -42,9 +42,8 @@ KeyValueParser::Options StreamKeyValueParser::parse(const char *arguments) std::vector<StreamRole> StreamKeyValueParser::roles(const OptionValue &values) { - /* If no configuration values to examine default to viewfinder. */ if (values.empty()) - return { StreamRole::Viewfinder }; + return {}; const std::vector<OptionValue> &streamParameters = values.toArray(); diff --git a/src/apps/common/stream_options.h b/src/apps/common/stream_options.h index a5f3bde0..a93f104c 100644 --- a/src/apps/common/stream_options.h +++ b/src/apps/common/stream_options.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * stream_options.h - Helper to parse options for streams + * Helper to parse options for streams */ #pragma once diff --git a/src/apps/ipa-verify/main.cpp b/src/apps/ipa-verify/main.cpp index 76ba5073..0903cd85 100644 --- a/src/apps/ipa-verify/main.cpp +++ b/src/apps/ipa-verify/main.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Ideas on Board Oy * - * ipa_verify.cpp - Verify signature on an IPA module + * Verify signature on an IPA module */ #include <iostream> diff --git a/src/apps/lc-compliance/capture_test.cpp b/src/apps/lc-compliance/capture_test.cpp deleted file mode 100644 index 1dcfcf92..00000000 --- a/src/apps/lc-compliance/capture_test.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2020, Google Inc. - * Copyright (C) 2021, Collabora Ltd. - * - * capture_test.cpp - Test camera capture - */ - -#include <iostream> - -#include <gtest/gtest.h> - -#include "environment.h" -#include "simple_capture.h" - -using namespace libcamera; - -const std::vector<int> NUMREQUESTS = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; -const std::vector<StreamRole> ROLES = { - StreamRole::Raw, - StreamRole::StillCapture, - StreamRole::VideoRecording, - StreamRole::Viewfinder -}; - -class SingleStream : public testing::TestWithParam<std::tuple<StreamRole, int>> -{ -public: - static std::string nameParameters(const testing::TestParamInfo<SingleStream::ParamType> &info); - -protected: - void SetUp() override; - void TearDown() override; - - std::shared_ptr<Camera> camera_; -}; - -/* - * We use gtest's SetUp() and TearDown() instead of constructor and destructor - * in order to be able to assert on them. - */ -void SingleStream::SetUp() -{ - Environment *env = Environment::get(); - - camera_ = env->cm()->get(env->cameraId()); - - ASSERT_EQ(camera_->acquire(), 0); -} - -void SingleStream::TearDown() -{ - if (!camera_) - return; - - camera_->release(); - camera_.reset(); -} - -std::string SingleStream::nameParameters(const testing::TestParamInfo<SingleStream::ParamType> &info) -{ - std::map<StreamRole, std::string> rolesMap = { - { StreamRole::Raw, "Raw" }, - { StreamRole::StillCapture, "StillCapture" }, - { StreamRole::VideoRecording, "VideoRecording" }, - { StreamRole::Viewfinder, "Viewfinder" } - }; - - std::string roleName = rolesMap[std::get<0>(info.param)]; - std::string numRequestsName = std::to_string(std::get<1>(info.param)); - - return roleName + "_" + numRequestsName; -} - -/* - * Test single capture cycles - * - * Makes sure the camera completes the exact number of requests queued. Example - * failure is a camera that completes less requests than the number of requests - * queued. - */ -TEST_P(SingleStream, Capture) -{ - auto [role, numRequests] = GetParam(); - - SimpleCaptureBalanced capture(camera_); - - capture.configure(role); - - capture.capture(numRequests); -} - -/* - * Test multiple start/stop cycles - * - * Makes sure the camera supports multiple start/stop cycles. Example failure is - * a camera that does not clean up correctly in its error path but is only - * tested by single-capture applications. - */ -TEST_P(SingleStream, CaptureStartStop) -{ - auto [role, numRequests] = GetParam(); - unsigned int numRepeats = 3; - - SimpleCaptureBalanced capture(camera_); - - capture.configure(role); - - for (unsigned int starts = 0; starts < numRepeats; starts++) - capture.capture(numRequests); -} - -/* - * Test unbalanced stop - * - * Makes sure the camera supports a stop with requests queued. Example failure - * is a camera that does not handle cancelation of buffers coming back from the - * video device while stopping. - */ -TEST_P(SingleStream, UnbalancedStop) -{ - auto [role, numRequests] = GetParam(); - - SimpleCaptureUnbalanced capture(camera_); - - capture.configure(role); - - capture.capture(numRequests); -} - -INSTANTIATE_TEST_SUITE_P(CaptureTests, - SingleStream, - testing::Combine(testing::ValuesIn(ROLES), - testing::ValuesIn(NUMREQUESTS)), - SingleStream::nameParameters); diff --git a/src/apps/lc-compliance/environment.cpp b/src/apps/lc-compliance/environment.cpp index 5eb3775f..987264f1 100644 --- a/src/apps/lc-compliance/environment.cpp +++ b/src/apps/lc-compliance/environment.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Collabora Ltd. * - * environment.cpp - Common environment for tests + * Common environment for tests */ #include "environment.h" diff --git a/src/apps/lc-compliance/environment.h b/src/apps/lc-compliance/environment.h index 0debbcce..834c722e 100644 --- a/src/apps/lc-compliance/environment.h +++ b/src/apps/lc-compliance/environment.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Collabora Ltd. * - * environment.h - Common environment for tests + * Common environment for tests */ #pragma once @@ -23,5 +23,5 @@ private: Environment() = default; std::string cameraId_; - libcamera::CameraManager *cm_; + libcamera::CameraManager *cm_ = nullptr; }; diff --git a/src/apps/lc-compliance/helpers/capture.cpp b/src/apps/lc-compliance/helpers/capture.cpp new file mode 100644 index 00000000..2a3fa3b6 --- /dev/null +++ b/src/apps/lc-compliance/helpers/capture.cpp @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020-2021, Google Inc. + * + * Simple capture helper + */ + +#include "capture.h" + +#include <assert.h> + +#include <gtest/gtest.h> + +using namespace libcamera; + +Capture::Capture(std::shared_ptr<Camera> camera) + : camera_(std::move(camera)), allocator_(camera_) +{ +} + +Capture::~Capture() +{ + stop(); +} + +void Capture::configure(libcamera::Span<const libcamera::StreamRole> roles) +{ + assert(!roles.empty()); + + config_ = camera_->generateConfiguration(roles); + if (!config_) + GTEST_SKIP() << "Roles not supported by camera"; + + ASSERT_EQ(config_->size(), roles.size()) << "Unexpected number of streams in configuration"; + + /* + * Set the buffers count to the largest value across all streams. + * \todo: Should all streams from a Camera have the same buffer count ? + */ + auto largest = + std::max_element(config_->begin(), config_->end(), + [](const StreamConfiguration &l, const StreamConfiguration &r) + { return l.bufferCount < r.bufferCount; }); + + assert(largest != config_->end()); + + for (auto &cfg : *config_) + cfg.bufferCount = largest->bufferCount; + + if (config_->validate() != CameraConfiguration::Valid) { + config_.reset(); + FAIL() << "Configuration not valid"; + } + + if (camera_->configure(config_.get())) { + config_.reset(); + FAIL() << "Failed to configure camera"; + } +} + +void Capture::run(unsigned int captureLimit, std::optional<unsigned int> queueLimit) +{ + assert(!queueLimit || captureLimit <= *queueLimit); + + captureLimit_ = captureLimit; + queueLimit_ = queueLimit; + + captureCount_ = queueCount_ = 0; + + EventLoop loop; + loop_ = &loop; + + start(); + + for (const auto &request : requests_) + queueRequest(request.get()); + + EXPECT_EQ(loop_->exec(), 0); + + stop(); + + EXPECT_LE(captureLimit_, captureCount_); + EXPECT_LE(captureCount_, queueCount_); + EXPECT_TRUE(!queueLimit_ || queueCount_ <= *queueLimit_); +} + +int Capture::queueRequest(libcamera::Request *request) +{ + if (queueLimit_ && queueCount_ >= *queueLimit_) + return 0; + + int ret = camera_->queueRequest(request); + if (ret < 0) + return ret; + + queueCount_ += 1; + return 0; +} + +void Capture::requestComplete(Request *request) +{ + captureCount_++; + if (captureCount_ >= captureLimit_) { + loop_->exit(0); + return; + } + + EXPECT_EQ(request->status(), Request::Status::RequestComplete) + << "Request didn't complete successfully"; + + request->reuse(Request::ReuseBuffers); + if (queueRequest(request)) + loop_->exit(-EINVAL); +} + +void Capture::start() +{ + assert(config_); + assert(!config_->empty()); + assert(!allocator_.allocated()); + assert(requests_.empty()); + + const auto bufferCount = config_->at(0).bufferCount; + + /* No point in testing less requests then the camera depth. */ + if (queueLimit_ && *queueLimit_ < bufferCount) { + GTEST_SKIP() << "Camera needs " << bufferCount + << " requests, can't test only " << *queueLimit_; + } + + for (std::size_t i = 0; i < bufferCount; i++) { + std::unique_ptr<Request> request = camera_->createRequest(); + ASSERT_TRUE(request) << "Can't create request"; + requests_.push_back(std::move(request)); + } + + for (const auto &cfg : *config_) { + Stream *stream = cfg.stream(); + + int count = allocator_.allocate(stream); + ASSERT_GE(count, 0) << "Failed to allocate buffers"; + + const auto &buffers = allocator_.buffers(stream); + ASSERT_EQ(buffers.size(), bufferCount) << "Mismatching buffer count"; + + for (std::size_t i = 0; i < bufferCount; i++) { + ASSERT_EQ(requests_[i]->addBuffer(stream, buffers[i].get()), 0) + << "Failed to add buffer to request"; + } + } + + ASSERT_TRUE(allocator_.allocated()); + + camera_->requestCompleted.connect(this, &Capture::requestComplete); + + ASSERT_EQ(camera_->start(), 0) << "Failed to start camera"; +} + +void Capture::stop() +{ + if (!config_ || !allocator_.allocated()) + return; + + camera_->stop(); + + camera_->requestCompleted.disconnect(this); + + requests_.clear(); + + for (const auto &cfg : *config_) { + EXPECT_EQ(allocator_.free(cfg.stream()), 0) + << "Failed to free buffers associated with stream"; + } + + EXPECT_FALSE(allocator_.allocated()); +} diff --git a/src/apps/lc-compliance/helpers/capture.h b/src/apps/lc-compliance/helpers/capture.h new file mode 100644 index 00000000..ea01c11c --- /dev/null +++ b/src/apps/lc-compliance/helpers/capture.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020-2021, Google Inc. + * + * Simple capture helper + */ + +#pragma once + +#include <memory> +#include <optional> + +#include <libcamera/libcamera.h> + +#include "../common/event_loop.h" + +class Capture +{ +public: + Capture(std::shared_ptr<libcamera::Camera> camera); + ~Capture(); + + void configure(libcamera::Span<const libcamera::StreamRole> roles); + void run(unsigned int captureLimit, std::optional<unsigned int> queueLimit = {}); + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(Capture) + + void start(); + void stop(); + + int queueRequest(libcamera::Request *request); + void requestComplete(libcamera::Request *request); + + std::shared_ptr<libcamera::Camera> camera_; + libcamera::FrameBufferAllocator allocator_; + std::unique_ptr<libcamera::CameraConfiguration> config_; + std::vector<std::unique_ptr<libcamera::Request>> requests_; + + EventLoop *loop_ = nullptr; + unsigned int captureLimit_ = 0; + std::optional<unsigned int> queueLimit_; + unsigned int captureCount_ = 0; + unsigned int queueCount_ = 0; +}; diff --git a/src/apps/lc-compliance/main.cpp b/src/apps/lc-compliance/main.cpp index 74e0d4df..e9f0ffbb 100644 --- a/src/apps/lc-compliance/main.cpp +++ b/src/apps/lc-compliance/main.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2020, Google Inc. * Copyright (C) 2021, Collabora Ltd. * - * main.cpp - lc-compliance - The libcamera compliance tool + * lc-compliance - The libcamera compliance tool */ #include <iomanip> @@ -45,13 +45,11 @@ class ThrowListener : public testing::EmptyTestEventListener static void listCameras(CameraManager *cm) { for (const std::shared_ptr<Camera> &cam : cm->cameras()) - std::cout << "- " << cam.get()->id() << std::endl; + std::cout << "- " << cam->id() << std::endl; } static int initCamera(CameraManager *cm, OptionsParser::Options options) { - std::shared_ptr<Camera> camera; - int ret = cm->start(); if (ret) { std::cout << "Failed to start camera manager: " @@ -66,7 +64,7 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options) } const std::string &cameraId = options[OptCamera]; - camera = cm->get(cameraId); + std::shared_ptr<Camera> camera = cm->get(cameraId); if (!camera) { std::cout << "Camera " << cameraId << " not found, available cameras:" << std::endl; listCameras(cm); @@ -82,45 +80,27 @@ static int initCamera(CameraManager *cm, OptionsParser::Options options) static int initGtestParameters(char *arg0, OptionsParser::Options options) { - const std::map<std::string, std::string> gtestFlags = { { "list", "--gtest_list_tests" }, - { "filter", "--gtest_filter" } }; - - int argc = 0; + std::vector<const char *> argv; std::string filterParam; - /* - * +2 to have space for both the 0th argument that is needed but not - * used and the null at the end. - */ - char **argv = new char *[(gtestFlags.size() + 2)]; - if (!argv) - return -ENOMEM; - - argv[0] = arg0; - argc++; + argv.push_back(arg0); - if (options.isSet(OptList)) { - argv[argc] = const_cast<char *>(gtestFlags.at("list").c_str()); - argc++; - } + if (options.isSet(OptList)) + argv.push_back("--gtest_list_tests"); if (options.isSet(OptFilter)) { /* * The filter flag needs to be passed as a single parameter, in * the format --gtest_filter=filterStr */ - filterParam = gtestFlags.at("filter") + "=" + - static_cast<const std::string &>(options[OptFilter]); - - argv[argc] = const_cast<char *>(filterParam.c_str()); - argc++; + filterParam = "--gtest_filter=" + options[OptFilter].toString(); + argv.push_back(filterParam.c_str()); } - argv[argc] = nullptr; - - ::testing::InitGoogleTest(&argc, argv); + argv.push_back(nullptr); - delete[] argv; + int argc = argv.size(); + ::testing::InitGoogleTest(&argc, const_cast<char **>(argv.data())); return 0; } diff --git a/src/apps/lc-compliance/meson.build b/src/apps/lc-compliance/meson.build index 51d9075a..80b9a160 100644 --- a/src/apps/lc-compliance/meson.build +++ b/src/apps/lc-compliance/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 -libgtest = dependency('gtest', required : get_option('lc-compliance'), +libgtest = dependency('gtest', version : '>=1.10.0', + required : get_option('lc-compliance'), fallback : ['gtest', 'gtest_dep']) if opt_lc_compliance.disabled() or not libevent.found() or not libgtest.found() @@ -12,9 +13,15 @@ lc_compliance_enabled = true lc_compliance_sources = files([ 'environment.cpp', + 'helpers/capture.cpp', 'main.cpp', - 'simple_capture.cpp', - 'capture_test.cpp', + 'test_base.cpp', + 'tests/capture_test.cpp', +]) + +lc_compliance_includes = ([ + include_directories('.'), + include_directories('helpers/') ]) lc_compliance = executable('lc-compliance', lc_compliance_sources, @@ -26,4 +33,6 @@ lc_compliance = executable('lc-compliance', lc_compliance_sources, libevent, libgtest, ], - install : true) + include_directories : lc_compliance_includes, + install : true, + install_tag : 'bin-devel') diff --git a/src/apps/lc-compliance/simple_capture.cpp b/src/apps/lc-compliance/simple_capture.cpp deleted file mode 100644 index cf4d7cf3..00000000 --- a/src/apps/lc-compliance/simple_capture.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2020-2021, Google Inc. - * - * simple_capture.cpp - Simple capture helper - */ - -#include <gtest/gtest.h> - -#include "simple_capture.h" - -using namespace libcamera; - -SimpleCapture::SimpleCapture(std::shared_ptr<Camera> camera) - : loop_(nullptr), camera_(camera), - allocator_(std::make_unique<FrameBufferAllocator>(camera)) -{ -} - -SimpleCapture::~SimpleCapture() -{ - stop(); -} - -void SimpleCapture::configure(StreamRole role) -{ - config_ = camera_->generateConfiguration({ role }); - - if (!config_) { - std::cout << "Role not supported by camera" << std::endl; - GTEST_SKIP(); - } - - if (config_->validate() != CameraConfiguration::Valid) { - config_.reset(); - FAIL() << "Configuration not valid"; - } - - if (camera_->configure(config_.get())) { - config_.reset(); - FAIL() << "Failed to configure camera"; - } -} - -void SimpleCapture::start() -{ - Stream *stream = config_->at(0).stream(); - int count = allocator_->allocate(stream); - - ASSERT_GE(count, 0) << "Failed to allocate buffers"; - EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected"; - - camera_->requestCompleted.connect(this, &SimpleCapture::requestComplete); - - ASSERT_EQ(camera_->start(), 0) << "Failed to start camera"; -} - -void SimpleCapture::stop() -{ - if (!config_ || !allocator_->allocated()) - return; - - camera_->stop(); - - camera_->requestCompleted.disconnect(this); - - Stream *stream = config_->at(0).stream(); - requests_.clear(); - allocator_->free(stream); -} - -/* SimpleCaptureBalanced */ - -SimpleCaptureBalanced::SimpleCaptureBalanced(std::shared_ptr<Camera> camera) - : SimpleCapture(camera) -{ -} - -void SimpleCaptureBalanced::capture(unsigned int numRequests) -{ - start(); - - Stream *stream = config_->at(0).stream(); - const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream); - - /* No point in testing less requests then the camera depth. */ - if (buffers.size() > numRequests) { - std::cout << "Camera needs " + std::to_string(buffers.size()) - + " requests, can't test only " - + std::to_string(numRequests) << std::endl; - GTEST_SKIP(); - } - - queueCount_ = 0; - captureCount_ = 0; - captureLimit_ = numRequests; - - /* Queue the recommended number of reqeuests. */ - for (const std::unique_ptr<FrameBuffer> &buffer : buffers) { - std::unique_ptr<Request> request = camera_->createRequest(); - ASSERT_TRUE(request) << "Can't create request"; - - ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request"; - - ASSERT_EQ(queueRequest(request.get()), 0) << "Failed to queue request"; - - requests_.push_back(std::move(request)); - } - - /* Run capture session. */ - loop_ = new EventLoop(); - loop_->exec(); - stop(); - delete loop_; - - ASSERT_EQ(captureCount_, captureLimit_); -} - -int SimpleCaptureBalanced::queueRequest(Request *request) -{ - queueCount_++; - if (queueCount_ > captureLimit_) - return 0; - - return camera_->queueRequest(request); -} - -void SimpleCaptureBalanced::requestComplete(Request *request) -{ - captureCount_++; - if (captureCount_ >= captureLimit_) { - loop_->exit(0); - return; - } - - request->reuse(Request::ReuseBuffers); - if (queueRequest(request)) - loop_->exit(-EINVAL); -} - -/* SimpleCaptureUnbalanced */ - -SimpleCaptureUnbalanced::SimpleCaptureUnbalanced(std::shared_ptr<Camera> camera) - : SimpleCapture(camera) -{ -} - -void SimpleCaptureUnbalanced::capture(unsigned int numRequests) -{ - start(); - - Stream *stream = config_->at(0).stream(); - const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_->buffers(stream); - - captureCount_ = 0; - captureLimit_ = numRequests; - - /* Queue the recommended number of reqeuests. */ - for (const std::unique_ptr<FrameBuffer> &buffer : buffers) { - std::unique_ptr<Request> request = camera_->createRequest(); - ASSERT_TRUE(request) << "Can't create request"; - - ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request"; - - ASSERT_EQ(camera_->queueRequest(request.get()), 0) << "Failed to queue request"; - - requests_.push_back(std::move(request)); - } - - /* Run capture session. */ - loop_ = new EventLoop(); - int status = loop_->exec(); - stop(); - delete loop_; - - ASSERT_EQ(status, 0); -} - -void SimpleCaptureUnbalanced::requestComplete(Request *request) -{ - captureCount_++; - if (captureCount_ >= captureLimit_) { - loop_->exit(0); - return; - } - - request->reuse(Request::ReuseBuffers); - if (camera_->queueRequest(request)) - loop_->exit(-EINVAL); -} diff --git a/src/apps/lc-compliance/simple_capture.h b/src/apps/lc-compliance/simple_capture.h deleted file mode 100644 index 2911d601..00000000 --- a/src/apps/lc-compliance/simple_capture.h +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2020-2021, Google Inc. - * - * simple_capture.h - Simple capture helper - */ - -#pragma once - -#include <memory> - -#include <libcamera/libcamera.h> - -#include "../common/event_loop.h" - -class SimpleCapture -{ -public: - void configure(libcamera::StreamRole role); - -protected: - SimpleCapture(std::shared_ptr<libcamera::Camera> camera); - virtual ~SimpleCapture(); - - void start(); - void stop(); - - virtual void requestComplete(libcamera::Request *request) = 0; - - EventLoop *loop_; - - std::shared_ptr<libcamera::Camera> camera_; - std::unique_ptr<libcamera::FrameBufferAllocator> allocator_; - std::unique_ptr<libcamera::CameraConfiguration> config_; - std::vector<std::unique_ptr<libcamera::Request>> requests_; -}; - -class SimpleCaptureBalanced : public SimpleCapture -{ -public: - SimpleCaptureBalanced(std::shared_ptr<libcamera::Camera> camera); - - void capture(unsigned int numRequests); - -private: - int queueRequest(libcamera::Request *request); - void requestComplete(libcamera::Request *request) override; - - unsigned int queueCount_; - unsigned int captureCount_; - unsigned int captureLimit_; -}; - -class SimpleCaptureUnbalanced : public SimpleCapture -{ -public: - SimpleCaptureUnbalanced(std::shared_ptr<libcamera::Camera> camera); - - void capture(unsigned int numRequests); - -private: - void requestComplete(libcamera::Request *request) override; - - unsigned int captureCount_; - unsigned int captureLimit_; -}; diff --git a/src/apps/lc-compliance/test_base.cpp b/src/apps/lc-compliance/test_base.cpp new file mode 100644 index 00000000..c9957b9e --- /dev/null +++ b/src/apps/lc-compliance/test_base.cpp @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Collabora Ltd. + * + * test_base.cpp - Base definitions for tests + */ + +#include "test_base.h" + +#include "environment.h" + +void CameraHolder::acquireCamera() +{ + Environment *env = Environment::get(); + + camera_ = env->cm()->get(env->cameraId()); + + ASSERT_EQ(camera_->acquire(), 0); +} + +void CameraHolder::releaseCamera() +{ + if (!camera_) + return; + + camera_->release(); + camera_.reset(); +} diff --git a/src/apps/lc-compliance/test_base.h b/src/apps/lc-compliance/test_base.h new file mode 100644 index 00000000..52347749 --- /dev/null +++ b/src/apps/lc-compliance/test_base.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021, Collabora Ltd. + * + * test_base.h - Base definitions for tests + */ + +#ifndef __LC_COMPLIANCE_TEST_BASE_H__ +#define __LC_COMPLIANCE_TEST_BASE_H__ + +#include <libcamera/libcamera.h> + +#include <gtest/gtest.h> + +class CameraHolder +{ +protected: + void acquireCamera(); + void releaseCamera(); + + std::shared_ptr<libcamera::Camera> camera_; +}; + +#endif /* __LC_COMPLIANCE_TEST_BASE_H__ */ diff --git a/src/apps/lc-compliance/tests/capture_test.cpp b/src/apps/lc-compliance/tests/capture_test.cpp new file mode 100644 index 00000000..29d8b7f8 --- /dev/null +++ b/src/apps/lc-compliance/tests/capture_test.cpp @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * Copyright (C) 2021, Collabora Ltd. + * + * Test camera capture + */ + +#include "capture.h" + +#include <sstream> +#include <string> +#include <tuple> +#include <vector> + +#include <gtest/gtest.h> + +#include "test_base.h" + +namespace { + +using namespace libcamera; + +class SimpleCapture : public testing::TestWithParam<std::tuple<std::vector<StreamRole>, int>>, public CameraHolder +{ +public: + static std::string nameParameters(const testing::TestParamInfo<SimpleCapture::ParamType> &info); + +protected: + void SetUp() override; + void TearDown() override; +}; + +/* + * We use gtest's SetUp() and TearDown() instead of constructor and destructor + * in order to be able to assert on them. + */ +void SimpleCapture::SetUp() +{ + acquireCamera(); +} + +void SimpleCapture::TearDown() +{ + releaseCamera(); +} + +std::string SimpleCapture::nameParameters(const testing::TestParamInfo<SimpleCapture::ParamType> &info) +{ + const auto &[roles, numRequests] = info.param; + std::ostringstream ss; + + for (StreamRole r : roles) + ss << r << '_'; + + ss << '_' << numRequests; + + return ss.str(); +} + +/* + * Test single capture cycles + * + * Makes sure the camera completes the exact number of requests queued. Example + * failure is a camera that completes less requests than the number of requests + * queued. + */ +TEST_P(SimpleCapture, Capture) +{ + const auto &[roles, numRequests] = GetParam(); + + Capture capture(camera_); + + capture.configure(roles); + + capture.run(numRequests, numRequests); +} + +/* + * Test multiple start/stop cycles + * + * Makes sure the camera supports multiple start/stop cycles. Example failure is + * a camera that does not clean up correctly in its error path but is only + * tested by single-capture applications. + */ +TEST_P(SimpleCapture, CaptureStartStop) +{ + const auto &[roles, numRequests] = GetParam(); + unsigned int numRepeats = 3; + + Capture capture(camera_); + + capture.configure(roles); + + for (unsigned int starts = 0; starts < numRepeats; starts++) + capture.run(numRequests, numRequests); +} + +/* + * Test unbalanced stop + * + * Makes sure the camera supports a stop with requests queued. Example failure + * is a camera that does not handle cancelation of buffers coming back from the + * video device while stopping. + */ +TEST_P(SimpleCapture, UnbalancedStop) +{ + const auto &[roles, numRequests] = GetParam(); + + Capture capture(camera_); + + capture.configure(roles); + + capture.run(numRequests); +} + +const int NUMREQUESTS[] = { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; + +const std::vector<StreamRole> SINGLEROLES[] = { + { StreamRole::Raw, }, + { StreamRole::StillCapture, }, + { StreamRole::VideoRecording, }, + { StreamRole::Viewfinder, }, +}; + +const std::vector<StreamRole> MULTIROLES[] = { + { StreamRole::Raw, StreamRole::StillCapture }, + { StreamRole::Raw, StreamRole::VideoRecording }, + { StreamRole::StillCapture, StreamRole::VideoRecording }, + { StreamRole::VideoRecording, StreamRole::VideoRecording }, +}; + +INSTANTIATE_TEST_SUITE_P(SingleStream, + SimpleCapture, + testing::Combine(testing::ValuesIn(SINGLEROLES), + testing::ValuesIn(NUMREQUESTS)), + SimpleCapture::nameParameters); + +INSTANTIATE_TEST_SUITE_P(MultiStream, + SimpleCapture, + testing::Combine(testing::ValuesIn(MULTIROLES), + testing::ValuesIn(NUMREQUESTS)), + SimpleCapture::nameParameters); + +} /* namespace */ diff --git a/src/apps/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert index 3695a5e9..fb5109ee 100644 --- a/src/apps/qcam/assets/shader/bayer_8.vert +++ b/src/apps/qcam/assets/shader/bayer_8.vert @@ -19,6 +19,8 @@ Copyright (C) 2021, Linaro attribute vec4 vertexIn; attribute vec2 textureIn; +uniform mat4 proj_matrix; + uniform vec2 tex_size; /* The texture size in pixels */ uniform vec2 tex_step; @@ -47,5 +49,5 @@ void main(void) { yCoord = center.y + vec4(-2.0 * tex_step.y, -tex_step.y, tex_step.y, 2.0 * tex_step.y); - gl_Position = vertexIn; + gl_Position = proj_matrix * vertexIn; } diff --git a/src/apps/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert index 12c41377..907e8741 100644 --- a/src/apps/qcam/assets/shader/identity.vert +++ b/src/apps/qcam/assets/shader/identity.vert @@ -9,10 +9,11 @@ attribute vec4 vertexIn; attribute vec2 textureIn; varying vec2 textureOut; +uniform mat4 proj_matrix; uniform float stride_factor; void main(void) { - gl_Position = vertexIn; + gl_Position = proj_matrix * vertexIn; textureOut = vec2(textureIn.x * stride_factor, textureIn.y); } diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp index 3c8b12a9..6b6d0713 100644 --- a/src/apps/qcam/cam_select_dialog.cpp +++ b/src/apps/qcam/cam_select_dialog.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com> * - * cam_select_dialog.cpp - qcam - Camera Selection dialog + * qcam - Camera Selection dialog */ #include "cam_select_dialog.h" @@ -15,7 +15,9 @@ #include <QComboBox> #include <QDialogButtonBox> #include <QFormLayout> +#include <QGuiApplication> #include <QLabel> +#include <QScreen> #include <QString> CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager, @@ -53,6 +55,14 @@ CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag layout->addRow("Location:", cameraLocation_); layout->addRow("Model:", cameraModel_); layout->addWidget(buttonBox); + + /* + * Decrease the minimum width of dialog to fit on narrow screens, with a + * 20 pixels margin. + */ + QRect screenGeometry = qGuiApp->primaryScreen()->availableGeometry(); + if (screenGeometry.width() < minimumWidth()) + setMinimumWidth(screenGeometry.width() - 20); } CameraSelectorDialog::~CameraSelectorDialog() = default; diff --git a/src/apps/qcam/cam_select_dialog.h b/src/apps/qcam/cam_select_dialog.h index 0b7709ed..4bec9ea9 100644 --- a/src/apps/qcam/cam_select_dialog.h +++ b/src/apps/qcam/cam_select_dialog.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Utkarsh Tiwari <utkarsh02t@gmail.com> * - * cam_select_dialog.h - qcam - Camera Selection dialog + * qcam - Camera Selection dialog */ #pragma once diff --git a/src/apps/qcam/format_converter.cpp b/src/apps/qcam/format_converter.cpp index de76b32c..b025a3c7 100644 --- a/src/apps/qcam/format_converter.cpp +++ b/src/apps/qcam/format_converter.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * format_convert.cpp - qcam - Convert buffer to RGB + * Convert buffer to RGB */ #include "format_converter.h" @@ -249,7 +249,7 @@ void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst dst_stride = width_ * 4; for (src_y = 0, dst_y = 0; dst_y < height_; src_y++, dst_y++) { - for (src_x = 0, dst_x = 0; dst_x < width_; ) { + for (src_x = 0, dst_x = 0; dst_x < width_;) { cb = src[src_y * src_stride + src_x * 4 + cb_pos_]; cr = src[src_y * src_stride + src_x * 4 + cr_pos]; diff --git a/src/apps/qcam/format_converter.h b/src/apps/qcam/format_converter.h index 37dbfae2..819a87a5 100644 --- a/src/apps/qcam/format_converter.h +++ b/src/apps/qcam/format_converter.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * format_convert.h - qcam - Convert buffer to RGB + * Convert buffer to RGB */ #pragma once diff --git a/src/apps/qcam/main.cpp b/src/apps/qcam/main.cpp index 36cb93a5..d0bde141 100644 --- a/src/apps/qcam/main.cpp +++ b/src/apps/qcam/main.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * main.cpp - qcam - The libcamera GUI test application + * qcam - The libcamera GUI test application */ #include <signal.h> @@ -21,6 +21,8 @@ using namespace libcamera; +namespace { + void signalHandler([[maybe_unused]] int signal) { qInfo() << "Exiting"; @@ -52,6 +54,8 @@ OptionsParser::Options parseOptions(int argc, char *argv[]) return options; } +} /* namespace */ + int main(int argc, char **argv) { QApplication app(argc, argv); diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp index 0f16c038..7e3f3da6 100644 --- a/src/apps/qcam/main_window.cpp +++ b/src/apps/qcam/main_window.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * main_window.cpp - qcam - Main application window + * qcam - Main application window */ #include "main_window.h" @@ -37,16 +37,6 @@ using namespace libcamera; -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -/* - * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow - * usage of Qt::fixed unconditionally. - */ -namespace Qt { -constexpr auto fixed = ::fixed; -} /* namespace Qt */ -#endif - /** * \brief Custom QEvent to signal capture completion */ @@ -190,7 +180,7 @@ int MainWindow::createToolbars() action = toolbar_->addAction(QIcon::fromTheme("application-exit", QIcon(":x-circle.svg")), "Quit"); - action->setShortcut(Qt::CTRL | Qt::Key_Q); + action->setShortcut(QKeySequence::Quit); connect(action, &QAction::triggered, this, &MainWindow::quit); /* Camera selector. */ @@ -221,7 +211,7 @@ int MainWindow::createToolbars() action->setShortcut(QKeySequence::SaveAs); connect(action, &QAction::triggered, this, &MainWindow::saveImageAs); -#ifdef HAVE_DNG +#ifdef HAVE_TIFF /* Save Raw action. */ action = toolbar_->addAction(QIcon::fromTheme("camera-photo", QIcon(":aperture.svg")), @@ -261,16 +251,14 @@ void MainWindow::updateTitle() void MainWindow::switchCamera() { /* Get and acquire the new camera. */ - std::string newCameraId = chooseCamera(); + std::shared_ptr<Camera> cam = chooseCamera(); - if (newCameraId.empty()) + if (!cam) return; - if (camera_ && newCameraId == camera_->id()) + if (camera_ && cam == camera_) return; - const std::shared_ptr<Camera> &cam = cm_->get(newCameraId); - if (cam->acquire()) { qInfo() << "Failed to acquire camera" << cam->id().c_str(); return; @@ -292,40 +280,41 @@ void MainWindow::switchCamera() startStopAction_->setChecked(true); /* Display the current cameraId in the toolbar .*/ - cameraSelectButton_->setText(QString::fromStdString(newCameraId)); + cameraSelectButton_->setText(QString::fromStdString(cam->id())); } -std::string MainWindow::chooseCamera() +std::shared_ptr<Camera> MainWindow::chooseCamera() { if (cameraSelectorDialog_->exec() != QDialog::Accepted) - return std::string(); + return {}; - return cameraSelectorDialog_->getCameraId(); + std::string id = cameraSelectorDialog_->getCameraId(); + return cm_->get(id); } int MainWindow::openCamera() { - std::string cameraName; - /* - * Use the camera specified on the command line, if any, or display the - * camera selection dialog box otherwise. + * If a camera is specified on the command line, get it. Otherwise, if + * only one camera is available, pick it automatically, else, display + * the selector dialog box. */ - if (options_.isSet(OptCamera)) - cameraName = static_cast<std::string>(options_[OptCamera]); - else - cameraName = chooseCamera(); - - if (cameraName == "") - return -EINVAL; + if (options_.isSet(OptCamera)) { + std::string cameraName = static_cast<std::string>(options_[OptCamera]); + camera_ = cm_->get(cameraName); + if (!camera_) + qInfo() << "Camera" << cameraName.c_str() << "not found"; + } else { + std::vector<std::shared_ptr<Camera>> cameras = cm_->cameras(); + camera_ = (cameras.size() == 1) ? cameras[0] : chooseCamera(); + if (!camera_) + qInfo() << "No camera detected"; + } - /* Get and acquire the camera. */ - camera_ = cm_->get(cameraName); - if (!camera_) { - qInfo() << "Camera" << cameraName.c_str() << "not found"; + if (!camera_) return -ENODEV; - } + /* Acquire the camera. */ if (camera_->acquire()) { qInfo() << "Failed to acquire camera"; camera_.reset(); @@ -333,7 +322,7 @@ int MainWindow::openCamera() } /* Set the camera switch button with the currently selected Camera id. */ - cameraSelectButton_->setText(QString::fromStdString(cameraName)); + cameraSelectButton_->setText(QString::fromStdString(camera_->id())); return 0; } @@ -367,6 +356,9 @@ int MainWindow::startCapture() /* Verify roles are supported. */ switch (roles.size()) { + case 0: + roles.push_back(StreamRole::Viewfinder); + break; case 1: if (roles[0] != StreamRole::Viewfinder) { qWarning() << "Only viewfinder supported for single stream"; @@ -397,10 +389,7 @@ int MainWindow::startCapture() /* Use a format supported by the viewfinder if available. */ std::vector<PixelFormat> formats = vfConfig.formats().pixelformats(); for (const PixelFormat &format : viewfinder_->nativeFormats()) { - auto match = std::find_if(formats.begin(), formats.end(), - [&](const PixelFormat &f) { - return f == format; - }); + auto match = std::find(formats.begin(), formats.end(), format); if (match != formats.end()) { vfConfig.pixelFormat = format; break; @@ -656,7 +645,7 @@ void MainWindow::captureRaw() void MainWindow::processRaw(FrameBuffer *buffer, [[maybe_unused]] const ControlList &metadata) { -#ifdef HAVE_DNG +#ifdef HAVE_TIFF QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); QString filename = QFileDialog::getSaveFileName(this, "Save DNG", defaultPath, "DNG Files (*.dng)"); diff --git a/src/apps/qcam/main_window.h b/src/apps/qcam/main_window.h index 2e3e1b5c..81fcf915 100644 --- a/src/apps/qcam/main_window.h +++ b/src/apps/qcam/main_window.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * main_window.h - qcam - Main application window + * qcam - Main application window */ #pragma once @@ -73,7 +73,7 @@ private Q_SLOTS: private: int createToolbars(); - std::string chooseCamera(); + std::shared_ptr<libcamera::Camera> chooseCamera(); int openCamera(); int startCapture(); diff --git a/src/apps/qcam/meson.build b/src/apps/qcam/meson.build index 2e77146c..f7c14064 100644 --- a/src/apps/qcam/meson.build +++ b/src/apps/qcam/meson.build @@ -1,13 +1,13 @@ # SPDX-License-Identifier: CC0-1.0 -qt5 = import('qt5') -qt5_dep = dependency('qt5', +qt6 = import('qt6') +qt6_dep = dependency('qt6', method : 'pkg-config', - modules : ['Core', 'Gui', 'Widgets'], + modules : ['Core', 'Gui', 'OpenGL', 'OpenGLWidgets', 'Widgets'], required : get_option('qcam'), - version : '>=5.4') + version : '>=6.2') -if not qt5_dep.found() +if not qt6_dep.found() qcam_enabled = false subdir_done() endif @@ -20,54 +20,40 @@ qcam_sources = files([ 'main.cpp', 'main_window.cpp', 'message_handler.cpp', + 'viewfinder_gl.cpp', 'viewfinder_qt.cpp', ]) qcam_moc_headers = files([ 'cam_select_dialog.h', 'main_window.h', + 'viewfinder_gl.h', 'viewfinder_qt.h', ]) qcam_resources = files([ 'assets/feathericons/feathericons.qrc', + 'assets/shader/shaders.qrc', ]) -qt5_cpp_args = [apps_cpp_args, '-DQT_NO_KEYWORDS'] +qt6_cpp_args = [ + apps_cpp_args, + '-DQT_NO_KEYWORDS', + '-Wno-extra-semi', +] -if cxx.has_header_symbol('QOpenGLWidget', 'QOpenGLWidget', - dependencies : qt5_dep, args : '-fPIC') - qcam_sources += files([ - 'viewfinder_gl.cpp', - ]) - qcam_moc_headers += files([ - 'viewfinder_gl.h', - ]) - qcam_resources += files([ - 'assets/shader/shaders.qrc' - ]) -endif - -# gcc 9 introduced a deprecated-copy warning that is triggered by Qt until -# Qt 5.13. clang 10 introduced the same warning, but detects more issues -# that are not fixed in Qt yet. Disable the warning manually in both cases. -if ((cc.get_id() == 'gcc' and cc.version().version_compare('>=9.0') and - qt5_dep.version().version_compare('<5.13')) or - (cc.get_id() == 'clang' and cc.version().version_compare('>=10.0'))) - qt5_cpp_args += ['-Wno-deprecated-copy'] -endif - -resources = qt5.preprocess(moc_headers : qcam_moc_headers, +resources = qt6.preprocess(moc_headers : qcam_moc_headers, qresources : qcam_resources, - dependencies : qt5_dep) + dependencies : qt6_dep) qcam = executable('qcam', qcam_sources, resources, install : true, + install_tag : 'bin', link_with : apps_lib, dependencies : [ libatomic, libcamera_public, libtiff, - qt5_dep, + qt6_dep, ], - cpp_args : qt5_cpp_args) + cpp_args : qt6_cpp_args) diff --git a/src/apps/qcam/message_handler.cpp b/src/apps/qcam/message_handler.cpp index 261623e1..c89714a9 100644 --- a/src/apps/qcam/message_handler.cpp +++ b/src/apps/qcam/message_handler.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com> * - * message_handler.cpp - qcam - Log message handling + * qcam - Log message handling */ #include "message_handler.h" diff --git a/src/apps/qcam/message_handler.h b/src/apps/qcam/message_handler.h index 56294d37..92ef74d1 100644 --- a/src/apps/qcam/message_handler.h +++ b/src/apps/qcam/message_handler.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com> * - * message_handler.cpp - qcam - Log message handling + * Log message handling */ #pragma once diff --git a/src/apps/qcam/viewfinder.h b/src/apps/qcam/viewfinder.h index a57446e8..914f88ec 100644 --- a/src/apps/qcam/viewfinder.h +++ b/src/apps/qcam/viewfinder.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * viewfinder.h - qcam - Viewfinder base class + * qcam - Viewfinder base class */ #pragma once diff --git a/src/apps/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp index f83b99ad..f31956ff 100644 --- a/src/apps/qcam/viewfinder_gl.cpp +++ b/src/apps/qcam/viewfinder_gl.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Linaro * - * viewfinderGL.cpp - OpenGL Viewfinder for rendering by OpenGL shader + * OpenGL Viewfinder for rendering by OpenGL shader */ #include "viewfinder_gl.h" @@ -12,6 +12,7 @@ #include <QByteArray> #include <QFile> #include <QImage> +#include <QMatrix4x4> #include <QStringList> #include <libcamera/formats.h> @@ -443,15 +444,11 @@ bool ViewFinderGL::createFragmentShader() close(); } - /* Bind shader pipeline for use */ - if (!shaderProgram_.bind()) { - qWarning() << "[ViewFinderGL]:" << shaderProgram_.log(); - close(); - } - attributeVertex = shaderProgram_.attributeLocation("vertexIn"); attributeTexture = shaderProgram_.attributeLocation("textureIn"); + vertexBuffer_.bind(); + shaderProgram_.enableAttributeArray(attributeVertex); shaderProgram_.setAttributeBuffer(attributeVertex, GL_FLOAT, @@ -466,6 +463,9 @@ bool ViewFinderGL::createFragmentShader() 2, 2 * sizeof(GLfloat)); + vertexBuffer_.release(); + + projMatrixUniform_ = shaderProgram_.uniformLocation("proj_matrix"); textureUniformY_ = shaderProgram_.uniformLocation("tex_y"); textureUniformU_ = shaderProgram_.uniformLocation("tex_u"); textureUniformV_ = shaderProgram_.uniformLocation("tex_v"); @@ -511,7 +511,7 @@ void ViewFinderGL::initializeGL() glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); - static const GLfloat coordinates[2][4][2]{ + const GLfloat coordinates[2][4][2]{ { /* Vertex coordinates */ { -1.0f, -1.0f }, @@ -535,8 +535,6 @@ void ViewFinderGL::initializeGL() /* Create Vertex Shader */ if (!createVertexShader()) qWarning() << "[ViewFinderGL]: create vertex shader failed."; - - glClearColor(1.0f, 1.0f, 1.0f, 0.0f); } void ViewFinderGL::doRender() @@ -805,28 +803,42 @@ void ViewFinderGL::doRender() shaderProgram_.setUniformValue(textureUniformStrideFactor_, static_cast<float>(size_.width() - 1) / (stridePixels - 1)); + + /* + * Place the viewfinder in the centre of the widget, preserving the + * aspect ratio of the image. + */ + QMatrix4x4 projMatrix; + QSizeF scaledSize = size_.scaled(size(), Qt::KeepAspectRatio); + projMatrix.scale(scaledSize.width() / size().width(), + scaledSize.height() / size().height()); + + shaderProgram_.setUniformValue(projMatrixUniform_, projMatrix); } void ViewFinderGL::paintGL() { - if (!fragmentShader_) + if (!fragmentShader_) { if (!createFragmentShader()) { qWarning() << "[ViewFinderGL]:" << "create fragment shader failed."; } + } - if (image_) { - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - doRender(); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + /* Bind shader pipeline for use. */ + if (!shaderProgram_.bind()) { + qWarning() << "[ViewFinderGL]:" << shaderProgram_.log(); + close(); } -} -void ViewFinderGL::resizeGL(int w, int h) -{ - glViewport(0, 0, w, h); + if (!image_) + return; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + doRender(); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } QSize ViewFinderGL::sizeHint() const diff --git a/src/apps/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h index 68c2912d..23c657bc 100644 --- a/src/apps/qcam/viewfinder_gl.h +++ b/src/apps/qcam/viewfinder_gl.h @@ -2,8 +2,7 @@ /* * Copyright (C) 2020, Linaro * - * viewfinder_GL.h - OpenGL Viewfinder for rendering by OpenGL shader - * + * OpenGL Viewfinder for rendering by OpenGL shader */ #pragma once @@ -52,7 +51,6 @@ Q_SIGNALS: protected: void initializeGL() override; void paintGL() override; - void resizeGL(int w, int h) override; QSize sizeHint() const override; private: @@ -89,6 +87,7 @@ private: /* Common texture parameters */ GLuint textureMinMagFilters_; + GLuint projMatrixUniform_; /* YUV texture parameters */ GLuint textureUniformU_; diff --git a/src/apps/qcam/viewfinder_qt.cpp b/src/apps/qcam/viewfinder_qt.cpp index 62ed5e7c..1a238922 100644 --- a/src/apps/qcam/viewfinder_qt.cpp +++ b/src/apps/qcam/viewfinder_qt.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * viewfinder_qt.cpp - qcam - QPainter-based ViewFinder + * qcam - QPainter-based ViewFinder */ #include "viewfinder_qt.h" @@ -18,6 +18,7 @@ #include <QMap> #include <QMutexLocker> #include <QPainter> +#include <QResizeEvent> #include <QtDebug> #include "../common/image.h" @@ -26,23 +27,23 @@ static const QMap<libcamera::PixelFormat, QImage::Format> nativeFormats { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) { libcamera::formats::ABGR8888, QImage::Format_RGBX8888 }, { libcamera::formats::XBGR8888, QImage::Format_RGBX8888 }, -#endif { libcamera::formats::ARGB8888, QImage::Format_RGB32 }, { libcamera::formats::XRGB8888, QImage::Format_RGB32 }, -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) { libcamera::formats::RGB888, QImage::Format_BGR888 }, -#endif { libcamera::formats::BGR888, QImage::Format_RGB888 }, { libcamera::formats::RGB565, QImage::Format_RGB16 }, }; ViewFinderQt::ViewFinderQt(QWidget *parent) - : QWidget(parent), buffer_(nullptr) + : QWidget(parent), place_(rect()), buffer_(nullptr) { icon_ = QIcon(":camera-off.svg"); + + QPalette pal = palette(); + pal.setColor(QPalette::Window, Qt::black); + setPalette(pal); } ViewFinderQt::~ViewFinderQt() @@ -117,6 +118,11 @@ void ViewFinderQt::render(libcamera::FrameBuffer *buffer, Image *image) } } + /* + * Indicate the widget paints all its pixels, to optimize rendering by + * skipping erasing the widget before painting. + */ + setAttribute(Qt::WA_OpaquePaintEvent, true); update(); if (buffer) @@ -132,6 +138,11 @@ void ViewFinderQt::stop() buffer_ = nullptr; } + /* + * The logo has a transparent background, reenable erasing the widget + * before painting. + */ + setAttribute(Qt::WA_OpaquePaintEvent, false); update(); } @@ -146,9 +157,23 @@ void ViewFinderQt::paintEvent(QPaintEvent *) { QPainter painter(this); - /* If we have an image, draw it. */ + painter.setBrush(palette().window()); + + /* If we have an image, draw it, with black letterbox rectangles. */ if (!image_.isNull()) { - painter.drawImage(rect(), image_, image_.rect()); + if (place_.width() < width()) { + QRect rect{ 0, 0, (width() - place_.width()) / 2, height() }; + painter.drawRect(rect); + rect.moveLeft(place_.right()); + painter.drawRect(rect); + } else { + QRect rect{ 0, 0, width(), (height() - place_.height()) / 2 }; + painter.drawRect(rect); + rect.moveTop(place_.bottom()); + painter.drawRect(rect); + } + + painter.drawImage(place_, image_, image_.rect()); return; } @@ -173,7 +198,6 @@ void ViewFinderQt::paintEvent(QPaintEvent *) else point.setY((height() - pixmap_.height()) / 2); - painter.setBackgroundMode(Qt::OpaqueMode); painter.drawPixmap(point, pixmap_); } @@ -181,3 +205,14 @@ QSize ViewFinderQt::sizeHint() const { return size_.isValid() ? size_ : QSize(640, 480); } + +void ViewFinderQt::resizeEvent(QResizeEvent *event) +{ + if (!size_.isValid()) + return; + + place_.setSize(size_.scaled(event->size(), Qt::KeepAspectRatio)); + place_.moveCenter(rect().center()); + + QWidget::resizeEvent(event); +} diff --git a/src/apps/qcam/viewfinder_qt.h b/src/apps/qcam/viewfinder_qt.h index eb3a9988..50fde88e 100644 --- a/src/apps/qcam/viewfinder_qt.h +++ b/src/apps/qcam/viewfinder_qt.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * viewfinder_qt.h - qcam - QPainter-based ViewFinder + * qcam - QPainter-based ViewFinder */ #pragma once @@ -44,6 +44,7 @@ Q_SIGNALS: protected: void paintEvent(QPaintEvent *) override; + void resizeEvent(QResizeEvent *) override; QSize sizeHint() const override; private: @@ -51,6 +52,7 @@ private: libcamera::PixelFormat format_; QSize size_; + QRect place_; /* Camera stopped icon */ QSize vfSize_; diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in new file mode 100644 index 00000000..89c530da --- /dev/null +++ b/src/gstreamer/gstlibcamera-controls.cpp.in @@ -0,0 +1,327 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Jaslo Ziska + * + * GStreamer Camera Controls + * + * This file is auto-generated. Do not edit. + */ + +#include <vector> + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> +#include <libcamera/geometry.h> + +#include "gstlibcamera-controls.h" + +using namespace libcamera; + +static void value_set_rectangle(GValue *value, const Rectangle &rect) +{ + Point top_left = rect.topLeft(); + Size size = rect.size(); + + GValue x = G_VALUE_INIT; + g_value_init(&x, G_TYPE_INT); + g_value_set_int(&x, top_left.x); + gst_value_array_append_and_take_value(value, &x); + + GValue y = G_VALUE_INIT; + g_value_init(&y, G_TYPE_INT); + g_value_set_int(&y, top_left.y); + gst_value_array_append_and_take_value(value, &y); + + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_INT); + g_value_set_int(&width, size.width); + gst_value_array_append_and_take_value(value, &width); + + GValue height = G_VALUE_INIT; + g_value_init(&height, G_TYPE_INT); + g_value_set_int(&height, size.height); + gst_value_array_append_and_take_value(value, &height); +} + +static Rectangle value_get_rectangle(const GValue *value) +{ + const GValue *r; + r = gst_value_array_get_value(value, 0); + int x = g_value_get_int(r); + r = gst_value_array_get_value(value, 1); + int y = g_value_get_int(r); + r = gst_value_array_get_value(value, 2); + int w = g_value_get_int(r); + r = gst_value_array_get_value(value, 3); + int h = g_value_get_int(r); + + return Rectangle(x, y, w, h); +} + +{% for vendor, ctrls in controls %} +{%- for ctrl in ctrls if ctrl.is_enum %} +static const GEnumValue {{ ctrl.name|snake_case }}_types[] = { +{%- for enum in ctrl.enum_values %} + { + controls::{{ ctrl.namespace }}{{ enum.name }}, + {{ enum.description|format_description|indent_str('\t\t') }}, + "{{ enum.gst_name }}" + }, +{%- endfor %} + {0, NULL, NULL} +}; + +#define TYPE_{{ ctrl.name|snake_case|upper }} \ + ({{ ctrl.name|snake_case }}_get_type()) +static GType {{ ctrl.name|snake_case }}_get_type() +{ + static GType {{ ctrl.name|snake_case }}_type = 0; + + if (!{{ ctrl.name|snake_case }}_type) + {{ ctrl.name|snake_case }}_type = + g_enum_register_static("{{ ctrl.name }}", + {{ ctrl.name|snake_case }}_types); + + return {{ ctrl.name|snake_case }}_type; +} +{% endfor %} +{%- endfor %} + +void GstCameraControls::installProperties(GObjectClass *klass, int lastPropId) +{ +{%- for vendor, ctrls in controls %} +{%- for ctrl in ctrls %} + +{%- set spec %} +{%- if ctrl.is_rectangle -%} +gst_param_spec_array( +{%- else -%} +g_param_spec_{{ ctrl.gtype }}( +{%- endif -%} +{%- if ctrl.is_array %} + "{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case }}-value", + "{{ ctrl.name }} Value", + "One {{ ctrl.name }} element value", +{%- else %} + "{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case }}", + "{{ ctrl.name }}", + {{ ctrl.description|format_description|indent_str('\t') }}, +{%- endif %} +{%- if ctrl.is_enum %} + TYPE_{{ ctrl.name|snake_case|upper }}, + {{ ctrl.default }}, +{%- elif ctrl.is_rectangle %} + g_param_spec_int( + "rectangle-value", + "Rectangle Value", + "One rectangle value, either x, y, width or height.", + {{ ctrl.min }}, {{ ctrl.max }}, {{ ctrl.default }}, + (GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS) + ), +{%- elif ctrl.gtype == 'boolean' %} + {{ ctrl.default }}, +{%- elif ctrl.gtype in ['float', 'int', 'int64', 'uchar'] %} + {{ ctrl.min }}, {{ ctrl.max }}, {{ ctrl.default }}, +{%- endif %} + (GParamFlags) (GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS) +) +{%- endset %} + + g_object_class_install_property( + klass, + lastPropId + controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}, +{%- if ctrl.is_array %} + gst_param_spec_array( + "{{ ctrl.vendor_prefix }}{{ ctrl.name|kebab_case }}", + "{{ ctrl.name }}", + {{ ctrl.description|format_description|indent_str('\t\t\t') }}, + {{ spec|indent_str('\t\t\t') }}, + (GParamFlags) (GST_PARAM_CONTROLLABLE | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS) + ) +{%- else %} + {{ spec|indent_str('\t\t') }} +{%- endif %} + ); +{%- endfor %} +{%- endfor %} +} + +bool GstCameraControls::getProperty(guint propId, GValue *value, + [[maybe_unused]] GParamSpec *pspec) +{ + if (!controls_acc_.contains(propId)) { + GST_WARNING("Control '%s' is not available, default value will " + "be returned", + controls::controls.at(propId)->name().c_str()); + return true; + } + const ControlValue &cv = controls_acc_.get(propId); + + switch (propId) { +{%- for vendor, ctrls in controls %} +{%- for ctrl in ctrls %} + + case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: { + auto control = cv.get<{{ ctrl.type }}>(); + +{%- if ctrl.is_array %} + for (size_t i = 0; i < control.size(); ++i) { + GValue element = G_VALUE_INIT; +{%- if ctrl.is_rectangle %} + g_value_init(&element, GST_TYPE_PARAM_ARRAY_LIST); + value_set_rectangle(&element, control[i]); +{%- else %} + g_value_init(&element, G_TYPE_{{ ctrl.gtype|upper }}); + g_value_set_{{ ctrl.gtype }}(&element, control[i]); +{%- endif %} + gst_value_array_append_and_take_value(value, &element); + } +{%- else %} +{%- if ctrl.is_rectangle %} + value_set_rectangle(value, control); +{%- else %} + g_value_set_{{ ctrl.gtype }}(value, control); +{%- endif %} +{%- endif %} + + return true; + } +{%- endfor %} +{%- endfor %} + + default: + return false; + } +} + +bool GstCameraControls::setProperty(guint propId, const GValue *value, + [[maybe_unused]] GParamSpec *pspec) +{ + /* + * Check whether the camera capabilities are already available. + * They might not be available if the pipeline has not started yet. + */ + if (!capabilities_.empty()) { + /* If so, check that the control is supported by the camera. */ + const ControlId *cid = capabilities_.idmap().at(propId); + auto info = capabilities_.find(cid); + + if (info == capabilities_.end()) { + GST_WARNING("Control '%s' is not supported by the " + "camera and will be ignored", + cid->name().c_str()); + return true; + } + } + + switch (propId) { +{%- for vendor, ctrls in controls %} +{%- for ctrl in ctrls %} + + case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: { +{%- if ctrl.is_array %} + size_t size = gst_value_array_get_size(value); +{%- if ctrl.size != 0 %} + if (size != {{ ctrl.size }}) { + GST_ERROR("Incorrect array size for control " + "'{{ ctrl.name|kebab_case }}', must be of " + "size {{ ctrl.size }}"); + return true; + } +{%- endif %} + + std::vector<{{ ctrl.element_type }}> values(size); + for (size_t i = 0; i < size; ++i) { + const GValue *element = + gst_value_array_get_value(value, i); +{%- if ctrl.is_rectangle %} + if (gst_value_array_get_size(element) != 4) { + GST_ERROR("Rectangle in control " + "'{{ ctrl.name|kebab_case }}' at" + "index %zu must be an array of size 4", + i); + return true; + } + values[i] = value_get_rectangle(element); +{%- else %} + values[i] = g_value_get_{{ ctrl.gtype }}(element); +{%- endif %} + } + +{%- if ctrl.size == 0 %} + Span<const {{ ctrl.element_type }}> val(values.data(), size); +{%- else %} + Span<const {{ ctrl.element_type }}, {{ ctrl.size }}> val(values.data(), size); +{%- endif %} +{%- else %} +{%- if ctrl.is_rectangle %} + if (gst_value_array_get_size(value) != 4) { + GST_ERROR("Rectangle in control " + "'{{ ctrl.name|kebab_case }}' must be an " + "array of size 4"); + return true; + } + Rectangle val = value_get_rectangle(value); +{%- else %} + auto val = g_value_get_{{ ctrl.gtype }}(value); +{%- endif %} +{%- endif %} + controls_.set(controls::{{ ctrl.namespace }}{{ ctrl.name }}, val); + controls_acc_.set(controls::{{ ctrl.namespace }}{{ ctrl.name }}, val); + return true; + } +{%- endfor %} +{%- endfor %} + + default: + return false; + } +} + +void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam) +{ + capabilities_ = cam->controls(); + + /* + * Check the controls which were set before the camera capabilities were + * known. This is required because GStreamer may set properties before + * the pipeline has started and thus before the camera was known. + */ + ControlList new_controls; + for (auto control = controls_acc_.begin(); + control != controls_acc_.end(); + ++control) { + unsigned int id = control->first; + ControlValue value = control->second; + + const ControlId *cid = capabilities_.idmap().at(id); + auto info = capabilities_.find(cid); + + /* Only add controls which are supported. */ + if (info != capabilities_.end()) + new_controls.set(id, value); + else + GST_WARNING("Control '%s' is not supported by the " + "camera and will be ignored", + cid->name().c_str()); + } + + controls_acc_ = new_controls; + controls_ = new_controls; +} + +void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request) +{ + request->controls().merge(controls_); + controls_.clear(); +} + +void GstCameraControls::readMetadata(libcamera::Request *request) +{ + controls_acc_.merge(request->metadata(), + ControlList::MergePolicy::OverwriteExisting); +} diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h new file mode 100644 index 00000000..749220b5 --- /dev/null +++ b/src/gstreamer/gstlibcamera-controls.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Collabora Ltd. + * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> + * + * GStreamer Camera Controls + */ + +#pragma once + +#include <memory> + +#include <libcamera/camera.h> +#include <libcamera/controls.h> +#include <libcamera/request.h> + +#include "gstlibcamerasrc.h" + +namespace libcamera { + +class GstCameraControls +{ +public: + static void installProperties(GObjectClass *klass, int lastProp); + + bool getProperty(guint propId, GValue *value, GParamSpec *pspec); + bool setProperty(guint propId, const GValue *value, GParamSpec *pspec); + + void setCamera(const std::shared_ptr<libcamera::Camera> &cam); + + void applyControls(std::unique_ptr<libcamera::Request> &request); + void readMetadata(libcamera::Request *request); + +private: + /* Supported controls and limits of camera. */ + ControlInfoMap capabilities_; + /* Set of user modified controls. */ + ControlList controls_; + /* Accumulator of all controls ever set and metadata returned by camera */ + ControlList controls_acc_; +}; + +} /* namespace libcamera */ diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp index 750ec351..a548b0c1 100644 --- a/src/gstreamer/gstlibcamera-utils.cpp +++ b/src/gstreamer/gstlibcamera-utils.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamera-utils.c - GStreamer libcamera Utility Function + * GStreamer libcamera Utility Function */ #include "gstlibcamera-utils.h" @@ -25,6 +25,26 @@ static struct { { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG8 }, { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG8 }, { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB8 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR10 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG10 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG10 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB10 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR12 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG12 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG12 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB12 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR14 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG14 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG14 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB14 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SBGGR16 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGBRG16 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SGRBG16 }, + { GST_VIDEO_FORMAT_ENCODED, formats::SRGGB16 }, + + /* Monochrome */ + { GST_VIDEO_FORMAT_GRAY8, formats::R8 }, + { GST_VIDEO_FORMAT_GRAY16_LE, formats::R16 }, /* RGB16 */ { GST_VIDEO_FORMAT_RGB16, formats::RGB565 }, @@ -54,6 +74,7 @@ static struct { { GST_VIDEO_FORMAT_I420, formats::YUV420 }, { GST_VIDEO_FORMAT_YV12, formats::YVU420 }, { GST_VIDEO_FORMAT_Y42B, formats::YUV422 }, + { GST_VIDEO_FORMAT_Y444, formats::YUV444 }, /* YUV Packed */ { GST_VIDEO_FORMAT_UYVY, formats::UYVY }, @@ -65,7 +86,7 @@ static struct { }; static GstVideoColorimetry -colorimetry_from_colorspace(const ColorSpace &colorSpace) +colorimetry_from_colorspace(const ColorSpace &colorSpace, GstVideoTransferFunction transfer) { GstVideoColorimetry colorimetry; @@ -93,6 +114,8 @@ colorimetry_from_colorspace(const ColorSpace &colorSpace) break; case ColorSpace::TransferFunction::Rec709: colorimetry.transfer = GST_VIDEO_TRANSFER_BT709; + if (transfer != GST_VIDEO_TRANSFER_UNKNOWN) + colorimetry.transfer = transfer; break; } @@ -124,7 +147,8 @@ colorimetry_from_colorspace(const ColorSpace &colorSpace) } static std::optional<ColorSpace> -colorspace_from_colorimetry(const GstVideoColorimetry &colorimetry) +colorspace_from_colorimetry(const GstVideoColorimetry &colorimetry, + GstVideoTransferFunction *transfer) { std::optional<ColorSpace> colorspace = ColorSpace::Raw; @@ -168,6 +192,7 @@ colorspace_from_colorimetry(const GstVideoColorimetry &colorimetry) case GST_VIDEO_TRANSFER_BT2020_12: case GST_VIDEO_TRANSFER_BT709: colorspace->transferFunction = ColorSpace::TransferFunction::Rec709; + *transfer = colorimetry.transfer; break; default: GST_WARNING("Colorimetry transfer function %d not mapped in gstlibcamera", @@ -234,20 +259,50 @@ gst_format_to_pixel_format(GstVideoFormat gst_format) return PixelFormat{}; } +static const struct { + PixelFormat format; + const gchar *name; +} bayer_map[]{ + { formats::SBGGR8, "bggr" }, + { formats::SGBRG8, "gbrg" }, + { formats::SGRBG8, "grbg" }, + { formats::SRGGB8, "rggb" }, + { formats::SBGGR10, "bggr10le" }, + { formats::SGBRG10, "gbrg10le" }, + { formats::SGRBG10, "grbg10le" }, + { formats::SRGGB10, "rggb10le" }, + { formats::SBGGR12, "bggr12le" }, + { formats::SGBRG12, "gbrg12le" }, + { formats::SGRBG12, "grbg12le" }, + { formats::SRGGB12, "rggb12le" }, + { formats::SBGGR14, "bggr14le" }, + { formats::SGBRG14, "gbrg14le" }, + { formats::SGRBG14, "grbg14le" }, + { formats::SRGGB14, "rggb14le" }, + { formats::SBGGR16, "bggr16le" }, + { formats::SGBRG16, "gbrg16le" }, + { formats::SGRBG16, "grbg16le" }, + { formats::SRGGB16, "rggb16le" }, +}; + static const gchar * -bayer_format_to_string(int format) +bayer_format_to_string(PixelFormat format) { - switch (format) { - case formats::SBGGR8: - return "bggr"; - case formats::SGBRG8: - return "gbrg"; - case formats::SGRBG8: - return "grbg"; - case formats::SRGGB8: - return "rggb"; + for (auto &b : bayer_map) { + if (b.format == format) + return b.name; } - return NULL; + return nullptr; +} + +static PixelFormat +bayer_format_from_string(const gchar *name) +{ + for (auto &b : bayer_map) { + if (strcmp(b.name, name) == 0) + return b.format; + } + return PixelFormat{}; } static GstStructure * @@ -307,13 +362,21 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats) GValue val = G_VALUE_INIT; g_value_init(&val, GST_TYPE_INT_RANGE); - gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep); - gst_structure_set_value(s, "width", &val); - gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep); - gst_structure_set_value(s, "height", &val); + if (range.min.width == range.max.width) { + gst_structure_set(s, "width", G_TYPE_INT, range.min.width, nullptr); + } else { + gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep); + gst_structure_set_value(s, "width", &val); + } + if (range.min.height == range.max.height) { + gst_structure_set(s, "height", G_TYPE_INT, range.min.height, nullptr); + } else { + gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep); + gst_structure_set_value(s, "height", &val); + } g_value_unset(&val); - gst_caps_append_structure(caps, s); + caps = gst_caps_merge_structure(caps, s); } } @@ -321,7 +384,8 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats) } GstCaps * -gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg) +gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg, + GstVideoTransferFunction transfer) { GstCaps *caps = gst_caps_new_empty(); GstStructure *s = bare_structure_from_format(stream_cfg.pixelFormat); @@ -332,8 +396,8 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg nullptr); if (stream_cfg.colorSpace) { - GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value()); - gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry); + GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value(), transfer); + g_autofree gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry); if (colorimetry_str) gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr); @@ -347,9 +411,8 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg return caps; } -void -gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg, - GstCaps *caps) +void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg, + GstCaps *caps, GstVideoTransferFunction *transfer) { GstVideoFormat gst_format = pixel_format_to_gst_format(stream_cfg.pixelFormat); guint i; @@ -414,6 +477,9 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg, const gchar *format = gst_structure_get_string(s, "format"); gst_format = gst_video_format_from_string(format); stream_cfg.pixelFormat = gst_format_to_pixel_format(gst_format); + } else if (gst_structure_has_name(s, "video/x-bayer")) { + const gchar *format = gst_structure_get_string(s, "format"); + stream_cfg.pixelFormat = bayer_format_from_string(format); } else if (gst_structure_has_name(s, "image/jpeg")) { stream_cfg.pixelFormat = formats::MJPEG; } else { @@ -428,13 +494,16 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg, /* Configure colorimetry */ if (gst_structure_has_field(s, "colorimetry")) { - const gchar *colorimetry_str = gst_structure_get_string(s, "colorimetry"); + const gchar *colorimetry_str; GstVideoColorimetry colorimetry; + gst_structure_fixate_field(s, "colorimetry"); + colorimetry_str = gst_structure_get_string(s, "colorimetry"); + if (!gst_video_colorimetry_from_string(&colorimetry, colorimetry_str)) g_critical("Invalid colorimetry %s", colorimetry_str); - stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry); + stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry, transfer); } } @@ -530,6 +599,43 @@ gst_task_resume(GstTask *task) } #endif +#if !GST_CHECK_VERSION(1, 22, 0) +/* + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * Library <2002> Ronald Bultje <rbultje@ronald.bitfreak.net> + * Copyright (C) <2007> David A. Schleef <ds@schleef.org> + */ +/* + * This function has been imported directly from the gstreamer project to + * support backwards compatibility and should be removed when the older version + * is no longer supported. + */ +gint gst_video_format_info_extrapolate_stride(const GstVideoFormatInfo *finfo, gint plane, gint stride) +{ + gint estride; + gint comp[GST_VIDEO_MAX_COMPONENTS]; + gint i; + + /* There is nothing to extrapolate on first plane. */ + if (plane == 0) + return stride; + + gst_video_format_info_component(finfo, plane, comp); + + /* + * For now, all planar formats have a single component on first plane, but + * if there was a planar format with more, we'd have to make a ratio of the + * number of component on the first plane against the number of component on + * the current plane. + */ + estride = 0; + for (i = 0; i < GST_VIDEO_MAX_COMPONENTS && comp[i] >= 0; i++) + estride += GST_VIDEO_FORMAT_INFO_SCALE_WIDTH(finfo, comp[i], stride); + + return estride; +} +#endif + G_LOCK_DEFINE_STATIC(cm_singleton_lock); static std::weak_ptr<CameraManager> cm_singleton_ptr; diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h index fd304a8b..5f4e8a0f 100644 --- a/src/gstreamer/gstlibcamera-utils.h +++ b/src/gstreamer/gstlibcamera-utils.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamera-utils.h - GStreamer libcamera Utility Functions + * GStreamer libcamera Utility Functions */ #pragma once @@ -16,18 +16,31 @@ #include <gst/video/video.h> GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &formats); -GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg); +GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg, + GstVideoTransferFunction transfer); void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg, - GstCaps *caps); + GstCaps *caps, GstVideoTransferFunction *transfer); void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps); void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls, const libcamera::ControlInfoMap &camera_controls, GstStructure *element_caps); void gst_libcamera_framerate_to_caps(GstCaps *caps, const GstStructure *element_caps); +#if !GST_CHECK_VERSION(1, 16, 0) +static inline void gst_clear_event(GstEvent **event_ptr) +{ + g_clear_pointer(event_ptr, gst_mini_object_unref); +} +#endif + #if !GST_CHECK_VERSION(1, 17, 1) gboolean gst_task_resume(GstTask *task); #endif + +#if !GST_CHECK_VERSION(1, 22, 0) +gint gst_video_format_info_extrapolate_stride(const GstVideoFormatInfo *finfo, gint plane, gint stride); +#endif + std::shared_ptr<libcamera::CameraManager> gst_libcamera_get_camera_manager(int &ret); /** diff --git a/src/gstreamer/gstlibcamera.cpp b/src/gstreamer/gstlibcamera.cpp index 52388b5e..bff98979 100644 --- a/src/gstreamer/gstlibcamera.cpp +++ b/src/gstreamer/gstlibcamera.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamera.c - GStreamer plugin + * GStreamer plugin */ #include "gstlibcameraprovider.h" diff --git a/src/gstreamer/gstlibcameraallocator.cpp b/src/gstreamer/gstlibcameraallocator.cpp index c740b8fc..d4492d99 100644 --- a/src/gstreamer/gstlibcameraallocator.cpp +++ b/src/gstreamer/gstlibcameraallocator.cpp @@ -3,11 +3,13 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcameraallocator.cpp - GStreamer Custom Allocator + * GStreamer Custom Allocator */ #include "gstlibcameraallocator.h" +#include <utility> + #include <libcamera/camera.h> #include <libcamera/framebuffer_allocator.h> #include <libcamera/stream.h> @@ -100,6 +102,11 @@ struct _GstLibcameraAllocator { * FrameWrap. */ GHashTable *pools; + /* + * The camera manager represents the library, which needs to be kept + * alive until all the memory has been released. + */ + std::shared_ptr<CameraManager> *cm_ptr; }; G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator, @@ -173,6 +180,9 @@ gst_libcamera_allocator_finalize(GObject *object) delete self->fb_allocator; + /* Keep last. */ + delete self->cm_ptr; + G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object); } @@ -191,16 +201,20 @@ GstLibcameraAllocator * gst_libcamera_allocator_new(std::shared_ptr<Camera> camera, CameraConfiguration *config_) { - auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR, - nullptr)); + g_autoptr(GstLibcameraAllocator) self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR, + nullptr)); + gint ret; + + self->cm_ptr = new std::shared_ptr<CameraManager>(gst_libcamera_get_camera_manager(ret)); + if (ret) + return nullptr; self->fb_allocator = new FrameBufferAllocator(camera); for (StreamConfiguration &streamCfg : *config_) { Stream *stream = streamCfg.stream(); - gint ret; ret = self->fb_allocator->allocate(stream); - if (ret == 0) + if (ret <= 0) return nullptr; GQueue *pool = g_queue_new(); @@ -214,7 +228,7 @@ gst_libcamera_allocator_new(std::shared_ptr<Camera> camera, g_hash_table_insert(self->pools, stream, pool); } - return self; + return std::exchange(self, nullptr); } bool diff --git a/src/gstreamer/gstlibcameraallocator.h b/src/gstreamer/gstlibcameraallocator.h index 0a08c3bb..1a6ba346 100644 --- a/src/gstreamer/gstlibcameraallocator.h +++ b/src/gstreamer/gstlibcameraallocator.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcameraallocator.h - GStreamer Custom Allocator + * GStreamer Custom Allocator */ #pragma once diff --git a/src/gstreamer/gstlibcamerapad.cpp b/src/gstreamer/gstlibcamerapad.cpp index 9e710a47..3bc2bc87 100644 --- a/src/gstreamer/gstlibcamerapad.cpp +++ b/src/gstreamer/gstlibcamerapad.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerapad.cpp - GStreamer Capture Pad + * GStreamer Capture Pad */ #include "gstlibcamerapad.h" @@ -18,6 +18,8 @@ struct _GstLibcameraPad { GstPad parent; StreamRole role; GstLibcameraPool *pool; + GstBufferPool *video_pool; + GstVideoInfo info; GstClockTime latency; }; @@ -153,6 +155,35 @@ gst_libcamera_pad_set_pool(GstPad *pad, GstLibcameraPool *pool) self->pool = pool; } +GstBufferPool * +gst_libcamera_pad_get_video_pool(GstPad *pad) +{ + auto *self = GST_LIBCAMERA_PAD(pad); + return self->video_pool; +} + +void gst_libcamera_pad_set_video_pool(GstPad *pad, GstBufferPool *video_pool) +{ + auto *self = GST_LIBCAMERA_PAD(pad); + + if (self->video_pool) + g_object_unref(self->video_pool); + self->video_pool = video_pool; +} + +GstVideoInfo gst_libcamera_pad_get_video_info(GstPad *pad) +{ + auto *self = GST_LIBCAMERA_PAD(pad); + return self->info; +} + +void gst_libcamera_pad_set_video_info(GstPad *pad, const GstVideoInfo *info) +{ + auto *self = GST_LIBCAMERA_PAD(pad); + + self->info = *info; +} + Stream * gst_libcamera_pad_get_stream(GstPad *pad) { diff --git a/src/gstreamer/gstlibcamerapad.h b/src/gstreamer/gstlibcamerapad.h index 103ee57a..f98b8a7f 100644 --- a/src/gstreamer/gstlibcamerapad.h +++ b/src/gstreamer/gstlibcamerapad.h @@ -3,7 +3,7 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerapad.h - GStreamer Capture Element + * GStreamer Capture Element */ #pragma once @@ -23,6 +23,14 @@ GstLibcameraPool *gst_libcamera_pad_get_pool(GstPad *pad); void gst_libcamera_pad_set_pool(GstPad *pad, GstLibcameraPool *pool); +GstBufferPool *gst_libcamera_pad_get_video_pool(GstPad *pad); + +void gst_libcamera_pad_set_video_pool(GstPad *pad, GstBufferPool *video_pool); + +GstVideoInfo gst_libcamera_pad_get_video_info(GstPad *pad); + +void gst_libcamera_pad_set_video_info(GstPad *pad, const GstVideoInfo *info); + libcamera::Stream *gst_libcamera_pad_get_stream(GstPad *pad); void gst_libcamera_pad_set_latency(GstPad *pad, GstClockTime latency); diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp index 0c2be43c..8278144f 100644 --- a/src/gstreamer/gstlibcamerapool.cpp +++ b/src/gstreamer/gstlibcamerapool.cpp @@ -3,11 +3,13 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerapool.cpp - GStreamer Buffer Pool + * GStreamer Buffer Pool */ #include "gstlibcamerapool.h" +#include <deque> + #include <libcamera/stream.h> #include "gstlibcamera-utils.h" @@ -24,24 +26,41 @@ static guint signals[N_SIGNALS]; struct _GstLibcameraPool { GstBufferPool parent; - GstAtomicQueue *queue; + std::deque<GstBuffer *> *queue; GstLibcameraAllocator *allocator; Stream *stream; }; G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL) +static GstBuffer * +gst_libcamera_pool_pop_buffer(GstLibcameraPool *self) +{ + GLibLocker lock(GST_OBJECT(self)); + GstBuffer *buf; + + if (self->queue->empty()) + return nullptr; + + buf = self->queue->front(); + self->queue->pop_front(); + + return buf; +} + static GstFlowReturn gst_libcamera_pool_acquire_buffer(GstBufferPool *pool, GstBuffer **buffer, [[maybe_unused]] GstBufferPoolAcquireParams *params) { GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool); - GstBuffer *buf = GST_BUFFER(gst_atomic_queue_pop(self->queue)); + GstBuffer *buf = gst_libcamera_pool_pop_buffer(self); + if (!buf) return GST_FLOW_ERROR; if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf)) { - gst_atomic_queue_push(self->queue, buf); + GLibLocker lock(GST_OBJECT(self)); + self->queue->push_back(buf); return GST_FLOW_ERROR; } @@ -64,9 +83,13 @@ static void gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer) { GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool); - bool do_notify = gst_atomic_queue_length(self->queue) == 0; + bool do_notify; - gst_atomic_queue_push(self->queue, buffer); + { + GLibLocker lock(GST_OBJECT(self)); + do_notify = self->queue->empty(); + self->queue->push_back(buffer); + } if (do_notify) g_signal_emit(self, signals[SIGNAL_BUFFER_NOTIFY], 0); @@ -75,7 +98,7 @@ gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer) static void gst_libcamera_pool_init(GstLibcameraPool *self) { - self->queue = gst_atomic_queue_new(4); + self->queue = new std::deque<GstBuffer *>(); } static void @@ -84,10 +107,10 @@ gst_libcamera_pool_finalize(GObject *object) GstLibcameraPool *self = GST_LIBCAMERA_POOL(object); GstBuffer *buf; - while ((buf = GST_BUFFER(gst_atomic_queue_pop(self->queue)))) + while ((buf = gst_libcamera_pool_pop_buffer(self))) gst_buffer_unref(buf); - gst_atomic_queue_unref(self->queue); + delete self->queue; g_object_unref(self->allocator); G_OBJECT_CLASS(gst_libcamera_pool_parent_class)->finalize(object); @@ -111,8 +134,20 @@ gst_libcamera_pool_class_init(GstLibcameraPoolClass *klass) G_TYPE_NONE, 0); } +static void +gst_libcamera_buffer_add_video_meta(GstBuffer *buffer, GstVideoInfo *info) +{ + GstVideoMeta *vmeta; + vmeta = gst_buffer_add_video_meta_full(buffer, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT(info), GST_VIDEO_INFO_WIDTH(info), + GST_VIDEO_INFO_HEIGHT(info), GST_VIDEO_INFO_N_PLANES(info), + info->offset, info->stride); + GST_META_FLAGS(vmeta) = (GstMetaFlags)(GST_META_FLAGS(vmeta) | GST_META_FLAG_POOLED); +} + GstLibcameraPool * -gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream) +gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream, + GstVideoInfo *info) { auto *pool = GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr)); @@ -122,7 +157,8 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream) gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream); for (gsize i = 0; i < pool_size; i++) { GstBuffer *buffer = gst_buffer_new(); - gst_atomic_queue_push(pool->queue, buffer); + gst_libcamera_buffer_add_video_meta(buffer, info); + pool->queue->push_back(buffer); } return pool; diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h index ce3bf60b..02ee4dd4 100644 --- a/src/gstreamer/gstlibcamerapool.h +++ b/src/gstreamer/gstlibcamerapool.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerapool.h - GStreamer Buffer Pool + * GStreamer Buffer Pool * * This is a partial implementation of GstBufferPool intended for internal use * only. This pool cannot be configured or activated. @@ -14,6 +14,7 @@ #include "gstlibcameraallocator.h" #include <gst/gst.h> +#include <gst/video/video.h> #include <libcamera/stream.h> @@ -21,7 +22,7 @@ G_DECLARE_FINAL_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_LIBCAMERA, POOL, GstBufferPool) GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator, - libcamera::Stream *stream); + libcamera::Stream *stream, GstVideoInfo *info); libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool *self); diff --git a/src/gstreamer/gstlibcameraprovider.cpp b/src/gstreamer/gstlibcameraprovider.cpp index ce3e0a08..5da96ea3 100644 --- a/src/gstreamer/gstlibcameraprovider.cpp +++ b/src/gstreamer/gstlibcameraprovider.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcameraprovider.c - GStreamer Device Provider + * GStreamer Device Provider */ #include <array> @@ -33,7 +33,6 @@ GST_DEBUG_CATEGORY_STATIC(provider_debug); enum { PROP_DEVICE_NAME = 1, - PROP_AUTO_FOCUS_MODE = 2, }; #define GST_TYPE_LIBCAMERA_DEVICE gst_libcamera_device_get_type() @@ -43,7 +42,6 @@ G_DECLARE_FINAL_TYPE(GstLibcameraDevice, gst_libcamera_device, struct _GstLibcameraDevice { GstDevice parent; gchar *name; - controls::AfModeEnum auto_focus_mode = controls::AfModeManual; }; G_DEFINE_TYPE(GstLibcameraDevice, gst_libcamera_device, GST_TYPE_DEVICE) @@ -60,7 +58,6 @@ gst_libcamera_device_create_element(GstDevice *device, const gchar *name) g_assert(source); g_object_set(source, "camera-name", GST_LIBCAMERA_DEVICE(device)->name, nullptr); - g_object_set(source, "auto-focus-mode", GST_LIBCAMERA_DEVICE(device)->auto_focus_mode, nullptr); return source; } @@ -87,9 +84,6 @@ gst_libcamera_device_set_property(GObject *object, guint prop_id, case PROP_DEVICE_NAME: device->name = g_value_dup_string(value); break; - case PROP_AUTO_FOCUS_MODE: - device->auto_focus_mode = static_cast<controls::AfModeEnum>(g_value_get_enum(value)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -129,15 +123,6 @@ gst_libcamera_device_class_init(GstLibcameraDeviceClass *klass) (GParamFlags)(G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property(object_class, PROP_DEVICE_NAME, pspec); - - pspec = g_param_spec_enum("auto-focus-mode", - "Set auto-focus mode", - "Available options: AfModeManual, " - "AfModeAuto or AfModeContinuous.", - gst_libcamera_auto_focus_get_type(), - static_cast<gint>(controls::AfModeManual), - G_PARAM_WRITABLE); - g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, pspec); } static GstDevice * diff --git a/src/gstreamer/gstlibcameraprovider.h b/src/gstreamer/gstlibcameraprovider.h index aaceabf5..19708b9d 100644 --- a/src/gstreamer/gstlibcameraprovider.h +++ b/src/gstreamer/gstlibcameraprovider.h @@ -3,7 +3,7 @@ * Copyright (C) 2020, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcameraprovider.h - GStreamer Device Provider + * GStreamer Device Provider */ #pragma once diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index 63d99571..b34f0897 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -3,16 +3,14 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerasrc.cpp - GStreamer Capture Element + * GStreamer Capture Element */ /** * \todo The following is a list of items that needs implementation in the GStreamer plugin * - Implement GstElement::send_event - * + Allowing application to send EOS * + Allowing application to use FLUSH/FLUSH_STOP * + Prevent the main thread from accessing streaming thread - * - Implement renegotiation (even if slow) * - Implement GstElement::request-new-pad (multi stream) * + Evaluate if a single streaming thread is fine * - Add application driven request (snapshot) @@ -29,6 +27,7 @@ #include "gstlibcamerasrc.h" +#include <atomic> #include <queue> #include <vector> @@ -38,10 +37,11 @@ #include <gst/base/base.h> +#include "gstlibcamera-controls.h" +#include "gstlibcamera-utils.h" #include "gstlibcameraallocator.h" #include "gstlibcamerapad.h" #include "gstlibcamerapool.h" -#include "gstlibcamera-utils.h" using namespace libcamera; @@ -129,10 +129,12 @@ struct GstLibcameraSrcState { ControlList initControls_; guint group_id_; + GstCameraControls controls_; int queueRequest(); void requestCompleted(Request *request); int processRequest(); + void clearRequests(); }; struct _GstLibcameraSrc { @@ -142,7 +144,8 @@ struct _GstLibcameraSrc { GstTask *task; gchar *camera_name; - controls::AfModeEnum auto_focus_mode = controls::AfModeManual; + + std::atomic<GstEvent *> pending_eos; GstLibcameraSrcState *state; GstLibcameraAllocator *allocator; @@ -152,10 +155,15 @@ struct _GstLibcameraSrc { enum { PROP_0, PROP_CAMERA_NAME, - PROP_AUTO_FOCUS_MODE, + PROP_LAST }; +static void gst_libcamera_src_child_proxy_init(gpointer g_iface, + gpointer iface_data); + G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT, + G_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY, + gst_libcamera_src_child_proxy_init) GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0, "libcamera Source")) @@ -178,6 +186,9 @@ int GstLibcameraSrcState::queueRequest() if (!request) return -ENOMEM; + /* Apply controls */ + controls_.applyControls(request); + std::unique_ptr<RequestWrap> wrap = std::make_unique<RequestWrap>(std::move(request)); @@ -221,6 +232,9 @@ GstLibcameraSrcState::requestCompleted(Request *request) { GLibLocker locker(&lock_); + + controls_.readMetadata(request); + wrap = std::move(queuedRequests_.front()); queuedRequests_.pop(); } @@ -254,6 +268,55 @@ GstLibcameraSrcState::requestCompleted(Request *request) gst_task_resume(src_->task); } +static void +gst_libcamera_extrapolate_info(GstVideoInfo *info, guint32 stride) +{ + guint i, estride; + gsize offset = 0; + + /* This should be updated if tiled formats get added in the future. */ + for (i = 0; i < GST_VIDEO_INFO_N_PLANES(info); i++) { + estride = gst_video_format_info_extrapolate_stride(info->finfo, i, stride); + info->stride[i] = estride; + info->offset[i] = offset; + offset += estride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info->finfo, i, + GST_VIDEO_INFO_HEIGHT(info)); + } +} + +static GstFlowReturn +gst_libcamera_video_frame_copy(GstBuffer *src, GstBuffer *dest, const GstVideoInfo *dest_info, guint32 stride) +{ + GstVideoInfo src_info = *dest_info; + GstVideoFrame src_frame, dest_frame; + + gst_libcamera_extrapolate_info(&src_info, stride); + src_info.size = gst_buffer_get_size(src); + + if (!gst_video_frame_map(&src_frame, &src_info, src, GST_MAP_READ)) { + GST_ERROR("Could not map src buffer"); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map(&dest_frame, const_cast<GstVideoInfo *>(dest_info), dest, GST_MAP_WRITE)) { + GST_ERROR("Could not map dest buffer"); + gst_video_frame_unmap(&src_frame); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_copy(&dest_frame, &src_frame)) { + GST_ERROR("Could not copy frame"); + gst_video_frame_unmap(&src_frame); + gst_video_frame_unmap(&dest_frame); + return GST_FLOW_ERROR; + } + + gst_video_frame_unmap(&src_frame); + gst_video_frame_unmap(&dest_frame); + + return GST_FLOW_OK; +} + /* Must be called with stream_lock held. */ int GstLibcameraSrcState::processRequest() { @@ -278,11 +341,41 @@ int GstLibcameraSrcState::processRequest() GstFlowReturn ret = GST_FLOW_OK; gst_flow_combiner_reset(src_->flow_combiner); - for (GstPad *srcpad : srcpads_) { + for (gsize i = 0; i < srcpads_.size(); i++) { + GstPad *srcpad = srcpads_[i]; Stream *stream = gst_libcamera_pad_get_stream(srcpad); GstBuffer *buffer = wrap->detachBuffer(stream); FrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer); + const StreamConfiguration &stream_cfg = config_->at(i); + GstBufferPool *video_pool = gst_libcamera_pad_get_video_pool(srcpad); + + if (video_pool) { + /* Only set video pool when a copy is needed. */ + GstBuffer *copy = NULL; + const GstVideoInfo info = gst_libcamera_pad_get_video_info(srcpad); + + ret = gst_buffer_pool_acquire_buffer(video_pool, ©, NULL); + if (ret != GST_FLOW_OK) { + gst_buffer_unref(buffer); + GST_ELEMENT_ERROR(src_, RESOURCE, SETTINGS, + ("Failed to acquire buffer"), + ("GstLibcameraSrcState::processRequest() failed: %s", g_strerror(-ret))); + return -EPIPE; + } + + ret = gst_libcamera_video_frame_copy(buffer, copy, &info, stream_cfg.stride); + gst_buffer_unref(buffer); + if (ret != GST_FLOW_OK) { + gst_buffer_unref(copy); + GST_ELEMENT_ERROR(src_, RESOURCE, SETTINGS, + ("Failed to copy buffer"), + ("GstLibcameraSrcState::processRequest() failed: %s", g_strerror(-ret))); + return -EPIPE; + } + + buffer = copy; + } if (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) { GST_BUFFER_PTS(buffer) = wrap->pts_; @@ -299,23 +392,57 @@ int GstLibcameraSrcState::processRequest() srcpad, ret); } - if (ret != GST_FLOW_OK) { - if (ret == GST_FLOW_EOS) { - g_autoptr(GstEvent) eos = gst_event_new_eos(); - guint32 seqnum = gst_util_seqnum_next(); - gst_event_set_seqnum(eos, seqnum); - for (GstPad *srcpad : srcpads_) - gst_pad_push_event(srcpad, gst_event_ref(eos)); - } else if (ret != GST_FLOW_FLUSHING) { - GST_ELEMENT_FLOW_ERROR(src_, ret); + switch (ret) { + case GST_FLOW_OK: + break; + + case GST_FLOW_NOT_NEGOTIATED: { + bool reconfigure = false; + for (GstPad *srcpad : srcpads_) { + if (gst_pad_needs_reconfigure(srcpad)) { + reconfigure = true; + break; + } } - return -EPIPE; + /* If no pads need a reconfiguration something went wrong. */ + if (!reconfigure) + err = -EPIPE; + + break; + } + + case GST_FLOW_EOS: { + g_autoptr(GstEvent) eos = gst_event_new_eos(); + guint32 seqnum = gst_util_seqnum_next(); + gst_event_set_seqnum(eos, seqnum); + for (GstPad *srcpad : srcpads_) + gst_pad_push_event(srcpad, gst_event_ref(eos)); + + err = -EPIPE; + break; + } + + case GST_FLOW_FLUSHING: + err = -EPIPE; + break; + + default: + GST_ELEMENT_FLOW_ERROR(src_, ret); + + err = -EPIPE; + break; } return err; } +void GstLibcameraSrcState::clearRequests() +{ + GLibLocker locker(&lock_); + completedRequests_ = {}; +} + static bool gst_libcamera_src_open(GstLibcameraSrc *self) { @@ -341,21 +468,22 @@ gst_libcamera_src_open(GstLibcameraSrc *self) } if (camera_name) { - cam = cm->get(self->camera_name); + cam = cm->get(camera_name); if (!cam) { GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND, - ("Could not find a camera named '%s'.", self->camera_name), + ("Could not find a camera named '%s'.", camera_name), ("libcamera::CameraMananger::get() returned nullptr")); return false; } } else { - if (cm->cameras().empty()) { + auto cameras = cm->cameras(); + if (cameras.empty()) { GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND, ("Could not find any supported camera on this system."), ("libcamera::CameraMananger::cameras() is empty")); return false; } - cam = cm->cameras()[0]; + cam = cameras[0]; } GST_INFO_OBJECT(self, "Using camera '%s'", cam->id().c_str()); @@ -368,6 +496,8 @@ gst_libcamera_src_open(GstLibcameraSrc *self) return false; } + self->state->controls_.setCamera(cam); + cam->requestCompleted.connect(self->state, &GstLibcameraSrcState::requestCompleted); /* No need to lock here, we didn't start our threads yet. */ @@ -377,6 +507,149 @@ gst_libcamera_src_open(GstLibcameraSrc *self) return true; } +/* Must be called with stream_lock held. */ +static bool +gst_libcamera_src_negotiate(GstLibcameraSrc *self) +{ + GstLibcameraSrcState *state = self->state; + std::vector<GstVideoTransferFunction> transfer(state->srcpads_.size(), + GST_VIDEO_TRANSFER_UNKNOWN); + + g_autoptr(GstStructure) element_caps = gst_structure_new_empty("caps"); + + for (gsize i = 0; i < state->srcpads_.size(); i++) { + GstPad *srcpad = state->srcpads_[i]; + StreamConfiguration &stream_cfg = state->config_->at(i); + + /* Retrieve the supported caps. */ + g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats()); + g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter); + if (gst_caps_is_empty(caps)) + return false; + + /* Fixate caps and configure the stream. */ + caps = gst_caps_make_writable(caps); + gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]); + gst_libcamera_get_framerate_from_caps(caps, element_caps); + } + + /* Validate the configuration. */ + if (state->config_->validate() == CameraConfiguration::Invalid) + return false; + + int ret = state->cam_->configure(state->config_.get()); + if (ret) { + GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, + ("Failed to configure camera: %s", g_strerror(-ret)), + ("Camera::configure() failed with error code %i", ret)); + return false; + } + + /* Check frame duration bounds within controls::FrameDurationLimits */ + gst_libcamera_clamp_and_set_frameduration(state->initControls_, + state->cam_->controls(), element_caps); + + /* + * Regardless if it has been modified, create clean caps and push the + * caps event. Downstream will decide if the caps are acceptable. + */ + for (gsize i = 0; i < state->srcpads_.size(); i++) { + GstPad *srcpad = state->srcpads_[i]; + const StreamConfiguration &stream_cfg = state->config_->at(i); + + g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg, transfer[i]); + gst_libcamera_framerate_to_caps(caps, element_caps); + + if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) + return false; + } + + if (self->allocator) + g_clear_object(&self->allocator); + + self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get()); + if (!self->allocator) { + GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT, + ("Failed to allocate memory"), + ("gst_libcamera_allocator_new() failed.")); + return false; + } + + for (gsize i = 0; i < state->srcpads_.size(); i++) { + GstPad *srcpad = state->srcpads_[i]; + const StreamConfiguration &stream_cfg = state->config_->at(i); + GstBufferPool *video_pool = NULL; + GstVideoInfo info; + + g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg, transfer[i]); + + gst_video_info_from_caps(&info, caps); + gst_libcamera_pad_set_video_info(srcpad, &info); + + /* Stride mismatch between camera stride and that calculated by video-info. */ + if (static_cast<unsigned int>(info.stride[0]) != stream_cfg.stride && + GST_VIDEO_INFO_FORMAT(&info) != GST_VIDEO_FORMAT_ENCODED) { + GstQuery *query = NULL; + const gboolean need_pool = true; + gboolean has_video_meta = false; + + gst_libcamera_extrapolate_info(&info, stream_cfg.stride); + + query = gst_query_new_allocation(caps, need_pool); + if (!gst_pad_peer_query(srcpad, query)) + GST_DEBUG_OBJECT(self, "Didn't get downstream ALLOCATION hints"); + else + has_video_meta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL); + + if (!has_video_meta) { + GstBufferPool *pool = NULL; + + if (gst_query_get_n_allocation_pools(query) > 0) + gst_query_parse_nth_allocation_pool(query, 0, &pool, NULL, NULL, NULL); + + if (pool) + video_pool = pool; + else { + GstStructure *config; + guint min_buffers = 3; + video_pool = gst_video_buffer_pool_new(); + + config = gst_buffer_pool_get_config(video_pool); + gst_buffer_pool_config_set_params(config, caps, info.size, min_buffers, 0); + + GST_DEBUG_OBJECT(self, "Own pool config is %" GST_PTR_FORMAT, config); + + gst_buffer_pool_set_config(GST_BUFFER_POOL_CAST(video_pool), config); + } + + GST_WARNING_OBJECT(self, "Downstream doesn't support video meta, need to copy frame."); + + if (!gst_buffer_pool_set_active(video_pool, true)) { + gst_caps_unref(caps); + GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, + ("Failed to active buffer pool"), + ("gst_libcamera_src_negotiate() failed.")); + return false; + } + } + gst_query_unref(query); + } + + GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, + stream_cfg.stream(), &info); + g_signal_connect_swapped(pool, "buffer-notify", + G_CALLBACK(gst_task_resume), self->task); + + gst_libcamera_pad_set_pool(srcpad, pool); + gst_libcamera_pad_set_video_pool(srcpad, video_pool); + + /* Clear all reconfigure flags. */ + gst_pad_check_reconfigure(srcpad); + } + + return true; +} + static void gst_libcamera_src_task_run(gpointer user_data) { @@ -397,6 +670,39 @@ gst_libcamera_src_task_run(gpointer user_data) bool doResume = false; + g_autoptr(GstEvent) event = self->pending_eos.exchange(nullptr); + if (event) { + for (GstPad *srcpad : state->srcpads_) + gst_pad_push_event(srcpad, gst_event_ref(event)); + + return; + } + + /* Check if a srcpad requested a renegotiation. */ + bool reconfigure = false; + for (GstPad *srcpad : state->srcpads_) { + if (gst_pad_check_reconfigure(srcpad)) { + /* Check if the caps even need changing. */ + g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad); + if (!gst_pad_peer_query_accept_caps(srcpad, caps)) { + reconfigure = true; + break; + } + } + } + + if (reconfigure) { + state->cam_->stop(); + state->clearRequests(); + + if (!gst_libcamera_src_negotiate(self)) { + GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED); + gst_task_stop(self->task); + } + + state->cam_->start(&state->initControls_); + } + /* * Create and queue one request. If no buffers are available the * function returns -ENOBUFS, which we ignore here as that's not a @@ -458,11 +764,8 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data); GLibRecLocker lock(&self->stream_lock); GstLibcameraSrcState *state = self->state; - GstFlowReturn flow_ret = GST_FLOW_OK; gint ret; - g_autoptr(GstStructure) element_caps = gst_structure_new_empty("caps"); - GST_DEBUG_OBJECT(self, "Streaming thread has started"); gint stream_id_num = 0; @@ -490,62 +793,16 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, } g_assert(state->config_->size() == state->srcpads_.size()); - for (gsize i = 0; i < state->srcpads_.size(); i++) { - GstPad *srcpad = state->srcpads_[i]; - StreamConfiguration &stream_cfg = state->config_->at(i); - - /* Retrieve the supported caps. */ - g_autoptr(GstCaps) filter = gst_libcamera_stream_formats_to_caps(stream_cfg.formats()); - g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad, filter); - if (gst_caps_is_empty(caps)) { - flow_ret = GST_FLOW_NOT_NEGOTIATED; - break; - } - - /* Fixate caps and configure the stream. */ - caps = gst_caps_make_writable(caps); - gst_libcamera_configure_stream_from_caps(stream_cfg, caps); - gst_libcamera_get_framerate_from_caps(caps, element_caps); - } - - if (flow_ret != GST_FLOW_OK) - goto done; - - /* Validate the configuration. */ - if (state->config_->validate() == CameraConfiguration::Invalid) { - flow_ret = GST_FLOW_NOT_NEGOTIATED; - goto done; - } - - ret = state->cam_->configure(state->config_.get()); - if (ret) { - GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, - ("Failed to configure camera: %s", g_strerror(-ret)), - ("Camera::configure() failed with error code %i", ret)); + if (!gst_libcamera_src_negotiate(self)) { + state->initControls_.clear(); + GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED); gst_task_stop(task); - flow_ret = GST_FLOW_NOT_NEGOTIATED; - goto done; + return; } - /* Check frame duration bounds within controls::FrameDurationLimits */ - gst_libcamera_clamp_and_set_frameduration(state->initControls_, - state->cam_->controls(), element_caps); - - /* - * Regardless if it has been modified, create clean caps and push the - * caps event. Downstream will decide if the caps are acceptable. - */ - for (gsize i = 0; i < state->srcpads_.size(); i++) { - GstPad *srcpad = state->srcpads_[i]; - const StreamConfiguration &stream_cfg = state->config_->at(i); - - g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg); - gst_libcamera_framerate_to_caps(caps, element_caps); - - if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps))) { - flow_ret = GST_FLOW_NOT_NEGOTIATED; - break; - } + self->flow_combiner = gst_flow_combiner_new(); + for (GstPad *srcpad : state->srcpads_) { + gst_flow_combiner_add_pad(self->flow_combiner, srcpad); /* Send an open segment event with time format. */ GstSegment segment; @@ -553,40 +810,6 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gst_pad_push_event(srcpad, gst_event_new_segment(&segment)); } - self->allocator = gst_libcamera_allocator_new(state->cam_, state->config_.get()); - if (!self->allocator) { - GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT, - ("Failed to allocate memory"), - ("gst_libcamera_allocator_new() failed.")); - gst_task_stop(task); - return; - } - - self->flow_combiner = gst_flow_combiner_new(); - for (gsize i = 0; i < state->srcpads_.size(); i++) { - GstPad *srcpad = state->srcpads_[i]; - const StreamConfiguration &stream_cfg = state->config_->at(i); - GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, - stream_cfg.stream()); - g_signal_connect_swapped(pool, "buffer-notify", - G_CALLBACK(gst_task_resume), task); - - gst_libcamera_pad_set_pool(srcpad, pool); - gst_flow_combiner_add_pad(self->flow_combiner, srcpad); - } - - if (self->auto_focus_mode != controls::AfModeManual) { - const ControlInfoMap &infoMap = state->cam_->controls(); - if (infoMap.find(&controls::AfMode) != infoMap.end()) { - state->initControls_.set(controls::AfMode, self->auto_focus_mode); - } else { - GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, - ("Failed to enable auto focus"), - ("AfMode not supported by this camera, " - "please retry with 'auto-focus-mode=AfModeManual'")); - } - } - ret = state->cam_->start(&state->initControls_); if (ret) { GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS, @@ -595,17 +818,6 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gst_task_stop(task); return; } - -done: - state->initControls_.clear(); - switch (flow_ret) { - case GST_FLOW_NOT_NEGOTIATED: - GST_ELEMENT_FLOW_ERROR(self, flow_ret); - gst_task_stop(task); - break; - default: - break; - } } static void @@ -619,11 +831,7 @@ gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task, GST_DEBUG_OBJECT(self, "Streaming thread is about to stop"); state->cam_->stop(); - - { - GLibLocker locker(&state->lock_); - state->completedRequests_ = {}; - } + state->clearRequests(); { GLibRecLocker locker(&self->stream_lock); @@ -663,17 +871,16 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); + GstLibcameraSrcState *state = self->state; switch (prop_id) { case PROP_CAMERA_NAME: g_free(self->camera_name); self->camera_name = g_value_dup_string(value); break; - case PROP_AUTO_FOCUS_MODE: - self->auto_focus_mode = static_cast<controls::AfModeEnum>(g_value_get_enum(value)); - break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + if (!state->controls_.setProperty(prop_id - PROP_LAST, value, pspec)) + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -684,16 +891,15 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, { GLibLocker lock(GST_OBJECT(object)); GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object); + GstLibcameraSrcState *state = self->state; switch (prop_id) { case PROP_CAMERA_NAME: g_value_set_string(value, self->camera_name); break; - case PROP_AUTO_FOCUS_MODE: - g_value_set_enum(value, static_cast<gint>(self->auto_focus_mode)); - break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + if (!state->controls_.getProperty(prop_id - PROP_LAST, value, pspec)) + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -747,6 +953,27 @@ gst_libcamera_src_change_state(GstElement *element, GstStateChange transition) return ret; } +static gboolean +gst_libcamera_src_send_event(GstElement *element, GstEvent *event) +{ + GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); + gboolean ret = FALSE; + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_EOS: { + GstEvent *oldEvent = self->pending_eos.exchange(event); + gst_clear_event(&oldEvent); + ret = TRUE; + break; + } + default: + gst_event_unref(event); + break; + } + + return ret; +} + static void gst_libcamera_src_finalize(GObject *object) { @@ -776,8 +1003,12 @@ gst_libcamera_src_init(GstLibcameraSrc *self) g_mutex_init(&state->lock_); - state->srcpads_.push_back(gst_pad_new_from_template(templ, "src")); - gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back()); + GstPad *pad = gst_pad_new_from_template(templ, "src"); + state->srcpads_.push_back(pad); + gst_element_add_pad(GST_ELEMENT(self), pad); + gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); + + GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE); /* C-style friend. */ state->src_ = self; @@ -806,6 +1037,8 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ, return NULL; } + gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); + return reinterpret_cast<GstPad *>(g_steal_pointer(&pad)); } @@ -814,6 +1047,8 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad) { GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element); + gst_child_proxy_child_removed(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad)); + GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad); { @@ -823,6 +1058,12 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad) auto end_iterator = pads.end(); auto pad_iterator = std::find(begin_iterator, end_iterator, pad); + GstBufferPool *video_pool = gst_libcamera_pad_get_video_pool(pad); + if (video_pool) { + gst_buffer_pool_set_active(video_pool, false); + gst_object_unref(video_pool); + } + if (pad_iterator != end_iterator) { g_object_unref(*pad_iterator); pads.erase(pad_iterator); @@ -844,11 +1085,12 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) element_class->request_new_pad = gst_libcamera_src_request_new_pad; element_class->release_pad = gst_libcamera_src_release_pad; element_class->change_state = gst_libcamera_src_change_state; + element_class->send_event = gst_libcamera_src_send_event; gst_element_class_set_metadata(element_class, "libcamera Source", "Source/Video", "Linux Camera source using libcamera", - "Nicolas Dufresne <nicolas.dufresne@collabora.com"); + "Nicolas Dufresne <nicolas.dufresne@collabora.com>"); gst_element_class_add_static_pad_template_with_gtype(element_class, &src_template, GST_TYPE_LIBCAMERA_PAD); @@ -864,12 +1106,35 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); - spec = g_param_spec_enum("auto-focus-mode", - "Set auto-focus mode", - "Available options: AfModeManual, " - "AfModeAuto or AfModeContinuous.", - gst_libcamera_auto_focus_get_type(), - static_cast<gint>(controls::AfModeManual), - G_PARAM_WRITABLE); - g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, spec); + GstCameraControls::installProperties(object_class, PROP_LAST); +} + +/* GstChildProxy implementation */ +static GObject * +gst_libcamera_src_child_proxy_get_child_by_index(GstChildProxy *child_proxy, + guint index) +{ + GLibLocker lock(GST_OBJECT(child_proxy)); + GObject *obj = nullptr; + + obj = reinterpret_cast<GObject *>(g_list_nth_data(GST_ELEMENT(child_proxy)->srcpads, index)); + if (obj) + gst_object_ref(obj); + + return obj; +} + +static guint +gst_libcamera_src_child_proxy_get_children_count(GstChildProxy *child_proxy) +{ + GLibLocker lock(GST_OBJECT(child_proxy)); + return GST_ELEMENT_CAST(child_proxy)->numsrcpads; +} + +static void +gst_libcamera_src_child_proxy_init(gpointer g_iface, [[maybe_unused]] gpointer iface_data) +{ + GstChildProxyInterface *iface = reinterpret_cast<GstChildProxyInterface *>(g_iface); + iface->get_child_by_index = gst_libcamera_src_child_proxy_get_child_by_index; + iface->get_children_count = gst_libcamera_src_child_proxy_get_children_count; } diff --git a/src/gstreamer/gstlibcamerasrc.h b/src/gstreamer/gstlibcamerasrc.h index 0a88ba02..a27db9ca 100644 --- a/src/gstreamer/gstlibcamerasrc.h +++ b/src/gstreamer/gstlibcamerasrc.h @@ -3,13 +3,11 @@ * Copyright (C) 2019, Collabora Ltd. * Author: Nicolas Dufresne <nicolas.dufresne@collabora.com> * - * gstlibcamerasrc.h - GStreamer Capture Element + * GStreamer Capture Element */ #pragma once -#include <libcamera/control_ids.h> - #include <gst/gst.h> G_BEGIN_DECLS @@ -19,32 +17,3 @@ G_DECLARE_FINAL_TYPE(GstLibcameraSrc, gst_libcamera_src, GST_LIBCAMERA, SRC, GstElement) G_END_DECLS - -inline GType -gst_libcamera_auto_focus_get_type() -{ - static GType type = 0; - static const GEnumValue values[] = { - { - static_cast<gint>(libcamera::controls::AfModeManual), - "AfModeManual", - "manual-focus", - }, - { - static_cast<gint>(libcamera::controls::AfModeAuto), - "AfModeAuto", - "automatic-auto-focus", - }, - { - static_cast<gint>(libcamera::controls::AfModeContinuous), - "AfModeContinuous", - "continuous-auto-focus", - }, - { 0, NULL, NULL } - }; - - if (!type) - type = g_enum_register_static("GstLibcameraAutoFocus", values); - - return type; -} diff --git a/src/gstreamer/meson.build b/src/gstreamer/meson.build index 20784b71..fd83e073 100644 --- a/src/gstreamer/meson.build +++ b/src/gstreamer/meson.build @@ -25,6 +25,17 @@ libcamera_gst_sources = [ 'gstlibcamerasrc.cpp', ] +# Generate gstreamer control properties + +gen_gst_controls_template = files('gstlibcamera-controls.cpp.in') +libcamera_gst_sources += custom_target('gstlibcamera-controls.cpp', + input : controls_files, + output : 'gstlibcamera-controls.cpp', + command : [gen_gst_controls, '-o', '@OUTPUT@', + '-t', gen_gst_controls_template, '@INPUT@'], + depend_files : [py_mod_controls], + env : py_build_env) + libcamera_gst_cpp_args = [ '-DVERSION="@0@"'.format(libcamera_git_version), '-DPACKAGE="@0@"'.format(meson.project_name()), @@ -46,3 +57,15 @@ libcamera_gst = shared_library('gstlibcamera', install : true, install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')), ) + +# Make the plugin visible to GStreamer inside meson devenv. +fs = import('fs') +gst_plugin_path = fs.parent(libcamera_gst.full_path()) + +gst_env = environment() +gst_env.prepend('GST_PLUGIN_PATH', gst_plugin_path) + +# Avoid polluting the system registry. +gst_env.set('GST_REGISTRY', gst_plugin_path / 'registry.data') + +meson.add_devenv(gst_env) diff --git a/src/ipa/ipa-sign-install.sh b/src/ipa/ipa-sign-install.sh index bcedb8b5..71696d5a 100755 --- a/src/ipa/ipa-sign-install.sh +++ b/src/ipa/ipa-sign-install.sh @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# ipa-sign-install.sh - Regenerate IPA module signatures when installing +# Regenerate IPA module signatures when installing key=$1 shift diff --git a/src/ipa/ipa-sign.sh b/src/ipa/ipa-sign.sh index 8673dad1..69024213 100755 --- a/src/ipa/ipa-sign.sh +++ b/src/ipa/ipa-sign.sh @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# ipa-sign.sh - Generate a signature for an IPA module +# Generate a signature for an IPA module key="$1" input="$2" diff --git a/src/ipa/ipu3/algorithms/af.cpp b/src/ipa/ipu3/algorithms/af.cpp index 12927eec..cf68fb59 100644 --- a/src/ipa/ipu3/algorithms/af.cpp +++ b/src/ipa/ipu3/algorithms/af.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Red Hat * - * af.cpp - IPU3 auto focus algorithm + * IPU3 auto focus algorithm */ #include "af.h" @@ -11,7 +11,6 @@ #include <chrono> #include <cmath> #include <fcntl.h> -#include <numeric> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> @@ -23,8 +22,6 @@ #include <libcamera/ipa/core_ipa_interface.h> -#include "libipa/histogram.h" - /** * \file af.h */ diff --git a/src/ipa/ipu3/algorithms/af.h b/src/ipa/ipu3/algorithms/af.h index c6168e30..68126d46 100644 --- a/src/ipa/ipu3/algorithms/af.h +++ b/src/ipa/ipu3/algorithms/af.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Red Hat * - * af.h - IPU3 Af algorithm + * IPU3 Af algorithm */ #pragma once diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 606a237a..39d0aebb 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -2,21 +2,22 @@ /* * Copyright (C) 2021, Ideas On Board * - * ipu3_agc.cpp - AGC/AEC mean-based control algorithm + * AGC/AEC mean-based control algorithm */ #include "agc.h" #include <algorithm> #include <chrono> -#include <cmath> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> #include <libcamera/control_ids.h> + #include <libcamera/ipa/core_ipa_interface.h> +#include "libipa/colours.h" #include "libipa/histogram.h" /** @@ -33,7 +34,7 @@ namespace ipa::ipu3::algorithms { * \class Agc * \brief A mean-based auto-exposure algorithm * - * This algorithm calculates a shutter time and an analogue gain so that the + * This algorithm calculates an exposure time and an analogue gain so that the * average value of the green channel of the brightest 2% of pixels approaches * 0.5. The AWB gains are not used here, and all cells in the grid have the same * weight, like an average-metering case. In this metering mode, the camera uses @@ -51,29 +52,37 @@ LOG_DEFINE_CATEGORY(IPU3Agc) static constexpr double kMinAnalogueGain = 1.0; /* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxShutterSpeed = 60ms; +static constexpr utils::Duration kMaxExposureTime = 60ms; /* Histogram constants */ static constexpr uint32_t knumHistogramBins = 256; -/* Target value to reach for the top 2% of the histogram */ -static constexpr double kEvGainTarget = 0.5; - -/* Number of frames to wait before calculating stats on minimum exposure */ -static constexpr uint32_t kNumStartupFrames = 10; +Agc::Agc() + : minExposureTime_(0s), maxExposureTime_(0s) +{ +} -/* - * Relative luminance target. +/** + * \brief Initialise the AGC algorithm from tuning files + * \param[in] context The shared IPA context + * \param[in] tuningData The YamlObject containing Agc tuning data + * + * This function calls the base class' tuningData parsers to discover which + * control values are supported. * - * It's a number that's chosen so that, when the camera points at a grey - * target, the resulting image brightness is considered right. + * \return 0 on success or errors from the base class */ -static constexpr double kRelativeLuminanceTarget = 0.16; - -Agc::Agc() - : frameCount_(0), minShutterSpeed_(0s), - maxShutterSpeed_(0s), filteredExposure_(0s) +int Agc::init(IPAContext &context, const YamlObject &tuningData) { + int ret; + + ret = parseTuningData(tuningData); + if (ret) + return ret; + + context.ctrlMap.merge(controls()); + + return 0; } /** @@ -90,10 +99,11 @@ int Agc::configure(IPAContext &context, IPAActiveState &activeState = context.activeState; stride_ = configuration.grid.stride; + bdsGrid_ = configuration.grid.bdsGrid; - minShutterSpeed_ = configuration.agc.minShutterSpeed; - maxShutterSpeed_ = std::min(configuration.agc.maxShutterSpeed, - kMaxShutterSpeed); + minExposureTime_ = configuration.agc.minExposureTime; + maxExposureTime_ = std::min(configuration.agc.maxExposureTime, + kMaxExposureTime); minAnalogueGain_ = std::max(configuration.agc.minAnalogueGain, kMinAnalogueGain); maxAnalogueGain_ = configuration.agc.maxAnalogueGain; @@ -102,168 +112,53 @@ int Agc::configure(IPAContext &context, activeState.agc.gain = minAnalogueGain_; activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - frameCount_ = 0; + context.activeState.agc.constraintMode = constraintModes().begin()->first; + context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + + /* \todo Run this again when FrameDurationLimits is passed in */ + setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, + maxAnalogueGain_); + resetFrameCount(); + return 0; } -/** - * \brief Estimate the mean value of the top 2% of the histogram - * \param[in] stats The statistics computed by the ImgU - * \param[in] grid The grid used to store the statistics in the IPU3 - * \return The mean value of the top 2% of the histogram - */ -double Agc::measureBrightness(const ipu3_uapi_stats_3a *stats, - const ipu3_uapi_grid_config &grid) const +Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, + const ipu3_uapi_grid_config &grid) { - /* Initialise the histogram array */ uint32_t hist[knumHistogramBins] = { 0 }; + rgbTriples_.clear(); + for (unsigned int cellY = 0; cellY < grid.height; cellY++) { for (unsigned int cellX = 0; cellX < grid.width; cellX++) { uint32_t cellPosition = cellY * stride_ + cellX; const ipu3_uapi_awb_set_item *cell = reinterpret_cast<const ipu3_uapi_awb_set_item *>( - &stats->awb_raw_buffer.meta_data[cellPosition] - ); + &stats->awb_raw_buffer.meta_data[cellPosition]); + + rgbTriples_.push_back({ + cell->R_avg, + (cell->Gr_avg + cell->Gb_avg) / 2, + cell->B_avg + }); - uint8_t gr = cell->Gr_avg; - uint8_t gb = cell->Gb_avg; /* * Store the average green value to estimate the * brightness. Even the overexposed pixels are * taken into account. */ - hist[(gr + gb) / 2]++; + hist[(cell->Gr_avg + cell->Gb_avg) / 2]++; } } - /* Estimate the quantile mean of the top 2% of the histogram. */ - return Histogram(Span<uint32_t>(hist)).interQuantileMean(0.98, 1.0); -} - -/** - * \brief Apply a filter on the exposure value to limit the speed of changes - * \param[in] exposureValue The target exposure from the AGC algorithm - * - * The speed of the filter is adaptive, and will produce the target quicker - * during startup, or when the target exposure is within 20% of the most recent - * filter output. - * - * \return The filtered exposure - */ -utils::Duration Agc::filterExposure(utils::Duration exposureValue) -{ - double speed = 0.2; - - /* Adapt instantly if we are in startup phase. */ - if (frameCount_ < kNumStartupFrames) - speed = 1.0; - - /* - * If we are close to the desired result, go faster to avoid making - * multiple micro-adjustments. - * \todo Make this customisable? - */ - if (filteredExposure_ < 1.2 * exposureValue && - filteredExposure_ > 0.8 * exposureValue) - speed = sqrt(speed); - - filteredExposure_ = speed * exposureValue + - filteredExposure_ * (1.0 - speed); - - LOG(IPU3Agc, Debug) << "After filtering, exposure " << filteredExposure_; - - return filteredExposure_; -} - -/** - * \brief Estimate the new exposure and gain values - * \param[inout] frameContext The shared IPA frame Context - * \param[in] yGain The gain calculated based on the relative luminance target - * \param[in] iqMeanGain The gain calculated based on the relative luminance target - */ -void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext, - double yGain, double iqMeanGain) -{ - const IPASessionConfiguration &configuration = context.configuration; - /* Get the effective exposure and gain applied on the sensor. */ - uint32_t exposure = frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - - /* Use the highest of the two gain estimates. */ - double evGain = std::max(yGain, iqMeanGain); - - /* Consider within 1% of the target as correctly exposed */ - if (utils::abs_diff(evGain, 1.0) < 0.01) - LOG(IPU3Agc, Debug) << "We are well exposed (evGain = " - << evGain << ")"; - - /* extracted from Rpi::Agc::computeTargetExposure */ - - /* Calculate the shutter time in seconds */ - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - - /* - * Update the exposure value for the next computation using the values - * of exposure and gain really used by the sensor. - */ - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - - LOG(IPU3Agc, Debug) << "Actual total exposure " << currentShutter * analogueGain - << " Shutter speed " << currentShutter - << " Gain " << analogueGain - << " Needed ev gain " << evGain; - - /* - * Calculate the current exposure value for the scene as the latest - * exposure value applied multiplied by the new estimated gain. - */ - utils::Duration exposureValue = effectiveExposureValue * evGain; - - /* Clamp the exposure value to the min and max authorized */ - utils::Duration maxTotalExposure = maxShutterSpeed_ * maxAnalogueGain_; - exposureValue = std::min(exposureValue, maxTotalExposure); - LOG(IPU3Agc, Debug) << "Target total exposure " << exposureValue - << ", maximum is " << maxTotalExposure; - - /* - * Filter the exposure. - * \todo estimate if we need to desaturate - */ - exposureValue = filterExposure(exposureValue); - - /* - * Divide the exposure value as new exposure and gain values. - * - * Push the shutter time up to the maximum first, and only then - * increase the gain. - */ - utils::Duration shutterTime = - std::clamp<utils::Duration>(exposureValue / minAnalogueGain_, - minShutterSpeed_, maxShutterSpeed_); - double stepGain = std::clamp(exposureValue / shutterTime, - minAnalogueGain_, maxAnalogueGain_); - LOG(IPU3Agc, Debug) << "Divided up shutter and gain are " - << shutterTime << " and " - << stepGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure and gain. */ - activeState.agc.exposure = shutterTime / configuration.sensor.lineDuration; - activeState.agc.gain = stepGain; + return Histogram(Span<uint32_t>(hist)); } /** * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] frameContext The shared IPA frame context - * \param[in] grid The grid used to store the statistics in the IPU3 - * \param[in] stats The IPU3 statistics and ISP results - * \param[in] gain The gain to apply to the frame - * \return The relative luminance - * - * This function estimates the average relative luminance of the frame that - * would be output by the sensor if an additional \a gain was applied. + * \param[in] gain The gain to apply in estimating luminance * * The estimation is based on the AWB statistics for the current frame. Red, * green and blue averages for all cells are first multiplied by the gain, and @@ -278,40 +173,22 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext, * * More detailed information can be found in: * https://en.wikipedia.org/wiki/Relative_luminance + * + * \return The relative luminance of the frame */ -double Agc::estimateLuminance(IPAActiveState &activeState, - const ipu3_uapi_grid_config &grid, - const ipu3_uapi_stats_3a *stats, - double gain) +double Agc::estimateLuminance(double gain) const { - double redSum = 0, greenSum = 0, blueSum = 0; - - /* Sum the per-channel averages, saturated to 255. */ - for (unsigned int cellY = 0; cellY < grid.height; cellY++) { - for (unsigned int cellX = 0; cellX < grid.width; cellX++) { - uint32_t cellPosition = cellY * stride_ + cellX; - - const ipu3_uapi_awb_set_item *cell = - reinterpret_cast<const ipu3_uapi_awb_set_item *>( - &stats->awb_raw_buffer.meta_data[cellPosition] - ); - const uint8_t G_avg = (cell->Gr_avg + cell->Gb_avg) / 2; + RGB<double> sum{ 0.0 }; - redSum += std::min(cell->R_avg * gain, 255.0); - greenSum += std::min(G_avg * gain, 255.0); - blueSum += std::min(cell->B_avg * gain, 255.0); - } + for (unsigned int i = 0; i < rgbTriples_.size(); i++) { + sum.r() += std::min(std::get<0>(rgbTriples_[i]) * gain, 255.0); + sum.g() += std::min(std::get<1>(rgbTriples_[i]) * gain, 255.0); + sum.b() += std::min(std::get<2>(rgbTriples_[i]) * gain, 255.0); } - /* - * Apply the AWB gains to approximate colours correctly, use the Rec. - * 601 formula to calculate the relative luminance, and normalize it. - */ - double ySum = redSum * activeState.awb.gains.red * 0.299 - + greenSum * activeState.awb.gains.green * 0.587 - + blueSum * activeState.awb.gains.blue * 0.114; - - return ySum / (grid.height * grid.width) / 255; + RGB<double> gains{{ rGain_, gGain_, bGain_ }}; + double ySum = rec601LuminanceFromRGB(sum * gains); + return ySum / (bdsGrid_.height * bdsGrid_.width) / 255; } /** @@ -330,44 +207,36 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, const ipu3_uapi_stats_3a *stats, ControlList &metadata) { - /* - * Estimate the gain needed to have the proportion of pixels in a given - * desired range. iqMean is the mean value of the top 2% of the - * cumulative histogram, and we want it to be as close as possible to a - * configured target. - */ - double iqMean = measureBrightness(stats, context.configuration.grid.bdsGrid); - double iqMeanGain = kEvGainTarget * knumHistogramBins / iqMean; + Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); + rGain_ = context.activeState.awb.gains.red; + gGain_ = context.activeState.awb.gains.blue; + bGain_ = context.activeState.awb.gains.green; /* - * Estimate the gain needed to achieve a relative luminance target. To - * account for non-linearity caused by saturation, the value needs to be - * estimated in an iterative process, as multiplying by a gain will not - * increase the relative luminance by the same factor if some image - * regions are saturated. + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. */ - double yGain = 1.0; - double yTarget = kRelativeLuminanceTarget; - - for (unsigned int i = 0; i < 8; i++) { - double yValue = estimateLuminance(context.activeState, - context.configuration.grid.bdsGrid, - stats, yGain); - double extraGain = std::min(10.0, yTarget / (yValue + .001)); - - yGain *= extraGain; - LOG(IPU3Agc, Debug) << "Y value: " << yValue - << ", Y target: " << yTarget - << ", gives gain " << yGain; - if (extraGain < 1.01) - break; - } - - computeExposure(context, frameContext, yGain, iqMeanGain); - frameCount_++; - utils::Duration exposureTime = context.configuration.sensor.lineDuration * frameContext.sensor.exposure; + double analogueGain = frameContext.sensor.gain; + utils::Duration effectiveExposureValue = exposureTime * analogueGain; + + utils::Duration newExposureTime; + double aGain, dGain; + std::tie(newExposureTime, aGain, dGain) = + calculateNewEv(context.activeState.agc.constraintMode, + context.activeState.agc.exposureMode, hist, + effectiveExposureValue); + + LOG(IPU3Agc, Debug) + << "Divided up exposure time, analogue gain and digital gain are " + << newExposureTime << ", " << aGain << " and " << dGain; + + IPAActiveState &activeState = context.activeState; + /* Update the estimated exposure time and gain. */ + activeState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration; + activeState.agc.gain = aGain; + metadata.set(controls::AnalogueGain, frameContext.sensor.gain); metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); @@ -377,7 +246,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, utils::Duration frameDuration = context.configuration.sensor.lineDuration * vTotal; metadata.set(controls::FrameDuration, frameDuration.get<std::micro>()); - } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index 9d6e3ff1..890c271b 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * agc.h - IPU3 AGC/AEC mean-based control algorithm + * IPU3 AGC/AEC mean-based control algorithm */ #pragma once @@ -13,6 +13,9 @@ #include <libcamera/geometry.h> +#include "libipa/agc_mean_luminance.h" +#include "libipa/histogram.h" + #include "algorithm.h" namespace libcamera { @@ -21,12 +24,13 @@ struct IPACameraSensorInfo; namespace ipa::ipu3::algorithms { -class Agc : public Algorithm +class Agc : public Algorithm, public AgcMeanLuminance { public: Agc(); ~Agc() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -34,27 +38,22 @@ public: ControlList &metadata) override; private: - double measureBrightness(const ipu3_uapi_stats_3a *stats, - const ipu3_uapi_grid_config &grid) const; - utils::Duration filterExposure(utils::Duration currentExposure); - void computeExposure(IPAContext &context, IPAFrameContext &frameContext, - double yGain, double iqMeanGain); - double estimateLuminance(IPAActiveState &activeState, - const ipu3_uapi_grid_config &grid, - const ipu3_uapi_stats_3a *stats, - double gain); - - uint64_t frameCount_; + double estimateLuminance(double gain) const override; + Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, + const ipu3_uapi_grid_config &grid); - utils::Duration minShutterSpeed_; - utils::Duration maxShutterSpeed_; + utils::Duration minExposureTime_; + utils::Duration maxExposureTime_; double minAnalogueGain_; double maxAnalogueGain_; - utils::Duration filteredExposure_; - uint32_t stride_; + double rGain_; + double gGain_; + double bGain_; + ipu3_uapi_grid_config bdsGrid_; + std::vector<std::tuple<uint8_t, uint8_t, uint8_t>> rgbTriples_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/ipu3/algorithms/algorithm.h b/src/ipa/ipu3/algorithms/algorithm.h index ae134a94..c7801f93 100644 --- a/src/ipa/ipu3/algorithms/algorithm.h +++ b/src/ipa/ipu3/algorithms/algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * algorithm.h - IPU3 control algorithm interface + * IPU3 control algorithm interface */ #pragma once diff --git a/src/ipa/ipu3/algorithms/awb.cpp b/src/ipa/ipu3/algorithms/awb.cpp index 5abd4621..55de05d9 100644 --- a/src/ipa/ipu3/algorithms/awb.cpp +++ b/src/ipa/ipu3/algorithms/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * awb.cpp - AWB control algorithm + * AWB control algorithm */ #include "awb.h" @@ -13,6 +13,8 @@ #include <libcamera/control_ids.h> +#include "libipa/colours.h" + /** * \file awb.h */ @@ -301,51 +303,24 @@ void Awb::prepare(IPAContext &context, params->use.acc_ccm = 1; } -/** - * The function estimates the correlated color temperature using - * from RGB color space input. - * In physics and color science, the Planckian locus or black body locus is - * the path or locus that the color of an incandescent black body would take - * in a particular chromaticity space as the blackbody temperature changes. - * - * If a narrow range of color temperatures is considered (those encapsulating - * daylight being the most practical case) one can approximate the Planckian - * locus in order to calculate the CCT in terms of chromaticity coordinates. - * - * More detailed information can be found in: - * https://en.wikipedia.org/wiki/Color_temperature#Approximation - */ -uint32_t Awb::estimateCCT(double red, double green, double blue) -{ - /* Convert the RGB values to CIE tristimulus values (XYZ) */ - double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); - double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); - double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); - - /* Calculate the normalized chromaticity values */ - double x = X / (X + Y + Z); - double y = Y / (X + Y + Z); - - /* Calculate CCT */ - double n = (x - 0.3320) / (0.1858 - y); - return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; -} - /* Generate an RGB vector with the average values for each zone */ void Awb::generateZones() { zones_.clear(); for (unsigned int i = 0; i < kAwbStatsSizeX * kAwbStatsSizeY; i++) { - RGB zone; double counted = awbStats_[i].counted; if (counted >= cellsPerZoneThreshold_) { - zone.G = awbStats_[i].sum.green / counted; - if (zone.G >= kMinGreenLevelInZone) { - zone.R = awbStats_[i].sum.red / counted; - zone.B = awbStats_[i].sum.blue / counted; + RGB<double> zone{{ + static_cast<double>(awbStats_[i].sum.red), + static_cast<double>(awbStats_[i].sum.green), + static_cast<double>(awbStats_[i].sum.blue) + }}; + + zone /= counted; + + if (zone.g() >= kMinGreenLevelInZone) zones_.push_back(zone); - } } } } @@ -412,32 +387,32 @@ void Awb::awbGreyWorld() * consider some variations, such as normalising all the zones first, or * doing an L2 average etc. */ - std::vector<RGB> &redDerivative(zones_); - std::vector<RGB> blueDerivative(redDerivative); + std::vector<RGB<double>> &redDerivative(zones_); + std::vector<RGB<double>> blueDerivative(redDerivative); std::sort(redDerivative.begin(), redDerivative.end(), - [](RGB const &a, RGB const &b) { - return a.G * b.R < b.G * a.R; + [](RGB<double> const &a, RGB<double> const &b) { + return a.g() * b.r() < b.g() * a.r(); }); std::sort(blueDerivative.begin(), blueDerivative.end(), - [](RGB const &a, RGB const &b) { - return a.G * b.B < b.G * a.B; + [](RGB<double> const &a, RGB<double> const &b) { + return a.g() * b.b() < b.g() * a.b(); }); /* Average the middle half of the values. */ int discard = redDerivative.size() / 4; - RGB sumRed(0, 0, 0); - RGB sumBlue(0, 0, 0); + RGB<double> sumRed{ 0.0 }; + RGB<double> sumBlue{ 0.0 }; for (auto ri = redDerivative.begin() + discard, bi = blueDerivative.begin() + discard; ri != redDerivative.end() - discard; ri++, bi++) sumRed += *ri, sumBlue += *bi; - double redGain = sumRed.G / (sumRed.R + 1), - blueGain = sumBlue.G / (sumBlue.B + 1); + double redGain = sumRed.g() / (sumRed.r() + 1), + blueGain = sumBlue.g() / (sumBlue.b() + 1); /* Color temperature is not relevant in Grey world but still useful to estimate it :-) */ - asyncResults_.temperatureK = estimateCCT(sumRed.R, sumRed.G, sumBlue.B); + asyncResults_.temperatureK = estimateCCT({{ sumRed.r(), sumRed.g(), sumBlue.b() }}); /* * Gain values are unsigned integer value ranging [0, 8) with 13 bit diff --git a/src/ipa/ipu3/algorithms/awb.h b/src/ipa/ipu3/algorithms/awb.h index 7a70854e..dbf69c90 100644 --- a/src/ipa/ipu3/algorithms/awb.h +++ b/src/ipa/ipu3/algorithms/awb.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * awb.h - IPU3 AWB control algorithm + * IPU3 AWB control algorithm */ #pragma once @@ -13,6 +13,8 @@ #include <libcamera/geometry.h> +#include "libcamera/internal/vector.h" + #include "algorithm.h" namespace libcamera { @@ -48,20 +50,6 @@ public: ControlList &metadata) override; private: - /* \todo Make these structs available to all the ISPs ? */ - struct RGB { - RGB(double _R = 0, double _G = 0, double _B = 0) - : R(_R), G(_G), B(_B) - { - } - double R, G, B; - RGB &operator+=(RGB const &other) - { - R += other.R, G += other.G, B += other.B; - return *this; - } - }; - struct AwbStatus { double temperatureK; double redGain; @@ -75,11 +63,10 @@ private: void generateAwbStats(const ipu3_uapi_stats_3a *stats); void clearAwbStats(); void awbGreyWorld(); - uint32_t estimateCCT(double red, double green, double blue); static constexpr uint16_t threshold(float value); static constexpr uint16_t gainValue(double gain); - std::vector<RGB> zones_; + std::vector<RGB<double>> zones_; Accumulator awbStats_[kAwbStatsSizeX * kAwbStatsSizeY]; AwbStatus asyncResults_; diff --git a/src/ipa/ipu3/algorithms/blc.cpp b/src/ipa/ipu3/algorithms/blc.cpp index e838072a..35748fb2 100644 --- a/src/ipa/ipu3/algorithms/blc.cpp +++ b/src/ipa/ipu3/algorithms/blc.cpp @@ -2,13 +2,11 @@ /* * Copyright (C) 2021, Google inc. * - * blc.cpp - IPU3 Black Level Correction control + * IPU3 Black Level Correction control */ #include "blc.h" -#include <string.h> - /** * \file blc.h * \brief IPU3 Black Level Correction control @@ -57,8 +55,8 @@ void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, * tuning processes. This is a first rough approximation. */ params->obgrid_param.gr = 64; - params->obgrid_param.r = 64; - params->obgrid_param.b = 64; + params->obgrid_param.r = 64; + params->obgrid_param.b = 64; params->obgrid_param.gb = 64; /* Enable the custom black level correction processing */ diff --git a/src/ipa/ipu3/algorithms/blc.h b/src/ipa/ipu3/algorithms/blc.h index 292bf67b..62748045 100644 --- a/src/ipa/ipu3/algorithms/blc.h +++ b/src/ipa/ipu3/algorithms/blc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google inc. * - * black_correction.h - IPU3 Black Level Correction control + * IPU3 Black Level Correction control */ #pragma once diff --git a/src/ipa/ipu3/algorithms/tone_mapping.cpp b/src/ipa/ipu3/algorithms/tone_mapping.cpp index a169894c..160338c1 100644 --- a/src/ipa/ipu3/algorithms/tone_mapping.cpp +++ b/src/ipa/ipu3/algorithms/tone_mapping.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google inc. * - * tone_mapping.cpp - IPU3 ToneMapping and Gamma control + * IPU3 ToneMapping and Gamma control */ #include "tone_mapping.h" diff --git a/src/ipa/ipu3/algorithms/tone_mapping.h b/src/ipa/ipu3/algorithms/tone_mapping.h index 5ae35da5..b2b38010 100644 --- a/src/ipa/ipu3/algorithms/tone_mapping.h +++ b/src/ipa/ipu3/algorithms/tone_mapping.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google inc. * - * tone_mapping.h - IPU3 ToneMapping and Gamma control + * IPU3 ToneMapping and Gamma control */ #pragma once diff --git a/src/ipa/ipu3/data/meson.build b/src/ipa/ipu3/data/meson.build index 1f50b630..0f7cd5c6 100644 --- a/src/ipa/ipu3/data/meson.build +++ b/src/ipa/ipu3/data/meson.build @@ -5,4 +5,5 @@ conf_files = files([ ]) install_data(conf_files, - install_dir : ipa_data_dir / 'ipu3') + install_dir : ipa_data_dir / 'ipu3', + install_tag : 'runtime') diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp index 959f314f..3b22f791 100644 --- a/src/ipa/ipu3/ipa_context.cpp +++ b/src/ipa/ipu3/ipa_context.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * ipa_context.cpp - IPU3 IPA Context + * IPU3 IPA Context */ #include "ipa_context.h" @@ -39,6 +39,10 @@ namespace libcamera::ipa::ipu3 { * \struct IPAContext * \brief Global IPA context data shared between all algorithms * + * \fn IPAContext::IPAContext + * \brief Initialize the instance with the given number of frame contexts + * \param[in] frameContextSize Size of the frame context ring buffer + * * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * @@ -47,6 +51,9 @@ namespace libcamera::ipa::ipu3 { * * \var IPAContext::activeState * \brief The current state of IPA algorithms + * + * \var IPAContext::ctrlMap + * \brief A ControlInfoMap::Map of controls populated by the algorithms */ /** @@ -89,11 +96,11 @@ namespace libcamera::ipa::ipu3 { * \var IPASessionConfiguration::agc * \brief AGC parameters configuration of the IPA * - * \var IPASessionConfiguration::agc.minShutterSpeed - * \brief Minimum shutter speed supported with the configured sensor + * \var IPASessionConfiguration::agc.minExposureTime + * \brief Minimum exposure time supported with the configured sensor * - * \var IPASessionConfiguration::agc.maxShutterSpeed - * \brief Maximum shutter speed supported with the configured sensor + * \var IPASessionConfiguration::agc.maxExposureTime + * \brief Maximum exposure time supported with the configured sensor * * \var IPASessionConfiguration::agc.minAnalogueGain * \brief Minimum analogue gain supported with the configured sensor diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index e9a3863b..97fcf06c 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * ipa_context.h - IPU3 IPA Context + * IPU3 IPA Context * */ @@ -12,6 +12,7 @@ #include <libcamera/base/utils.h> +#include <libcamera/controls.h> #include <libcamera/geometry.h> #include <libipa/fc_queue.h> @@ -32,8 +33,8 @@ struct IPASessionConfiguration { } af; struct { - utils::Duration minShutterSpeed; - utils::Duration maxShutterSpeed; + utils::Duration minExposureTime; + utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; } agc; @@ -55,6 +56,8 @@ struct IPAActiveState { struct { uint32_t exposure; double gain; + uint32_t constraintMode; + uint32_t exposureMode; } agc; struct { @@ -81,10 +84,17 @@ struct IPAFrameContext : public FrameContext { }; struct IPAContext { + IPAContext(unsigned int frameContextSize) + : frameContexts(frameContextSize) + { + } + IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue<IPAFrameContext> frameContexts; + + ControlInfoMap::Map ctrlMap; }; } /* namespace ipa::ipu3 */ diff --git a/src/ipa/ipu3/ipu3-ipa-design-guide.rst b/src/ipa/ipu3/ipu3-ipa-design-guide.rst index 72506397..85d735c6 100644 --- a/src/ipa/ipu3/ipu3-ipa-design-guide.rst +++ b/src/ipa/ipu3/ipu3-ipa-design-guide.rst @@ -27,8 +27,8 @@ from applications, and managing events from the pipeline handler. └─┬───┬───┬──────┬────┬────┬────┬─┴────▼─┬──┘ 1: init() │ │ │ │ ▲ │ ▲ │ ▲ │ ▲ │ 2: configure() │1 │2 │3 │4│ │4│ │4│ │4│ │5 3: mapBuffers(), start() - │ │ │ │ │ │ │ │ │ │ │ │ 4: (▼) queueRequest(), fillParamsBuffer(), processStatsBuffer() - ▼ ▼ ▼ ▼ │ ▼ │ ▼ │ ▼ │ ▼ (▲) setSensorControls, paramsBufferReady, metadataReady Signals + │ │ │ │ │ │ │ │ │ │ │ │ 4: (▼) queueRequest(), computeParams(), processStats() + ▼ ▼ ▼ ▼ │ ▼ │ ▼ │ ▼ │ ▼ (▲) setSensorControls, paramsComputed, metadataReady Signals ┌──────────────────┴────┴────┴────┴─────────┐ 5: stop(), unmapBuffers() │ IPU3 IPA │ │ ┌───────────────────────┐ │ @@ -104,8 +104,8 @@ to operate when running: - configure() - queueRequest() -- fillParamsBuffer() -- processStatsBuffer() +- computeParams() +- processStats() The configuration phase allows the pipeline-handler to inform the IPA of the current stream configurations, which is then passed into each @@ -119,7 +119,7 @@ When configured, the IPA is notified by the pipeline handler of the Camera ``start()`` event, after which incoming requests will be queued for processing, requiring a parameter buffer (``ipu3_uapi_params``) to be populated for the ImgU. This is given to the IPA through -``fillParamsBuffer()``, and then passed directly to each algorithm +``computeParams()``, and then passed directly to each algorithm through the ``prepare()`` call allowing the ISP configuration to be updated for the needs of each component that the algorithm is responsible for. @@ -129,7 +129,7 @@ structure that it modifies, and it should take care to ensure that any structure set by a use flag is fully initialised to suitable values. The parameter buffer is returned to the pipeline handler through the -``paramsBufferReady`` signal, and from there queued to the ImgU along +``paramsComputed`` signal, and from there queued to the ImgU along with a raw frame captured with the CIO2. Post-frame completion @@ -138,7 +138,7 @@ Post-frame completion When the capture of an image is completed, and successfully processed through the ImgU, the generated statistics buffer (``ipu3_uapi_stats_3a``) is given to the IPA through -``processStatsBuffer()``. This provides the IPA with an opportunity to +``processStats()``. This provides the IPA with an opportunity to examine the results of the ISP and run the calculations required by each algorithm on the new data. The algorithms may require context from the operations of other algorithms, for example, the AWB might choose to use diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 08ee6eb3..1cae08bf 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipu3.cpp - IPU3 Image Processing Algorithms + * IPU3 Image Processing Algorithms */ #include <algorithm> @@ -23,24 +23,22 @@ #include <libcamera/base/utils.h> #include <libcamera/control_ids.h> +#include <libcamera/controls.h> #include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> +#include <libcamera/request.h> + #include <libcamera/ipa/ipa_interface.h> #include <libcamera/ipa/ipa_module_info.h> #include <libcamera/ipa/ipu3_ipa_interface.h> -#include <libcamera/request.h> #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/yaml_parser.h" -#include "algorithms/af.h" -#include "algorithms/agc.h" -#include "algorithms/algorithm.h" -#include "algorithms/awb.h" -#include "algorithms/blc.h" -#include "algorithms/tone_mapping.h" #include "libipa/camera_sensor_helper.h" #include "ipa_context.h" +#include "module.h" /* Minimum grid width, expressed as a number of cells */ static constexpr uint32_t kMinGridWidth = 16; @@ -89,14 +87,14 @@ namespace ipa::ipu3 { * parameter buffer, and adapting the settings of the sensor attached to the * IPU3 CIO2 through sensor-specific V4L2 controls. * - * In fillParamsBuffer(), we populate the ImgU parameter buffer with + * In computeParams(), we populate the ImgU parameter buffer with * settings to configure the device in preparation for handling the frame * queued in the Request. * * When the frame has completed processing, the ImgU will generate a statistics - * buffer which is given to the IPA with processStatsBuffer(). In this we run the + * buffer which is given to the IPA with processStats(). In this we run the * algorithms to parse the statistics and cache any results for the next - * fillParamsBuffer() call. + * computeParams() call. * * The individual algorithms are split into modular components that are called * iteratively to allow them to process statistics from the ImgU in the order @@ -114,7 +112,7 @@ namespace ipa::ipu3 { * blue gains to apply to generate a neutral grey frame overall. * * AGC is handled by calculating a histogram of the green channel to estimate an - * analogue gain and shutter time which will provide a well exposed frame. A + * analogue gain and exposure time which will provide a well exposed frame. A * low-pass IIR filter is used to smooth the changes to the sensor to reduce * perceivable steps. * @@ -157,10 +155,10 @@ public: void unmapBuffers(const std::vector<unsigned int> &ids) override; void queueRequest(const uint32_t frame, const ControlList &controls) override; - void fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) override; - void processStatsBuffer(const uint32_t frame, const int64_t frameTimestamp, - const uint32_t bufferId, - const ControlList &sensorControls) override; + void computeParams(const uint32_t frame, const uint32_t bufferId) override; + void processStats(const uint32_t frame, const int64_t frameTimestamp, + const uint32_t bufferId, + const ControlList &sensorControls) override; protected: std::string logPrefix() const override; @@ -189,7 +187,7 @@ private: }; IPAIPU3::IPAIPU3() - : context_({ {}, {}, { kMaxFrameContexts } }) + : context_(kMaxFrameContexts) { } @@ -217,13 +215,13 @@ void IPAIPU3::updateSessionConfiguration(const ControlInfoMap &sensorControls) /* * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for shutter speed and analogue gain. + * to know the limits for exposure time and analogue gain. * As it depends on the sensor, update it with the controls. * - * \todo take VBLANK into account for maximum shutter speed + * \todo take VBLANK into account for maximum exposure time */ - context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.minExposureTime = minExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.maxExposureTime = maxExposure * context_.configuration.sensor.lineDuration; context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); } @@ -287,6 +285,7 @@ void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo, frameDurations[1], frameDurations[2]); + controls.merge(context_.ctrlMap); *ipaControls = ControlInfoMap(std::move(controls), controls::controls); } @@ -312,8 +311,8 @@ int IPAIPU3::init(const IPASettings &settings, /* Clean context */ context_.configuration = {}; - context_.configuration.sensor.lineDuration = sensorInfo.minLineLength - * 1.0s / sensorInfo.pixelRate; + context_.configuration.sensor.lineDuration = + sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; /* Load the tuning data file. */ File file(settings.configurationFile); @@ -476,8 +475,8 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo, context_.frameContexts.clear(); /* Initialise the sensor configuration. */ - context_.configuration.sensor.lineDuration = sensorInfo_.minLineLength - * 1.0s / sensorInfo_.pixelRate; + context_.configuration.sensor.lineDuration = + sensorInfo_.minLineLength * 1.0s / sensorInfo_.pixelRate; context_.configuration.sensor.size = sensorInfo_.outputSize; /* @@ -539,7 +538,7 @@ void IPAIPU3::unmapBuffers(const std::vector<unsigned int> &ids) * Algorithms are expected to fill the IPU3 parameter buffer for the next * frame given their most recent processing of the ImgU statistics. */ -void IPAIPU3::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) +void IPAIPU3::computeParams(const uint32_t frame, const uint32_t bufferId) { auto it = buffers_.find(bufferId); if (it == buffers_.end()) { @@ -567,7 +566,7 @@ void IPAIPU3::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) for (auto const &algo : algorithms()) algo->prepare(context_, frame, frameContext, params); - paramsBufferReady.emit(frame); + paramsComputed.emit(frame); } /** @@ -581,9 +580,9 @@ void IPAIPU3::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) * statistics are passed to each algorithm module to run their calculations and * update their state accordingly. */ -void IPAIPU3::processStatsBuffer(const uint32_t frame, - [[maybe_unused]] const int64_t frameTimestamp, - const uint32_t bufferId, const ControlList &sensorControls) +void IPAIPU3::processStats(const uint32_t frame, + [[maybe_unused]] const int64_t frameTimestamp, + const uint32_t bufferId, const ControlList &sensorControls) { auto it = buffers_.find(bufferId); if (it == buffers_.end()) { @@ -672,7 +671,7 @@ extern "C" { const struct IPAModuleInfo ipaModuleInfo = { IPA_MODULE_API_VERSION, 1, - "PipelineHandlerIPU3", + "ipu3", "ipu3", }; diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build index 66c39843..34de6213 100644 --- a/src/ipa/ipu3/meson.build +++ b/src/ipa/ipu3/meson.build @@ -12,12 +12,10 @@ ipu3_ipa_sources = files([ ipu3_ipa_sources += ipu3_ipa_algorithms -mod = shared_module(ipa_name, - [ipu3_ipa_sources, libcamera_generated_ipa_headers], +mod = shared_module(ipa_name, ipu3_ipa_sources, name_prefix : '', - include_directories : [ipa_includes, libipa_includes], - dependencies : libcamera_private, - link_with : libipa, + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], install : true, install_dir : ipa_install_dir) diff --git a/src/ipa/ipu3/module.h b/src/ipa/ipu3/module.h index d94fc459..60f65cc4 100644 --- a/src/ipa/ipu3/module.h +++ b/src/ipa/ipu3/module.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas On Board * - * module.h - IPU3 IPA Module + * IPU3 IPA Module */ #pragma once diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp new file mode 100644 index 00000000..d37a9b66 --- /dev/null +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -0,0 +1,589 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * Base class for mean luminance AGC algorithms + */ + +#include "agc_mean_luminance.h" + +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + +#include "exposure_mode_helper.h" + +using namespace libcamera::controls; + +/** + * \file agc_mean_luminance.h + * \brief Base class implementing mean luminance AEGC + */ + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +LOG_DEFINE_CATEGORY(AgcMeanLuminance) + +namespace ipa { + +/* + * Number of frames for which to run the algorithm at full speed, before slowing + * down to prevent large and jarring changes in exposure from frame to frame. + */ +static constexpr uint32_t kNumStartupFrames = 10; + +/* + * Default relative luminance target + * + * This value should be chosen so that when the camera points at a grey target, + * the resulting image brightness looks "right". Custom values can be passed + * as the relativeLuminanceTarget value in sensor tuning files. + */ +static constexpr double kDefaultRelativeLuminanceTarget = 0.16; + +/** + * \struct AgcMeanLuminance::AgcConstraint + * \brief The boundaries and target for an AeConstraintMode constraint + * + * This structure describes an AeConstraintMode constraint for the purposes of + * this algorithm. These constraints are expressed as a pair of quantile + * boundaries for a histogram, along with a luminance target and a bounds-type. + * The algorithm uses the constraints by ensuring that the defined portion of a + * luminance histogram (I.E. lying between the two quantiles) is above or below + * the given luminance value. + */ + +/** + * \enum AgcMeanLuminance::AgcConstraint::Bound + * \brief Specify whether the constraint defines a lower or upper bound + * \var AgcMeanLuminance::AgcConstraint::Lower + * \brief The constraint defines a lower bound + * \var AgcMeanLuminance::AgcConstraint::Upper + * \brief The constraint defines an upper bound + */ + +/** + * \var AgcMeanLuminance::AgcConstraint::bound + * \brief The type of constraint bound + */ + +/** + * \var AgcMeanLuminance::AgcConstraint::qLo + * \brief The lower quantile to use for the constraint + */ + +/** + * \var AgcMeanLuminance::AgcConstraint::qHi + * \brief The upper quantile to use for the constraint + */ + +/** + * \var AgcMeanLuminance::AgcConstraint::yTarget + * \brief The luminance target for the constraint + */ + +/** + * \class AgcMeanLuminance + * \brief A mean-based auto-exposure algorithm + * + * This algorithm calculates an exposure time, analogue and digital gain such + * that the normalised mean luminance value of an image is driven towards a + * target, which itself is discovered from tuning data. The algorithm is a + * two-stage process. + * + * In the first stage, an initial gain value is derived by iteratively comparing + * the gain-adjusted mean luminance across the entire image against a target, + * and selecting a value which pushes it as closely as possible towards the + * target. + * + * In the second stage we calculate the gain required to drive the average of a + * section of a histogram to a target value, where the target and the boundaries + * of the section of the histogram used in the calculation are taken from the + * values defined for the currently configured AeConstraintMode within the + * tuning data. This class provides a helper function to parse those tuning data + * to discover the constraints, and so requires a specific format for those + * data which is described in \ref parseTuningData(). The gain from the first + * stage is then clamped to the gain from this stage. + * + * The final gain is used to adjust the effective exposure value of the image, + * and that new exposure value is divided into exposure time, analogue gain and + * digital gain according to the selected AeExposureMode. This class uses the + * \ref ExposureModeHelper class to assist in that division, and expects the + * data needed to initialise that class to be present in tuning data in a + * format described in \ref parseTuningData(). + * + * In order to be able to use this algorithm an IPA module needs to be able to + * do the following: + * + * 1. Provide a luminance estimation across an entire image. + * 2. Provide a luminance Histogram for the image to use in calculating + * constraint compliance. The precision of the Histogram that is available + * will determine the supportable precision of the constraints. + * + * IPA modules that want to use this class to implement their AEGC algorithm + * should derive it and provide an overriding estimateLuminance() function for + * this class to use. They must call parseTuningData() in init(), and must also + * call setLimits() and resetFrameCounter() in configure(). They may then use + * calculateNewEv() in process(). If the limits passed to setLimits() change for + * any reason (for example, in response to a FrameDurationLimit control being + * passed in queueRequest()) then setLimits() must be called again with the new + * values. + */ + +AgcMeanLuminance::AgcMeanLuminance() + : frameCount_(0), filteredExposure_(0s), relativeLuminanceTarget_(0) +{ +} + +AgcMeanLuminance::~AgcMeanLuminance() = default; + +void AgcMeanLuminance::parseRelativeLuminanceTarget(const YamlObject &tuningData) +{ + relativeLuminanceTarget_ = + tuningData["relativeLuminanceTarget"].get<double>(kDefaultRelativeLuminanceTarget); +} + +void AgcMeanLuminance::parseConstraint(const YamlObject &modeDict, int32_t id) +{ + for (const auto &[boundName, content] : modeDict.asDict()) { + if (boundName != "upper" && boundName != "lower") { + LOG(AgcMeanLuminance, Warning) + << "Ignoring unknown constraint bound '" << boundName << "'"; + continue; + } + + unsigned int idx = static_cast<unsigned int>(boundName == "upper"); + AgcConstraint::Bound bound = static_cast<AgcConstraint::Bound>(idx); + double qLo = content["qLo"].get<double>().value_or(0.98); + double qHi = content["qHi"].get<double>().value_or(1.0); + double yTarget = + content["yTarget"].getList<double>().value_or(std::vector<double>{ 0.5 }).at(0); + + AgcConstraint constraint = { bound, qLo, qHi, yTarget }; + + if (!constraintModes_.count(id)) + constraintModes_[id] = {}; + + if (idx) + constraintModes_[id].push_back(constraint); + else + constraintModes_[id].insert(constraintModes_[id].begin(), constraint); + } +} + +int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData) +{ + std::vector<ControlValue> availableConstraintModes; + + const YamlObject &yamlConstraintModes = tuningData[controls::AeConstraintMode.name()]; + if (yamlConstraintModes.isDictionary()) { + for (const auto &[modeName, modeDict] : yamlConstraintModes.asDict()) { + if (AeConstraintModeNameValueMap.find(modeName) == + AeConstraintModeNameValueMap.end()) { + LOG(AgcMeanLuminance, Warning) + << "Skipping unknown constraint mode '" << modeName << "'"; + continue; + } + + if (!modeDict.isDictionary()) { + LOG(AgcMeanLuminance, Error) + << "Invalid constraint mode '" << modeName << "'"; + return -EINVAL; + } + + parseConstraint(modeDict, + AeConstraintModeNameValueMap.at(modeName)); + availableConstraintModes.push_back( + AeConstraintModeNameValueMap.at(modeName)); + } + } + + /* + * If the tuning data file contains no constraints then we use the + * default constraint that the IPU3/RkISP1 Agc algorithms were adhering + * to anyway before centralisation; this constraint forces the top 2% of + * the histogram to be at least 0.5. + */ + if (constraintModes_.empty()) { + AgcConstraint constraint = { + AgcConstraint::Bound::Lower, + 0.98, + 1.0, + 0.5 + }; + + constraintModes_[controls::ConstraintNormal].insert( + constraintModes_[controls::ConstraintNormal].begin(), + constraint); + availableConstraintModes.push_back(controls::ConstraintNormal); + } + + controls_[&controls::AeConstraintMode] = ControlInfo(availableConstraintModes); + + return 0; +} + +int AgcMeanLuminance::parseExposureModes(const YamlObject &tuningData) +{ + std::vector<ControlValue> availableExposureModes; + + const YamlObject &yamlExposureModes = tuningData[controls::AeExposureMode.name()]; + if (yamlExposureModes.isDictionary()) { + for (const auto &[modeName, modeValues] : yamlExposureModes.asDict()) { + if (AeExposureModeNameValueMap.find(modeName) == + AeExposureModeNameValueMap.end()) { + LOG(AgcMeanLuminance, Warning) + << "Skipping unknown exposure mode '" << modeName << "'"; + continue; + } + + if (!modeValues.isDictionary()) { + LOG(AgcMeanLuminance, Error) + << "Invalid exposure mode '" << modeName << "'"; + return -EINVAL; + } + + std::vector<uint32_t> exposureTimes = + modeValues["exposureTime"].getList<uint32_t>().value_or(std::vector<uint32_t>{}); + std::vector<double> gains = + modeValues["gain"].getList<double>().value_or(std::vector<double>{}); + + if (exposureTimes.size() != gains.size()) { + LOG(AgcMeanLuminance, Error) + << "Exposure time and gain array sizes unequal"; + return -EINVAL; + } + + if (exposureTimes.empty()) { + LOG(AgcMeanLuminance, Error) + << "Exposure time and gain arrays are empty"; + return -EINVAL; + } + + std::vector<std::pair<utils::Duration, double>> stages; + for (unsigned int i = 0; i < exposureTimes.size(); i++) { + stages.push_back({ + std::chrono::microseconds(exposureTimes[i]), + gains[i] + }); + } + + std::shared_ptr<ExposureModeHelper> helper = + std::make_shared<ExposureModeHelper>(stages); + + exposureModeHelpers_[AeExposureModeNameValueMap.at(modeName)] = helper; + availableExposureModes.push_back(AeExposureModeNameValueMap.at(modeName)); + } + } + + /* + * If we don't have any exposure modes in the tuning data we create an + * ExposureModeHelper using an empty vector of stages. This will result + * in the ExposureModeHelper simply driving the exposure time as high as + * possible before touching gain. + */ + if (availableExposureModes.empty()) { + int32_t exposureModeId = controls::ExposureNormal; + std::vector<std::pair<utils::Duration, double>> stages = { }; + + std::shared_ptr<ExposureModeHelper> helper = + std::make_shared<ExposureModeHelper>(stages); + + exposureModeHelpers_[exposureModeId] = helper; + availableExposureModes.push_back(exposureModeId); + } + + controls_[&controls::AeExposureMode] = ControlInfo(availableExposureModes); + + return 0; +} + +/** + * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls + * \param[in] tuningData the YamlObject representing the tuning data + * + * This function parses tuning data to build the list of allowed values for the + * AeConstraintMode and AeExposureMode controls. Those tuning data must provide + * the data in a specific format; the Agc algorithm's tuning data should contain + * a dictionary called AeConstraintMode containing per-mode setting dictionaries + * with the key being a value from \ref controls::AeConstraintModeNameValueMap. + * Each mode dict may contain either a "lower" or "upper" key or both, for + * example: + * + * \code{.unparsed} + * algorithms: + * - Agc: + * AeConstraintMode: + * ConstraintNormal: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * ConstraintHighlight: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * upper: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.8 + * + * \endcode + * + * For the AeExposureMode control the data should contain a dictionary called + * AeExposureMode containing per-mode setting dictionaries with the key being a + * value from \ref controls::AeExposureModeNameValueMap. Each mode dict should + * contain an array of exposure times with the key "exposureTime" and an array + * of gain values with the key "gain", in this format: + * + * \code{.unparsed} + * algorithms: + * - Agc: + * AeExposureMode: + * ExposureNormal: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * ExposureShort: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * + * \endcode + * + * \return 0 on success or a negative error code + */ +int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData) +{ + int ret; + + parseRelativeLuminanceTarget(tuningData); + + ret = parseConstraintModes(tuningData); + if (ret) + return ret; + + return parseExposureModes(tuningData); +} + +/** + * \brief Set the ExposureModeHelper limits for this class + * \param[in] minExposureTime Minimum exposure time to allow + * \param[in] maxExposureTime Maximum ewposure time to allow + * \param[in] minGain Minimum gain to allow + * \param[in] maxGain Maximum gain to allow + * + * This function calls \ref ExposureModeHelper::setLimits() for each + * ExposureModeHelper that has been created for this class. + */ +void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, + utils::Duration maxExposureTime, + double minGain, double maxGain) +{ + for (auto &[id, helper] : exposureModeHelpers_) + helper->setLimits(minExposureTime, maxExposureTime, minGain, maxGain); +} + +/** + * \fn AgcMeanLuminance::constraintModes() + * \brief Get the constraint modes that have been parsed from tuning data + */ + +/** + * \fn AgcMeanLuminance::exposureModeHelpers() + * \brief Get the ExposureModeHelpers that have been parsed from tuning data + */ + +/** + * \fn AgcMeanLuminance::controls() + * \brief Get the controls that have been generated after parsing tuning data + */ + +/** + * \fn AgcMeanLuminance::estimateLuminance(const double gain) + * \brief Estimate the luminance of an image, adjusted by a given gain + * \param[in] gain The gain with which to adjust the luminance estimate + * + * This function estimates the average relative luminance of the frame that + * would be output by the sensor if an additional \a gain was applied. It is a + * pure virtual function because estimation of luminance is a hardware-specific + * operation, which depends wholly on the format of the stats that are delivered + * to libcamera from the ISP. Derived classes must override this function with + * one that calculates the normalised mean luminance value across the entire + * image. + * + * \return The normalised relative luminance of the image + */ + +/** + * \brief Estimate the initial gain needed to achieve a relative luminance + * target + * \return The calculated initial gain + */ +double AgcMeanLuminance::estimateInitialGain() const +{ + double yTarget = relativeLuminanceTarget_; + double yGain = 1.0; + + /* + * To account for non-linearity caused by saturation, the value needs to + * be estimated in an iterative process, as multiplying by a gain will + * not increase the relative luminance by the same factor if some image + * regions are saturated. + */ + for (unsigned int i = 0; i < 8; i++) { + double yValue = estimateLuminance(yGain); + double extra_gain = std::min(10.0, yTarget / (yValue + .001)); + + yGain *= extra_gain; + LOG(AgcMeanLuminance, Debug) << "Y value: " << yValue + << ", Y target: " << yTarget + << ", gives gain " << yGain; + + if (utils::abs_diff(extra_gain, 1.0) < 0.01) + break; + } + + return yGain; +} + +/** + * \brief Clamp gain within the bounds of a defined constraint + * \param[in] constraintModeIndex The index of the constraint to adhere to + * \param[in] hist A histogram over which to calculate inter-quantile means + * \param[in] gain The gain to clamp + * + * \return The gain clamped within the constraint bounds + */ +double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex, + const Histogram &hist, + double gain) +{ + std::vector<AgcConstraint> &constraints = constraintModes_[constraintModeIndex]; + for (const AgcConstraint &constraint : constraints) { + double newGain = constraint.yTarget * hist.bins() / + hist.interQuantileMean(constraint.qLo, constraint.qHi); + + if (constraint.bound == AgcConstraint::Bound::Lower && + newGain > gain) + gain = newGain; + + if (constraint.bound == AgcConstraint::Bound::Upper && + newGain < gain) + gain = newGain; + } + + return gain; +} + +/** + * \brief Apply a filter on the exposure value to limit the speed of changes + * \param[in] exposureValue The target exposure from the AGC algorithm + * + * The speed of the filter is adaptive, and will produce the target quicker + * during startup, or when the target exposure is within 20% of the most recent + * filter output. + * + * \return The filtered exposure + */ +utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) +{ + double speed = 0.2; + + /* Adapt instantly if we are in startup phase. */ + if (frameCount_ < kNumStartupFrames) + speed = 1.0; + + /* + * If we are close to the desired result, go faster to avoid making + * multiple micro-adjustments. + * \todo Make this customisable? + */ + if (filteredExposure_ < 1.2 * exposureValue && + filteredExposure_ > 0.8 * exposureValue) + speed = sqrt(speed); + + filteredExposure_ = speed * exposureValue + + filteredExposure_ * (1.0 - speed); + + return filteredExposure_; +} + +/** + * \brief Calculate the new exposure value and splut it between exposure time + * and gain + * \param[in] constraintModeIndex The index of the current constraint mode + * \param[in] exposureModeIndex The index of the current exposure mode + * \param[in] yHist A Histogram from the ISP statistics to use in constraining + * the calculated gain + * \param[in] effectiveExposureValue The EV applied to the frame from which the + * statistics in use derive + * + * Calculate a new exposure value to try to obtain the target. The calculated + * exposure value is filtered to prevent rapid changes from frame to frame, and + * divided into exposure time, analogue and digital gain. + * + * \return Tuple of exposure time, analogue gain, and digital gain + */ +std::tuple<utils::Duration, double, double> +AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, + uint32_t exposureModeIndex, + const Histogram &yHist, + utils::Duration effectiveExposureValue) +{ + /* + * The pipeline handler should validate that we have received an allowed + * value for AeExposureMode. + */ + std::shared_ptr<ExposureModeHelper> exposureModeHelper = + exposureModeHelpers_.at(exposureModeIndex); + + if (effectiveExposureValue == 0s) { + LOG(AgcMeanLuminance, Error) + << "Effective exposure value is 0. This is a bug in AGC " + "and must be fixed for proper operation."; + /* + * Return an arbitrary exposure time > 0 to ensure regulation + * doesn't get stuck with 0 in case the sensor driver allows a + * min exposure of 0. + */ + return exposureModeHelper->splitExposure(10ms); + } + + double gain = estimateInitialGain(); + gain = constraintClampGain(constraintModeIndex, yHist, gain); + + /* + * We don't check whether we're already close to the target, because + * even if the effective exposure value is the same as the last frame's + * we could have switched to an exposure mode that would require a new + * pass through the splitExposure() function. + */ + + utils::Duration newExposureValue = effectiveExposureValue * gain; + + /* + * We filter the exposure value to make sure changes are not too jarring + * from frame to frame. + */ + newExposureValue = filterExposure(newExposureValue); + + frameCount_++; + return exposureModeHelper->splitExposure(newExposureValue); +} + +/** + * \fn AgcMeanLuminance::resetFrameCount() + * \brief Reset the frame counter + * + * This function resets the internal frame counter, which exists to help the + * algorithm decide whether it should respond instantly or not. The expectation + * is for derived classes to call this function before each camera start call in + * their configure() function. + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h new file mode 100644 index 00000000..c41391cb --- /dev/null +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + agc_mean_luminance.h - Base class for mean luminance AGC algorithms + */ + +#pragma once + +#include <map> +#include <memory> +#include <tuple> +#include <vector> + +#include <libcamera/base/utils.h> + +#include <libcamera/controls.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "exposure_mode_helper.h" +#include "histogram.h" + +namespace libcamera { + +namespace ipa { + +class AgcMeanLuminance +{ +public: + AgcMeanLuminance(); + virtual ~AgcMeanLuminance(); + + struct AgcConstraint { + enum class Bound { + Lower = 0, + Upper = 1 + }; + Bound bound; + double qLo; + double qHi; + double yTarget; + }; + + int parseTuningData(const YamlObject &tuningData); + + void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, + double minGain, double maxGain); + + std::map<int32_t, std::vector<AgcConstraint>> constraintModes() + { + return constraintModes_; + } + + std::map<int32_t, std::shared_ptr<ExposureModeHelper>> exposureModeHelpers() + { + return exposureModeHelpers_; + } + + ControlInfoMap::Map controls() + { + return controls_; + } + + std::tuple<utils::Duration, double, double> + calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, + const Histogram &yHist, utils::Duration effectiveExposureValue); + + void resetFrameCount() + { + frameCount_ = 0; + } + +private: + virtual double estimateLuminance(const double gain) const = 0; + + void parseRelativeLuminanceTarget(const YamlObject &tuningData); + void parseConstraint(const YamlObject &modeDict, int32_t id); + int parseConstraintModes(const YamlObject &tuningData); + int parseExposureModes(const YamlObject &tuningData); + double estimateInitialGain() const; + double constraintClampGain(uint32_t constraintModeIndex, + const Histogram &hist, + double gain); + utils::Duration filterExposure(utils::Duration exposureValue); + + uint64_t frameCount_; + utils::Duration filteredExposure_; + double relativeLuminanceTarget_; + + std::map<int32_t, std::vector<AgcConstraint>> constraintModes_; + std::map<int32_t, std::shared_ptr<ExposureModeHelper>> exposureModeHelpers_; + ControlInfoMap::Map controls_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/algorithm.cpp b/src/ipa/libipa/algorithm.cpp index bc1c29a6..201efdfd 100644 --- a/src/ipa/libipa/algorithm.cpp +++ b/src/ipa/libipa/algorithm.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * algorithm.cpp - IPA control algorithm interface + * IPA control algorithm interface */ #include "algorithm.h" diff --git a/src/ipa/libipa/algorithm.h b/src/ipa/libipa/algorithm.h index 987e3e4c..9a19dbd6 100644 --- a/src/ipa/libipa/algorithm.h +++ b/src/ipa/libipa/algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * algorithm.h - ISP control algorithm interface + * ISP control algorithm interface */ #pragma once diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp new file mode 100644 index 00000000..214bac8b --- /dev/null +++ b/src/ipa/libipa/awb.cpp @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * Generic AWB algorithms + */ + +#include "awb.h" + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +/** + * \file awb.h + * \brief Base classes for AWB algorithms + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Awb) + +namespace ipa { + +/** + * \class AwbResult + * \brief The result of an AWB calculation + * + * This class holds the result of an auto white balance calculation. + */ + +/** + * \var AwbResult::gains + * \brief The calculated white balance gains + */ + +/** + * \var AwbResult::colourTemperature + * \brief The calculated colour temperature in Kelvin + */ + +/** + * \class AwbStats + * \brief An abstraction class wrapping hardware-specific AWB statistics + * + * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to + * implement this class to give the algorithm access to the hardware-specific + * statistics data. + */ + +/** + * \fn AwbStats::computeColourError() + * \brief Compute an error value for when the given gains would be applied + * \param[in] gains The gains to apply + * + * Compute an error value (non-greyness) assuming the given \a gains would be + * applied. To keep the actual implementations computationally inexpensive, + * the squared colour error shall be returned. + * + * If the AWB statistics provide multiple zones, the average of the individual + * squared errors shall be returned. Averaging/normalizing is necessary so that + * the numeric dimensions are the same on all hardware platforms. + * + * \return The computed error value + */ + +/** + * \fn AwbStats::rgbMeans() + * \brief Get RGB means of the statistics + * + * Fetch the RGB means from the statistics. The values of each channel are + * dimensionless and only the ratios are used for further calculations. This is + * used by the simple grey world model to calculate the gains to apply. + * + * \return The RGB means + */ + +/** + * \class AwbAlgorithm + * \brief A base class for auto white balance algorithms + * + * This class is a base class for auto white balance algorithms. It defines an + * interface for the algorithms to implement, and is used by the IPAs to + * interact with the concrete implementation. + */ + +/** + * \fn AwbAlgorithm::init() + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * \return 0 on success, a negative error code otherwise + */ + +/** + * \fn AwbAlgorithm::calculateAwb() + * \brief Calculate AWB data from the given statistics + * \param[in] stats The statistics to use for the calculation + * \param[in] lux The lux value of the scene + * + * Calculate an AwbResult object from the given statistics and lux value. A \a + * lux value of 0 means it is unknown or invalid and the algorithm shall ignore + * it. + * + * \return The AWB result + */ + +/** + * \fn AwbAlgorithm::gainsFromColourTemperature() + * \brief Compute white balance gains from a colour temperature + * \param[in] colourTemperature The colour temperature in Kelvin + * + * Compute the white balance gains from a \a colourTemperature. This function + * does not take any statistics into account. It is used to compute the colour + * gains when the user manually specifies a colour temperature. + * + * \return The colour gains or std::nullopt if the conversion is not possible + */ + +/** + * \fn AwbAlgorithm::controls() + * \brief Get the controls info map for this algorithm + * + * \return The controls info map + */ + +/** + * \fn AwbAlgorithm::handleControls() + * \param[in] controls The controls to handle + * \brief Handle the controls supplied in a request + */ + +/** + * \brief Parse the mode configurations from the tuning data + * \param[in] tuningData the YamlObject representing the tuning data + * \param[in] def The default value for the AwbMode control + * + * Utility function to parse the tuning data for an AwbMode entry and read all + * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and + * populates AwbAlgorithm::modes_. For a list of possible modes see \ref + * controls::AwbModeEnum. + * + * Each mode entry must contain a "lo" and "hi" key to specify the lower and + * upper colour temperature of that mode. For example: + * + * \code{.unparsed} + * algorithms: + * - Awb: + * AwbMode: + * AwbAuto: + * lo: 2500 + * hi: 8000 + * AwbIncandescent: + * lo: 2500 + * hi: 3000 + * ... + * \endcode + * + * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is + * returned. + * + * \sa controls::AwbModeEnum + * \return Zero on success, negative error code otherwise + */ +int AwbAlgorithm::parseModeConfigs(const YamlObject &tuningData, + const ControlValue &def) +{ + std::vector<ControlValue> availableModes; + + const YamlObject &yamlModes = tuningData[controls::AwbMode.name()]; + if (!yamlModes.isDictionary()) { + LOG(Awb, Error) + << "AwbModes must be a dictionary."; + return -EINVAL; + } + + for (const auto &[modeName, modeDict] : yamlModes.asDict()) { + if (controls::AwbModeNameValueMap.find(modeName) == + controls::AwbModeNameValueMap.end()) { + LOG(Awb, Warning) + << "Skipping unknown AWB mode '" + << modeName << "'"; + continue; + } + + if (!modeDict.isDictionary()) { + LOG(Awb, Error) + << "Invalid AWB mode '" << modeName << "'"; + return -EINVAL; + } + + const auto &modeValue = static_cast<controls::AwbModeEnum>( + controls::AwbModeNameValueMap.at(modeName)); + + ModeConfig &config = modes_[modeValue]; + + auto hi = modeDict["hi"].get<double>(); + if (!hi) { + LOG(Awb, Error) << "Failed to read hi param of mode " + << modeName; + return -EINVAL; + } + config.ctHi = *hi; + + auto lo = modeDict["lo"].get<double>(); + if (!lo) { + LOG(Awb, Error) << "Failed to read low param of mode " + << modeName; + return -EINVAL; + } + config.ctLo = *lo; + + availableModes.push_back(modeValue); + } + + if (modes_.empty()) { + LOG(Awb, Error) << "No AWB modes configured"; + return -EINVAL; + } + + if (!def.isNone() && + modes_.find(def.get<controls::AwbModeEnum>()) == modes_.end()) { + const auto &names = controls::AwbMode.enumerators(); + LOG(Awb, Error) << names.at(def.get<controls::AwbModeEnum>()) + << " mode is missing in the configuration."; + return -EINVAL; + } + + controls_[&controls::AwbMode] = ControlInfo(availableModes, def); + + return 0; +} + +/** + * \class AwbAlgorithm::ModeConfig + * \brief Holds the configuration of a single AWB mode + * + * AWB modes limit the regulation of the AWB algorithm to a specific range of + * colour temperatures. + */ + +/** + * \var AwbAlgorithm::ModeConfig::ctLo + * \brief The lowest valid colour temperature of that mode + */ + +/** + * \var AwbAlgorithm::ModeConfig::ctHi + * \brief The highest valid colour temperature of that mode + */ + +/** + * \var AwbAlgorithm::controls_ + * \brief Controls info map for the controls provided by the algorithm + */ + +/** + * \var AwbAlgorithm::modes_ + * \brief Map of all configured modes + * \sa AwbAlgorithm::parseModeConfigs + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h new file mode 100644 index 00000000..f4a86038 --- /dev/null +++ b/src/ipa/libipa/awb.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * Generic AWB algorithms + */ + +#pragma once + +#include <map> +#include <optional> + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> + +#include "libcamera/internal/vector.h" +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +namespace ipa { + +struct AwbResult { + RGB<double> gains; + double colourTemperature; +}; + +struct AwbStats { + virtual double computeColourError(const RGB<double> &gains) const = 0; + virtual RGB<double> rgbMeans() const = 0; + +protected: + ~AwbStats() = default; +}; + +class AwbAlgorithm +{ +public: + virtual ~AwbAlgorithm() = default; + + virtual int init(const YamlObject &tuningData) = 0; + virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; + virtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0; + + const ControlInfoMap::Map &controls() const + { + return controls_; + } + + virtual void handleControls([[maybe_unused]] const ControlList &controls) {} + +protected: + int parseModeConfigs(const YamlObject &tuningData, + const ControlValue &def = {}); + + struct ModeConfig { + double ctHi; + double ctLo; + }; + + ControlInfoMap::Map controls_; + std::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp new file mode 100644 index 00000000..d2bcbd83 --- /dev/null +++ b/src/ipa/libipa/awb_bayes.cpp @@ -0,0 +1,505 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * Copyright (C) 2024 Ideas on Board Oy + * + * Implementation of a bayesian AWB algorithm + */ + +#include "awb_bayes.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <map> +#include <ostream> +#include <vector> + +#include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + +#include "colours.h" + +/** + * \file awb_bayes.h + * \brief Implementation of bayesian auto white balance algorithm + * + * This implementation is based on the initial implementation done by + * RaspberryPi. + * + * \todo Documentation + * + * \todo Not all the features implemented by RaspberryPi were ported over to + * this algorithm because they either rely on hardware features not generally + * available or were considered not important enough at the moment. + * + * The following parts are not implemented: + * + * - min_pixels: minimum proportion of pixels counted within AWB region for it + * to be "useful" + * - min_g: minimum G value of those pixels, to be regarded a "useful" + * - min_regions: number of AWB regions that must be "useful" in order to do the + * AWB calculation + * - deltaLimit: clamp on colour error term (so as not to penalize non-grey + * excessively) + * - bias_proportion: The biasProportion parameter adds a small proportion of + * the counted pixels to a region biased to the biasCT colour temperature. + * A typical value for biasProportion would be between 0.05 to 0.1. + * - bias_ct: CT target for the search bias + * - sensitivityR: red sensitivity ratio (set to canonical sensor's R/G divided + * by this sensor's R/G) + * - sensitivityB: blue sensitivity ratio (set to canonical sensor's B/G divided + * by this sensor's B/G) + */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Awb) + +namespace { + +template<typename T> +class LimitsRecorder +{ +public: + LimitsRecorder() + : min_(std::numeric_limits<T>::max()), + max_(std::numeric_limits<T>::min()) + { + } + + void record(const T &value) + { + min_ = std::min(min_, value); + max_ = std::max(max_, value); + } + + const T &min() const { return min_; } + const T &max() const { return max_; } + +private: + T min_; + T max_; +}; + +#ifndef __DOXYGEN__ +template<typename T> +std::ostream &operator<<(std::ostream &out, const LimitsRecorder<T> &v) +{ + out << "[ " << v.min() << ", " << v.max() << " ]"; + return out; +} +#endif + +} /* namespace */ + +namespace ipa { + +/** + * \brief Step size control for CT search + */ +constexpr double kSearchStep = 0.2; + +/** + * \copydoc libcamera::ipa::Interpolator::interpolate() + */ +template<> +void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, double lambda) +{ + dest = Pwl::combine(a, b, + [=](double /*x*/, double y0, double y1) -> double { + return y0 * (1.0 - lambda) + y1 * lambda; + }); +} + +/** + * \class AwbBayes + * \brief Implementation of a bayesian auto white balance algorithm + * + * In a bayesian AWB algorithm the auto white balance estimation is improved by + * taking the likelihood of a given lightsource based on the estimated lux level + * into account. E.g. If it is very bright we can assume that we are outside and + * that colour temperatures around 6500 are preferred. + * + * The second part of this algorithm is the search for the most likely colour + * temperature. It is implemented in AwbBayes::coarseSearch() and in + * AwbBayes::fineSearch(). The search works very well without prior likelihoods + * and therefore the algorithm itself provides very good results even without + * prior likelihoods. + */ + +/** + * \var AwbBayes::transversePos_ + * \brief How far to wander off CT curve towards "more purple" + */ + +/** + * \var AwbBayes::transverseNeg_ + * \brief How far to wander off CT curve towards "more green" + */ + +/** + * \var AwbBayes::currentMode_ + * \brief The currently selected mode + */ + +int AwbBayes::init(const YamlObject &tuningData) +{ + int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains"); + if (ret) { + LOG(Awb, Error) + << "Failed to parse 'colourGains' " + << "parameter from tuning file"; + return ret; + } + + ctR_.clear(); + ctB_.clear(); + for (const auto &[ct, g] : colourGainCurve_.data()) { + ctR_.append(ct, 1.0 / g[0]); + ctB_.append(ct, 1.0 / g[1]); + } + + /* We will want the inverse functions of these too. */ + ctRInverse_ = ctR_.inverse().first; + ctBInverse_ = ctB_.inverse().first; + + ret = readPriors(tuningData); + if (ret) { + LOG(Awb, Error) << "Failed to read priors"; + return ret; + } + + ret = parseModeConfigs(tuningData, controls::AwbAuto); + if (ret) { + LOG(Awb, Error) + << "Failed to parse mode parameter from tuning file"; + return ret; + } + currentMode_ = &modes_[controls::AwbAuto]; + + transversePos_ = tuningData["transversePos"].get<double>(0.01); + transverseNeg_ = tuningData["transverseNeg"].get<double>(0.01); + if (transversePos_ <= 0 || transverseNeg_ <= 0) { + LOG(Awb, Error) << "AwbConfig: transversePos/Neg must be > 0"; + return -EINVAL; + } + + return 0; +} + +int AwbBayes::readPriors(const YamlObject &tuningData) +{ + const auto &priorsList = tuningData["priors"]; + std::map<uint32_t, Pwl> priors; + + if (!priorsList) { + LOG(Awb, Error) << "No priors specified"; + return -EINVAL; + } + + for (const auto &p : priorsList.asList()) { + if (!p.contains("lux")) { + LOG(Awb, Error) << "Missing lux value"; + return -EINVAL; + } + + uint32_t lux = p["lux"].get<uint32_t>(0); + if (priors.count(lux)) { + LOG(Awb, Error) << "Duplicate prior for lux value " << lux; + return -EINVAL; + } + + std::vector<uint32_t> temperatures = + p["ct"].getList<uint32_t>().value_or(std::vector<uint32_t>{}); + std::vector<double> probabilities = + p["probability"].getList<double>().value_or(std::vector<double>{}); + + if (temperatures.size() != probabilities.size()) { + LOG(Awb, Error) + << "Ct and probability array sizes are unequal"; + return -EINVAL; + } + + if (temperatures.empty()) { + LOG(Awb, Error) + << "Ct and probability arrays are empty"; + return -EINVAL; + } + + std::map<int, double> ctToProbability; + for (unsigned int i = 0; i < temperatures.size(); i++) { + int t = temperatures[i]; + if (ctToProbability.count(t)) { + LOG(Awb, Error) << "Duplicate ct value"; + return -EINVAL; + } + + ctToProbability[t] = probabilities[i]; + } + + auto &pwl = priors[lux]; + for (const auto &[ct, prob] : ctToProbability) { + if (prob < 1e-6) { + LOG(Awb, Error) << "Prior probability must be larger than 1e-6"; + return -EINVAL; + } + pwl.append(ct, prob); + } + } + + if (priors.empty()) { + LOG(Awb, Error) << "No priors specified"; + return -EINVAL; + } + + priors_.setData(std::move(priors)); + + return 0; +} + +void AwbBayes::handleControls(const ControlList &controls) +{ + auto mode = controls.get(controls::AwbMode); + if (mode) { + auto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode)); + if (it != modes_.end()) + currentMode_ = &it->second; + else + LOG(Awb, Error) << "Unsupported AWB mode " << *mode; + } +} + +std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature) +{ + /* + * \todo In the RaspberryPi code, the ct curve was interpolated in + * the white point space (1/x) not in gains space. This feels counter + * intuitive, as the gains are in linear space. But I can't prove it. + */ + const auto &gains = colourGainCurve_.getInterpolated(colourTemperature); + return RGB<double>{ { gains[0], 1.0, gains[1] } }; +} + +AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) +{ + ipa::Pwl prior; + if (lux > 0) { + prior = priors_.getInterpolated(lux); + prior.map([](double x, double y) { + LOG(Awb, Debug) << "(" << x << "," << y << ")"; + }); + } else { + prior.append(0, 1.0); + } + + double t = coarseSearch(prior, stats); + double r = ctR_.eval(t); + double b = ctB_.eval(t); + LOG(Awb, Debug) + << "After coarse search: r " << r << " b " << b << " (gains r " + << 1 / r << " b " << 1 / b << ")"; + + /* + * Original comment from RaspberryPi: + * Not entirely sure how to handle the fine search yet. Mostly the + * estimated CT is already good enough, but the fine search allows us to + * wander transversely off the CT curve. Under some illuminants, where + * there may be more or less green light, this may prove beneficial, + * though I probably need more real datasets before deciding exactly how + * this should be controlled and tuned. + */ + fineSearch(t, r, b, prior, stats); + LOG(Awb, Debug) + << "After fine search: r " << r << " b " << b << " (gains r " + << 1 / r << " b " << 1 / b << ")"; + + return { { { 1.0 / r, 1.0, 1.0 / b } }, t }; +} + +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const +{ + std::vector<Pwl::Point> points; + size_t bestPoint = 0; + double t = currentMode_->ctLo; + int spanR = -1; + int spanB = -1; + LimitsRecorder<double> errorLimits; + LimitsRecorder<double> priorLogLikelihoodLimits; + + /* Step down the CT curve evaluating log likelihood. */ + while (true) { + double r = ctR_.eval(t, &spanR); + double b = ctB_.eval(t, &spanB); + RGB<double> gains({ 1 / r, 1.0, 1 / b }); + double delta2Sum = stats.computeColourError(gains); + double priorLogLikelihood = log(prior.eval(prior.domain().clamp(t))); + double finalLogLikelihood = delta2Sum - priorLogLikelihood; + + errorLimits.record(delta2Sum); + priorLogLikelihoodLimits.record(priorLogLikelihood); + + points.push_back({ { t, finalLogLikelihood } }); + if (points.back().y() < points[bestPoint].y()) + bestPoint = points.size() - 1; + + if (t == currentMode_->ctHi) + break; + + /* + * Ensure even steps along the r/b curve by scaling them by the + * current t. + */ + t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi); + } + + t = points[bestPoint].x(); + LOG(Awb, Debug) << "Coarse search found CT " << t + << " error limits:" << errorLimits + << " prior log likelihood limits:" << priorLogLikelihoodLimits; + + /* + * We have the best point of the search, but refine it with a quadratic + * interpolation around its neighbors. + */ + if (points.size() > 2) { + bestPoint = std::clamp(bestPoint, std::size_t{ 1 }, points.size() - 2); + t = interpolateQuadratic(points[bestPoint - 1], + points[bestPoint], + points[bestPoint + 1]); + LOG(Awb, Debug) + << "After quadratic refinement, coarse search has CT " + << t; + } + + return t; +} + +void AwbBayes::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior, const AwbStats &stats) const +{ + int spanR = -1; + int spanB = -1; + double step = t / 10 * kSearchStep * 0.1; + int nsteps = 5; + ctR_.eval(t, &spanR); + ctB_.eval(t, &spanB); + double rDiff = ctR_.eval(t + nsteps * step, &spanR) - + ctR_.eval(t - nsteps * step, &spanR); + double bDiff = ctB_.eval(t + nsteps * step, &spanB) - + ctB_.eval(t - nsteps * step, &spanB); + Pwl::Point transverse({ bDiff, -rDiff }); + if (transverse.length2() < 1e-6) + return; + /* + * transverse is a unit vector orthogonal to the b vs. r function + * (pointing outwards with r and b increasing) + */ + transverse = transverse / transverse.length(); + double bestLogLikelihood = 0; + double bestT = 0; + Pwl::Point bestRB(0); + double transverseRange = transverseNeg_ + transversePos_; + const int maxNumDeltas = 12; + LimitsRecorder<double> errorLimits; + LimitsRecorder<double> priorLogLikelihoodLimits; + + + /* a transverse step approximately every 0.01 r/b units */ + int numDeltas = floor(transverseRange * 100 + 0.5) + 1; + numDeltas = std::clamp(numDeltas, 3, maxNumDeltas); + + /* + * Step down CT curve. March a bit further if the transverse range is + * large. + */ + nsteps += numDeltas; + for (int i = -nsteps; i <= nsteps; i++) { + double tTest = t + i * step; + double priorLogLikelihood = + log(prior.eval(prior.domain().clamp(tTest))); + priorLogLikelihoodLimits.record(priorLogLikelihood); + Pwl::Point rbStart{ { ctR_.eval(tTest, &spanR), + ctB_.eval(tTest, &spanB) } }; + Pwl::Point samples[maxNumDeltas]; + int bestPoint = 0; + + /* + * Sample numDeltas points transversely *off* the CT curve + * in the range [-transverseNeg, transversePos]. + * The x values of a sample contains the distance and the y + * value contains the corresponding log likelihood. + */ + double transverseStep = transverseRange / (numDeltas - 1); + for (int j = 0; j < numDeltas; j++) { + auto &p = samples[j]; + p.x() = -transverseNeg_ + transverseStep * j; + Pwl::Point rbTest = rbStart + transverse * p.x(); + RGB<double> gains({ 1 / rbTest[0], 1.0, 1 / rbTest[1] }); + double delta2Sum = stats.computeColourError(gains); + errorLimits.record(delta2Sum); + p.y() = delta2Sum - priorLogLikelihood; + + if (p.y() < samples[bestPoint].y()) + bestPoint = j; + } + + /* + * We have all samples transversely across the CT curve, + * now let's do a quadratic interpolation for the best result. + */ + bestPoint = std::clamp(bestPoint, 1, numDeltas - 2); + double bestOffset = interpolateQuadratic(samples[bestPoint - 1], + samples[bestPoint], + samples[bestPoint + 1]); + Pwl::Point rbTest = rbStart + transverse * bestOffset; + RGB<double> gains({ 1 / rbTest[0], 1.0, 1 / rbTest[1] }); + double delta2Sum = stats.computeColourError(gains); + errorLimits.record(delta2Sum); + double finalLogLikelihood = delta2Sum - priorLogLikelihood; + + if (bestT == 0 || finalLogLikelihood < bestLogLikelihood) { + bestLogLikelihood = finalLogLikelihood; + bestT = tTest; + bestRB = rbTest; + } + } + + t = bestT; + r = bestRB[0]; + b = bestRB[1]; + LOG(Awb, Debug) + << "Fine search found t " << t << " r " << r << " b " << b + << " error limits: " << errorLimits + << " prior log likelihood limits: " << priorLogLikelihoodLimits; +} + +/** + * \brief Find extremum of function + * \param[in] a First point + * \param[in] b Second point + * \param[in] c Third point + * + * Given 3 points on a curve, find the extremum of the function in that interval + * by fitting a quadratic. + * + * \return The x value of the extremum clamped to the interval [a.x(), c.x()] + */ +double AwbBayes::interpolateQuadratic(Pwl::Point const &a, Pwl::Point const &b, + Pwl::Point const &c) const +{ + const double eps = 1e-3; + Pwl::Point ca = c - a; + Pwl::Point ba = b - a; + double denominator = 2 * (ba.y() * ca.x() - ca.y() * ba.x()); + if (std::abs(denominator) > eps) { + double numerator = ba.y() * ca.x() * ca.x() - ca.y() * ba.x() * ba.x(); + double result = numerator / denominator + a.x(); + return std::max(a.x(), std::min(c.x(), result)); + } + /* has degenerated to straight line segment */ + return a.y() < c.y() - eps ? a.x() : (c.y() < a.y() - eps ? c.x() : b.x()); +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h new file mode 100644 index 00000000..47ef3cce --- /dev/null +++ b/src/ipa/libipa/awb_bayes.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * Base class for bayes AWB algorithms + */ + +#pragma once + +#include <libcamera/controls.h> + +#include "libcamera/internal/vector.h" +#include "libcamera/internal/yaml_parser.h" + +#include "awb.h" +#include "interpolator.h" +#include "pwl.h" + +namespace libcamera { + +namespace ipa { + +class AwbBayes : public AwbAlgorithm +{ +public: + AwbBayes() = default; + + int init(const YamlObject &tuningData) override; + AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + std::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override; + void handleControls(const ControlList &controls) override; + +private: + int readPriors(const YamlObject &tuningData); + + void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior, + const AwbStats &stats) const; + double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const; + double interpolateQuadratic(ipa::Pwl::Point const &a, + ipa::Pwl::Point const &b, + ipa::Pwl::Point const &c) const; + + Interpolator<Pwl> priors_; + Interpolator<Vector<double, 2>> colourGainCurve_; + + ipa::Pwl ctR_; + ipa::Pwl ctB_; + ipa::Pwl ctRInverse_; + ipa::Pwl ctBInverse_; + + double transversePos_; + double transverseNeg_; + + ModeConfig *currentMode_ = nullptr; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp new file mode 100644 index 00000000..d252edb2 --- /dev/null +++ b/src/ipa/libipa/awb_grey.cpp @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * Implementation of grey world AWB algorithm + */ + +#include "awb_grey.h" + +#include <algorithm> + +#include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + +#include "colours.h" + +using namespace libcamera::controls; + +/** + * \file awb_grey.h + * \brief Implementation of a grey world AWB algorithm + */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Awb) +namespace ipa { + +/** + * \class AwbGrey + * \brief A Grey world auto white balance algorithm + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data for the algorithm + * + * Load the colour temperature curve from the tuning data. If there is no tuning + * data available, continue with a warning. Manual colour temperature will not + * work in that case. + * + * \return 0 on success, a negative error code otherwise + */ +int AwbGrey::init(const YamlObject &tuningData) +{ + Interpolator<Vector<double, 2>> gains; + int ret = gains.readYaml(tuningData["colourGains"], "ct", "gains"); + if (ret < 0) + LOG(Awb, Warning) + << "Failed to parse 'colourGains' " + << "parameter from tuning file; " + << "manual colour temperature will not work properly"; + else + colourGainCurve_ = gains; + + return 0; +} + +/** + * \brief Calculate AWB data from the given statistics + * \param[in] stats The statistics to use for the calculation + * \param[in] lux The lux value of the scene + * + * The colour temperature is estimated based on the colours::estimateCCT() + * function. The gains are calculated purely based on the RGB means provided by + * the \a stats. The colour temperature is not taken into account when + * calculating the gains. + * + * The \a lux parameter is not used in this algorithm. + * + * \return The AWB result + */ +AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux) +{ + AwbResult result; + auto means = stats.rgbMeans(); + result.colourTemperature = estimateCCT(means); + + /* + * Estimate the red and blue gains to apply in a grey world. The green + * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the + * divisor to a minimum value of 1.0. + */ + result.gains.r() = means.g() / std::max(means.r(), 1.0); + result.gains.g() = 1.0; + result.gains.b() = means.g() / std::max(means.b(), 1.0); + return result; +} + +/** + * \brief Compute white balance gains from a colour temperature + * \param[in] colourTemperature The colour temperature in Kelvin + * + * Compute the white balance gains from a \a colourTemperature. This function + * does not take any statistics into account. It simply interpolates the colour + * gains configured in the colour temperature curve. + * + * \return The colour gains if a colour temperature curve is available, + * [1, 1, 1] otherwise. + */ +std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature) +{ + if (!colourGainCurve_) { + LOG(Awb, Error) << "No gains defined"; + return std::nullopt; + } + + auto gains = colourGainCurve_->getInterpolated(colourTemperature); + return RGB<double>{ { gains[0], 1.0, gains[1] } }; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h new file mode 100644 index 00000000..f82a368d --- /dev/null +++ b/src/ipa/libipa/awb_grey.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Ideas on Board Oy + * + * AWB grey world algorithm + */ + +#pragma once + +#include <optional> + +#include "libcamera/internal/vector.h" + +#include "awb.h" +#include "interpolator.h" + +namespace libcamera { + +namespace ipa { + +class AwbGrey : public AwbAlgorithm +{ +public: + AwbGrey() = default; + + int init(const YamlObject &tuningData) override; + AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override; + +private: + std::optional<Interpolator<Vector<double, 2>>> colourGainCurve_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp index f0ecc383..7c66cd57 100644 --- a/src/ipa/libipa/camera_sensor_helper.cpp +++ b/src/ipa/libipa/camera_sensor_helper.cpp @@ -2,12 +2,13 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_sensor_helper.cpp - Helper class that performs sensor-specific + * Helper class that performs sensor-specific * parameter computations */ #include "camera_sensor_helper.h" #include <cmath> +#include <limits> #include <libcamera/base/log.h> @@ -40,6 +41,7 @@ namespace ipa { */ /** + * \fn CameraSensorHelper::CameraSensorHelper() * \brief Construct a CameraSensorHelper instance * * CameraSensorHelper derived class instances shall never be constructed @@ -48,6 +50,33 @@ namespace ipa { */ /** + * \fn CameraSensorHelper::blackLevel() + * \brief Fetch the black level of the sensor + * + * This function returns the black level of the sensor scaled to a 16bit pixel + * width. If it is unknown an empty optional is returned. + * + * \todo Fill the blanks and add pedestal values for all supported sensors. Once + * done, drop the std::optional<>. + * + * Black levels are typically the result of the following phenomena: + * - Pedestal added by the sensor to pixel values. They are typically fixed, + * sometimes programmable and should be reported in datasheets (but + * documentation is not always available). + * - Dark currents and other physical effects that add charge to pixels in the + * absence of light. Those can depend on the integration time and the sensor + * die temperature, and their contribution to pixel values depend on the + * sensor gains. + * + * The pedestal is usually the value with the biggest contribution to the + * overall black level. In most cases it is either known before or in rare cases + * (there is not a single driver with such a control in the linux kernel) can be + * queried from the sensor. This function provides that fixed, known value. + * + * \return The black level of the sensor, or std::nullopt if not known + */ + +/** * \brief Compute gain code from the analogue gain absolute value * \param[in] gain The real gain to pass * @@ -58,21 +87,16 @@ namespace ipa { */ uint32_t CameraSensorHelper::gainCode(double gain) const { - const AnalogueGainConstants &k = gainConstants_; - - switch (gainType_) { - case AnalogueGainLinear: - ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0); - - return (k.linear.c0 - k.linear.c1 * gain) / - (k.linear.m1 * gain - k.linear.m0); + if (auto *l = std::get_if<AnalogueGainLinear>(&gain_)) { + ASSERT(l->m0 == 0 || l->m1 == 0); - case AnalogueGainExponential: - ASSERT(k.exp.a != 0 && k.exp.m != 0); + return (l->c0 - l->c1 * gain) / + (l->m1 * gain - l->m0); + } else if (auto *e = std::get_if<AnalogueGainExp>(&gain_)) { + ASSERT(e->a != 0 && e->m != 0); - return std::log2(gain / k.exp.a) / k.exp.m; - - default: + return std::log2(gain / e->a) / e->m; + } else { ASSERT(false); return 0; } @@ -90,38 +114,26 @@ uint32_t CameraSensorHelper::gainCode(double gain) const */ double CameraSensorHelper::gain(uint32_t gainCode) const { - const AnalogueGainConstants &k = gainConstants_; double gain = static_cast<double>(gainCode); - switch (gainType_) { - case AnalogueGainLinear: - ASSERT(k.linear.m0 == 0 || k.linear.m1 == 0); - - return (k.linear.m0 * gain + k.linear.c0) / - (k.linear.m1 * gain + k.linear.c1); + if (auto *l = std::get_if<AnalogueGainLinear>(&gain_)) { + ASSERT(l->m0 == 0 || l->m1 == 0); - case AnalogueGainExponential: - ASSERT(k.exp.a != 0 && k.exp.m != 0); + return (l->m0 * gain + l->c0) / + (l->m1 * gain + l->c1); + } else if (auto *e = std::get_if<AnalogueGainExp>(&gain_)) { + ASSERT(e->a != 0 && e->m != 0); - return k.exp.a * std::exp2(k.exp.m * gain); - - default: + return e->a * std::exp2(e->m * gain); + } else { ASSERT(false); return 0.0; } } /** - * \enum CameraSensorHelper::AnalogueGainType - * \brief The gain calculation modes as defined by the MIPI CCS - * - * Describes the image sensor analogue gain capabilities. - * Two modes are possible, depending on the sensor: Linear and Exponential. - */ - -/** - * \var CameraSensorHelper::AnalogueGainLinear - * \brief Gain is computed using linear gain estimation + * \struct CameraSensorHelper::AnalogueGainLinear + * \brief Analogue gain constants for the linear gain model * * The relationship between the integer gain parameter and the resulting gain * multiplier is given by the following equation: @@ -136,11 +148,27 @@ double CameraSensorHelper::gain(uint32_t gainCode) const * The full Gain equation therefore reduces to either: * * \f$gain=\frac{c0}{m1x+c1}\f$ or \f$\frac{m0x+c0}{c1}\f$ + * + * \var CameraSensorHelper::AnalogueGainLinear::m0 + * \brief Constant used in the linear gain coding/decoding + * + * \note Either m0 or m1 shall be zero. + * + * \var CameraSensorHelper::AnalogueGainLinear::c0 + * \brief Constant used in the linear gain coding/decoding + * + * \var CameraSensorHelper::AnalogueGainLinear::m1 + * \brief Constant used in the linear gain coding/decoding + * + * \note Either m0 or m1 shall be zero. + * + * \var CameraSensorHelper::AnalogueGainLinear::c1 + * \brief Constant used in the linear gain coding/decoding */ /** - * \var CameraSensorHelper::AnalogueGainExponential - * \brief Gain is expressed using an exponential model + * \struct CameraSensorHelper::AnalogueGainExp + * \brief Analogue gain constants for the exponential gain model * * The relationship between the integer gain parameter and the resulting gain * multiplier is given by the following equation: @@ -156,61 +184,22 @@ double CameraSensorHelper::gain(uint32_t gainCode) const * * When the gain is expressed in dB, 'a' is equal to 1 and 'm' to * \f$log_{2}{10^{\frac{1}{20}}}\f$. - */ - -/** - * \struct CameraSensorHelper::AnalogueGainLinearConstants - * \brief Analogue gain constants for the linear gain model - * - * \var CameraSensorHelper::AnalogueGainLinearConstants::m0 - * \brief Constant used in the linear gain coding/decoding * - * \note Either m0 or m1 shall be zero. - * - * \var CameraSensorHelper::AnalogueGainLinearConstants::c0 - * \brief Constant used in the linear gain coding/decoding - * - * \var CameraSensorHelper::AnalogueGainLinearConstants::m1 - * \brief Constant used in the linear gain coding/decoding - * - * \note Either m0 or m1 shall be zero. - * - * \var CameraSensorHelper::AnalogueGainLinearConstants::c1 - * \brief Constant used in the linear gain coding/decoding - */ - -/** - * \struct CameraSensorHelper::AnalogueGainExpConstants - * \brief Analogue gain constants for the exponential gain model - * - * \var CameraSensorHelper::AnalogueGainExpConstants::a + * \var CameraSensorHelper::AnalogueGainExp::a * \brief Constant used in the exponential gain coding/decoding * - * \var CameraSensorHelper::AnalogueGainExpConstants::m + * \var CameraSensorHelper::AnalogueGainExp::m * \brief Constant used in the exponential gain coding/decoding */ /** - * \struct CameraSensorHelper::AnalogueGainConstants - * \brief Analogue gain model constants - * - * This union stores the constants used to calculate the analogue gain. The - * CameraSensorHelper::gainType_ variable selects which union member is valid. - * - * \var CameraSensorHelper::AnalogueGainConstants::linear - * \brief Constants for the linear gain model - * - * \var CameraSensorHelper::AnalogueGainConstants::exp - * \brief Constants for the exponential gain model - */ - -/** - * \var CameraSensorHelper::gainType_ - * \brief The analogue gain model type + * \var CameraSensorHelper::blackLevel_ + * \brief The black level of the sensor + * \sa CameraSensorHelper::blackLevel() */ /** - * \var CameraSensorHelper::gainConstants_ + * \var CameraSensorHelper::gain_ * \brief The analogue gain parameters used for calculation * * The analogue gain is calculated through a formula, and its parameters are @@ -366,42 +355,168 @@ static constexpr double expGainDb(double step) return log2_10 * step / 20; } -class CameraSensorHelperAr0521 : public CameraSensorHelper +class CameraSensorHelperAr0144 : public CameraSensorHelper { public: - uint32_t gainCode(double gain) const override; - double gain(uint32_t gainCode) const override; + CameraSensorHelperAr0144() + { + /* Power-on default value: 168 at 12bits. */ + blackLevel_ = 2688; + } + + uint32_t gainCode(double gain) const override + { + /* The recommended minimum gain is 1.6842 to avoid artifacts. */ + gain = std::clamp(gain, 1.0 / (1.0 - 13.0 / 32.0), 18.45); + + /* + * The analogue gain is made of a coarse exponential gain in + * the range [2^0, 2^4] and a fine inversely linear gain in the + * range [1.0, 2.0[. There is an additional fixed 1.153125 + * multiplier when the coarse gain reaches 2^2. + */ + + if (gain > 4.0) + gain /= 1.153125; + + unsigned int coarse = std::log2(gain); + unsigned int fine = (1 - (1 << coarse) / gain) * 32; + + /* The fine gain rounding depends on the coarse gain. */ + if (coarse == 1 || coarse == 3) + fine &= ~1; + else if (coarse == 4) + fine &= ~3; + + return (coarse << 4) | (fine & 0xf); + } + + double gain(uint32_t gainCode) const override + { + unsigned int coarse = gainCode >> 4; + unsigned int fine = gainCode & 0xf; + unsigned int d1; + double d2, m; + + switch (coarse) { + default: + case 0: + d1 = 1; + d2 = 32.0; + m = 1.0; + break; + case 1: + d1 = 2; + d2 = 16.0; + m = 1.0; + break; + case 2: + d1 = 1; + d2 = 32.0; + m = 1.153125; + break; + case 3: + d1 = 2; + d2 = 16.0; + m = 1.153125; + break; + case 4: + d1 = 4; + d2 = 8.0; + m = 1.153125; + break; + } + + /* + * With infinite precision, the calculated gain would be exact, + * and the reverse conversion with gainCode() would produce the + * same gain code. In the real world, rounding errors may cause + * the calculated gain to be lower by an amount negligible for + * all purposes, except for the reverse conversion. Converting + * the gain to a gain code could then return the quantized value + * just lower than the original gain code. To avoid this, tests + * showed that adding the machine epsilon to the multiplier m is + * sufficient. + */ + m += std::numeric_limits<decltype(m)>::epsilon(); + + return m * (1 << coarse) / (1.0 - (fine / d1) / d2); + } private: static constexpr double kStep_ = 16; }; +REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144) -uint32_t CameraSensorHelperAr0521::gainCode(double gain) const +class CameraSensorHelperAr0521 : public CameraSensorHelper { - gain = std::clamp(gain, 1.0, 15.5); - unsigned int coarse = std::log2(gain); - unsigned int fine = (gain / (1 << coarse) - 1) * kStep_; +public: + uint32_t gainCode(double gain) const override + { + gain = std::clamp(gain, 1.0, 15.5); + unsigned int coarse = std::log2(gain); + unsigned int fine = (gain / (1 << coarse) - 1) * kStep_; - return (coarse << 4) | (fine & 0xf); -} + return (coarse << 4) | (fine & 0xf); + } -double CameraSensorHelperAr0521::gain(uint32_t gainCode) const -{ - unsigned int coarse = gainCode >> 4; - unsigned int fine = gainCode & 0xf; + double gain(uint32_t gainCode) const override + { + unsigned int coarse = gainCode >> 4; + unsigned int fine = gainCode & 0xf; - return (1 << coarse) * (1 + fine / kStep_); -} + return (1 << coarse) * (1 + fine / kStep_); + } +private: + static constexpr double kStep_ = 16; +}; REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521) +class CameraSensorHelperGc05a2 : public CameraSensorHelper +{ +public: + CameraSensorHelperGc05a2() + { + /* From datasheet: 64 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("gc05a2", CameraSensorHelperGc05a2) + +class CameraSensorHelperGc08a3 : public CameraSensorHelper +{ +public: + CameraSensorHelperGc08a3() + { + /* From datasheet: 64 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("gc08a3", CameraSensorHelperGc08a3) + +class CameraSensorHelperImx214 : public CameraSensorHelper +{ +public: + CameraSensorHelperImx214() + { + /* From datasheet: 64 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214) + class CameraSensorHelperImx219 : public CameraSensorHelper { public: CameraSensorHelperImx219() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 0, 256, -1, 256 }; + /* From datasheet: 64 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 0, 256, -1, 256 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219) @@ -411,19 +526,33 @@ class CameraSensorHelperImx258 : public CameraSensorHelper public: CameraSensorHelperImx258() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 0, 512, -1, 512 }; + /* From datasheet: 0x40 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258) +class CameraSensorHelperImx283 : public CameraSensorHelper +{ +public: + CameraSensorHelperImx283() + { + /* From datasheet: 0x32 at 10bits. */ + blackLevel_ = 3200; + gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("imx283", CameraSensorHelperImx283) + class CameraSensorHelperImx290 : public CameraSensorHelper { public: CameraSensorHelperImx290() { - gainType_ = AnalogueGainExponential; - gainConstants_.exp = { 1.0, expGainDb(0.3) }; + /* From datasheet: 0xf0 at 12bits. */ + blackLevel_ = 3840; + gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290) @@ -433,8 +562,7 @@ class CameraSensorHelperImx296 : public CameraSensorHelper public: CameraSensorHelperImx296() { - gainType_ = AnalogueGainExponential; - gainConstants_.exp = { 1.0, expGainDb(0.1) }; + gain_ = AnalogueGainExp{ 1.0, expGainDb(0.1) }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296) @@ -444,13 +572,39 @@ class CameraSensorHelperImx327 : public CameraSensorHelperImx290 }; REGISTER_CAMERA_SENSOR_HELPER("imx327", CameraSensorHelperImx327) +class CameraSensorHelperImx335 : public CameraSensorHelper +{ +public: + CameraSensorHelperImx335() + { + /* From datasheet: 0x32 at 10bits. */ + blackLevel_ = 3200; + gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("imx335", CameraSensorHelperImx335) + +class CameraSensorHelperImx415 : public CameraSensorHelper +{ +public: + CameraSensorHelperImx415() + { + gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("imx415", CameraSensorHelperImx415) + +class CameraSensorHelperImx462 : public CameraSensorHelperImx290 +{ +}; +REGISTER_CAMERA_SENSOR_HELPER("imx462", CameraSensorHelperImx462) + class CameraSensorHelperImx477 : public CameraSensorHelper { public: CameraSensorHelperImx477() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 0, 1024, -1, 1024 }; + gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; } }; REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477) @@ -464,8 +618,7 @@ public: * The Sensor Manual doesn't appear to document the gain model. * This has been validated with some empirical testing only. */ - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685) @@ -475,8 +628,7 @@ class CameraSensorHelperOv2740 : public CameraSensorHelper public: CameraSensorHelperOv2740() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740) @@ -486,8 +638,9 @@ class CameraSensorHelperOv4689 : public CameraSensorHelper public: CameraSensorHelperOv4689() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + /* From datasheet: 0x40 at 12bits. */ + blackLevel_ = 1024; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689) @@ -497,8 +650,9 @@ class CameraSensorHelperOv5640 : public CameraSensorHelper public: CameraSensorHelperOv5640() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 16 }; + /* From datasheet: 0x10 at 10bits. */ + blackLevel_ = 1024; + gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640) @@ -508,8 +662,7 @@ class CameraSensorHelperOv5647 : public CameraSensorHelper public: CameraSensorHelperOv5647() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 16 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647) @@ -519,8 +672,7 @@ class CameraSensorHelperOv5670 : public CameraSensorHelper public: CameraSensorHelperOv5670() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670) @@ -530,8 +682,9 @@ class CameraSensorHelperOv5675 : public CameraSensorHelper public: CameraSensorHelperOv5675() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + /* From Linux kernel driver: 0x40 at 10bits. */ + blackLevel_ = 4096; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675) @@ -541,26 +694,33 @@ class CameraSensorHelperOv5693 : public CameraSensorHelper public: CameraSensorHelperOv5693() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 16 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693) +class CameraSensorHelperOv64a40 : public CameraSensorHelper +{ +public: + CameraSensorHelperOv64a40() + { + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + } +}; +REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40) + class CameraSensorHelperOv8858 : public CameraSensorHelper { public: CameraSensorHelperOv8858() { - gainType_ = AnalogueGainLinear; - /* * \todo Validate the selected 1/128 step value as it differs * from what the sensor manual describes. * * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267 */ - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858) @@ -570,8 +730,7 @@ class CameraSensorHelperOv8865 : public CameraSensorHelper public: CameraSensorHelperOv8865() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865) @@ -581,8 +740,7 @@ class CameraSensorHelperOv13858 : public CameraSensorHelper public: CameraSensorHelperOv13858() { - gainType_ = AnalogueGainLinear; - gainConstants_.linear = { 1, 0, 0, 128 }; + gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; } }; REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858) diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h index 3ea1806c..a9300a64 100644 --- a/src/ipa/libipa/camera_sensor_helper.h +++ b/src/ipa/libipa/camera_sensor_helper.h @@ -2,15 +2,16 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_sensor_helper.h - Helper class that performs sensor-specific parameter computations + * Helper class that performs sensor-specific parameter computations */ #pragma once -#include <stdint.h> - #include <memory> +#include <optional> +#include <stdint.h> #include <string> +#include <variant> #include <vector> #include <libcamera/base/class.h> @@ -25,34 +26,25 @@ public: CameraSensorHelper() = default; virtual ~CameraSensorHelper() = default; + std::optional<int16_t> blackLevel() const { return blackLevel_; } virtual uint32_t gainCode(double gain) const; virtual double gain(uint32_t gainCode) const; protected: - enum AnalogueGainType { - AnalogueGainLinear, - AnalogueGainExponential, - }; - - struct AnalogueGainLinearConstants { + struct AnalogueGainLinear { int16_t m0; int16_t c0; int16_t m1; int16_t c1; }; - struct AnalogueGainExpConstants { + struct AnalogueGainExp { double a; double m; }; - union AnalogueGainConstants { - AnalogueGainLinearConstants linear; - AnalogueGainExpConstants exp; - }; - - AnalogueGainType gainType_; - AnalogueGainConstants gainConstants_; + std::optional<int16_t> blackLevel_; + std::variant<std::monostate, AnalogueGainLinear, AnalogueGainExp> gain_; private: LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper) @@ -88,7 +80,7 @@ public: } private: - std::unique_ptr<CameraSensorHelper> createInstance() const + std::unique_ptr<CameraSensorHelper> createInstance() const override { return std::make_unique<_Helper>(); } diff --git a/src/ipa/libipa/colours.cpp b/src/ipa/libipa/colours.cpp new file mode 100644 index 00000000..97124cf4 --- /dev/null +++ b/src/ipa/libipa/colours.cpp @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * libipa miscellaneous colour helpers + */ + +#include "colours.h" + +#include <algorithm> +#include <cmath> + +namespace libcamera { + +namespace ipa { + +/** + * \file colours.h + * \brief Functions to reduce code duplication between IPA modules + */ + +/** + * \brief Estimate luminance from RGB values following ITU-R BT.601 + * \param[in] rgb The RGB value + * + * This function estimates a luminance value from a triplet of Red, Green and + * Blue values, following the formula defined by ITU-R Recommendation BT.601-7 + * which can be found at https://www.itu.int/rec/R-REC-BT.601 + * + * \return The estimated luminance value + */ +double rec601LuminanceFromRGB(const RGB<double> &rgb) +{ + static const Vector<double, 3> rgb2y{{ + 0.299, 0.587, 0.114 + }}; + + return rgb.dot(rgb2y); +} + +/** + * \brief Estimate correlated colour temperature from RGB color space input + * \param[in] rgb The RGB value + * + * This function estimates the correlated color temperature RGB color space + * input. In physics and color science, the Planckian locus or black body locus + * is the path or locus that the color of an incandescent black body would take + * in a particular chromaticity space as the black body temperature changes. + * + * If a narrow range of color temperatures is considered (those encapsulating + * daylight being the most practical case) one can approximate the Planckian + * locus in order to calculate the CCT in terms of chromaticity coordinates. + * + * More detailed information can be found in: + * https://en.wikipedia.org/wiki/Color_temperature#Approximation + * + * \return The estimated color temperature + */ +uint32_t estimateCCT(const RGB<double> &rgb) +{ + /* + * Convert the RGB values to CIE tristimulus values (XYZ) and divide by + * the sum of X, Y and Z to calculate the CIE xy chromaticity. + */ + static const Matrix<double, 3, 3> rgb2xyz({ + -0.14282, 1.54924, -0.95641, + -0.32466, 1.57837, -0.73191, + -0.68202, 0.77073, 0.56332 + }); + + Vector<double, 3> xyz = rgb2xyz * rgb; + xyz /= xyz.sum(); + + /* Calculate CCT */ + double n = (xyz.x() - 0.3320) / (0.1858 - xyz.y()); + return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/colours.h b/src/ipa/libipa/colours.h new file mode 100644 index 00000000..d39b2ca8 --- /dev/null +++ b/src/ipa/libipa/colours.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * libipa miscellaneous colour helpers + */ + +#pragma once + +#include <stdint.h> + +#include "libcamera/internal/vector.h" + +namespace libcamera { + +namespace ipa { + +double rec601LuminanceFromRGB(const RGB<double> &rgb); +uint32_t estimateCCT(const RGB<double> &rgb); + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp new file mode 100644 index 00000000..0c1e99e3 --- /dev/null +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class that performs computations relating to exposure + */ +#include "exposure_mode_helper.h" + +#include <algorithm> + +#include <libcamera/base/log.h> + +/** + * \file exposure_mode_helper.h + * \brief Helper class that performs computations relating to exposure + * + * AEGC algorithms have a need to split exposure between exposure time, analogue + * and digital gain. Multiple implementations do so based on paired stages of + * exposure time and gain limits; provide a helper to avoid duplicating the code. + */ + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +LOG_DEFINE_CATEGORY(ExposureModeHelper) + +namespace ipa { + +/** + * \class ExposureModeHelper + * \brief Class for splitting exposure into exposure time and total gain + * + * The ExposureModeHelper class provides a standard interface through which an + * AEGC algorithm can divide exposure between exposure time and gain. It is + * configured with a set of exposure time and gain pairs and works by initially + * fixing gain at 1.0 and increasing exposure time up to the exposure time value + * from the first pair in the set in an attempt to meet the required exposure + * value. + * + * If the required exposure is not achievable by the first exposure time value + * alone it ramps gain up to the value from the first pair in the set. If the + * required exposure is still not met it then allows exposure time to ramp up to + * the exposure time value from the second pair in the set, and continues in this + * vein until either the required exposure time is met, or else the hardware's + * exposure time or gain limits are reached. + * + * This method allows users to strike a balance between a well-exposed image and + * an acceptable frame-rate, as opposed to simply maximising exposure time + * followed by gain. The same helpers can be used to perform the latter + * operation if needed by passing an empty set of pairs to the initialisation + * function. + * + * The gain values may exceed a camera sensor's analogue gain limits if either + * it or the IPA is also capable of digital gain. The configure() function must + * be called with the hardware's limits to inform the helper of those + * constraints. Any gain that is needed will be applied as analogue gain first + * until the hardware's limit is reached, following which digital gain will be + * used. + */ + +/** + * \brief Construct an ExposureModeHelper instance + * \param[in] stages The vector of paired exposure time and gain limits + * + * The input stages are exposure time and _total_ gain pairs; the gain + * encompasses both analogue and digital gain. + * + * The vector of stages may be empty. In that case, the helper will simply use + * the runtime limits set through setLimits() instead. + */ +ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages) +{ + minExposureTime_ = 0us; + maxExposureTime_ = 0us; + minGain_ = 0; + maxGain_ = 0; + + for (const auto &[s, g] : stages) { + exposureTimes_.push_back(s); + gains_.push_back(g); + } +} + +/** + * \brief Set the exposure time and gain limits + * \param[in] minExposureTime The minimum exposure time supported + * \param[in] maxExposureTime The maximum exposure time supported + * \param[in] minGain The minimum analogue gain supported + * \param[in] maxGain The maximum analogue gain supported + * + * This function configures the exposure time and analogue gain limits that need + * to be adhered to as the helper divides up exposure. Note that this function + * *must* be called whenever those limits change and before splitExposure() is + * used. + * + * If the algorithm using the helpers needs to indicate that either exposure time + * or analogue gain or both should be fixed it can do so by setting both the + * minima and maxima to the same value. + */ +void ExposureModeHelper::setLimits(utils::Duration minExposureTime, + utils::Duration maxExposureTime, + double minGain, double maxGain) +{ + minExposureTime_ = minExposureTime; + maxExposureTime_ = maxExposureTime; + minGain_ = minGain; + maxGain_ = maxGain; +} + +utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime) const +{ + return std::clamp(exposureTime, minExposureTime_, maxExposureTime_); +} + +double ExposureModeHelper::clampGain(double gain) const +{ + return std::clamp(gain, minGain_, maxGain_); +} + +/** + * \brief Split exposure into exposure time and gain + * \param[in] exposure Exposure value + * + * This function divides a given exposure into exposure time, analogue and + * digital gain by iterating through stages of exposure time and gain limits. + * At each stage the current stage's exposure time limit is multiplied by the + * previous stage's gain limit (or 1.0 initially) to see if the combination of + * the two can meet the required exposure. If they cannot then the current + * stage's exposure time limit is multiplied by the same stage's gain limit to + * see if that combination can meet the required exposure time. If they cannot + * then the function moves to consider the next stage. + * + * When a combination of exposure time and gain _stage_ limits are found that + * are sufficient to meet the required exposure, the function attempts to reduce + * exposure time as much as possible whilst fixing gain and still meeting the + * exposure. If a _runtime_ limit prevents exposure time from being lowered + * enough to meet the exposure with gain fixed at the stage limit, gain is also + * lowered to compensate. + * + * Once the exposure time and gain values are ascertained, gain is assigned as + * analogue gain as much as possible, with digital gain only in use if the + * maximum analogue gain runtime limit is unable to accommodate the exposure + * value. + * + * If no combination of exposure time and gain limits is found that meets the + * required exposure, the helper falls-back to simply maximising the exposure + * time first, followed by analogue gain, followed by digital gain. + * + * \return Tuple of exposure time, analogue gain, and digital gain + */ +std::tuple<utils::Duration, double, double> +ExposureModeHelper::splitExposure(utils::Duration exposure) const +{ + ASSERT(maxExposureTime_); + ASSERT(maxGain_); + + bool gainFixed = minGain_ == maxGain_; + bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; + + /* + * There's no point entering the loop if we cannot change either gain + * nor exposure time anyway. + */ + if (exposureTimeFixed && gainFixed) + return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) }; + + utils::Duration exposureTime; + double stageGain = 1.0; + double gain; + + for (unsigned int stage = 0; stage < gains_.size(); stage++) { + double lastStageGain = stage == 0 ? 1.0 : clampGain(gains_[stage - 1]); + utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]); + stageGain = clampGain(gains_[stage]); + + /* + * We perform the clamping on both exposure time and gain in + * case the helper has had limits set that prevent those values + * being lowered beyond a certain minimum...this can happen at + * runtime for various reasons and so would not be known when + * the stage limits are initialised. + */ + + /* Clamp the gain to lastStageGain and regulate exposureTime. */ + if (stageExposureTime * lastStageGain >= exposure) { + exposureTime = clampExposureTime(exposure / clampGain(lastStageGain)); + gain = clampGain(exposure / exposureTime); + + return { exposureTime, gain, exposure / (exposureTime * gain) }; + } + + /* Clamp the exposureTime to stageExposureTime and regulate gain. */ + if (stageExposureTime * stageGain >= exposure) { + exposureTime = clampExposureTime(stageExposureTime); + gain = clampGain(exposure / exposureTime); + + return { exposureTime, gain, exposure / (exposureTime * gain) }; + } + } + + /* + * From here on all we can do is max out the exposure time, followed by + * the analogue gain. If we still haven't achieved the target we send + * the rest of the exposure time to digital gain. If we were given no + * stages to use then the default stageGain of 1.0 is used so that + * exposure time is maxed before gain is touched at all. + */ + exposureTime = clampExposureTime(exposure / clampGain(stageGain)); + gain = clampGain(exposure / exposureTime); + + return { exposureTime, gain, exposure / (exposureTime * gain) }; +} + +/** + * \fn ExposureModeHelper::minExposureTime() + * \brief Retrieve the configured minimum exposure time limit set through + * setLimits() + * \return The minExposureTime_ value + */ + +/** + * \fn ExposureModeHelper::maxExposureTime() + * \brief Retrieve the configured maximum exposure time set through setLimits() + * \return The maxExposureTime_ value + */ + +/** + * \fn ExposureModeHelper::minGain() + * \brief Retrieve the configured minimum gain set through setLimits() + * \return The minGain_ value + */ + +/** + * \fn ExposureModeHelper::maxGain() + * \brief Retrieve the configured maximum gain set through setLimits() + * \return The maxGain_ value + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h new file mode 100644 index 00000000..c5be1b67 --- /dev/null +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class that performs computations relating to exposure + */ + +#pragma once + +#include <tuple> +#include <utility> +#include <vector> + +#include <libcamera/base/span.h> +#include <libcamera/base/utils.h> + +namespace libcamera { + +namespace ipa { + +class ExposureModeHelper +{ +public: + ExposureModeHelper(const Span<std::pair<utils::Duration, double>> stages); + ~ExposureModeHelper() = default; + + void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, + double minGain, double maxGain); + + std::tuple<utils::Duration, double, double> + splitExposure(utils::Duration exposure) const; + + utils::Duration minExposureTime() const { return minExposureTime_; } + utils::Duration maxExposureTime() const { return maxExposureTime_; } + double minGain() const { return minGain_; } + double maxGain() const { return maxGain_; } + +private: + utils::Duration clampExposureTime(utils::Duration exposureTime) const; + double clampGain(double gain) const; + + std::vector<utils::Duration> exposureTimes_; + std::vector<double> gains_; + + utils::Duration minExposureTime_; + utils::Duration maxExposureTime_; + double minGain_; + double maxGain_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/fc_queue.cpp b/src/ipa/libipa/fc_queue.cpp index e812faa5..0365e919 100644 --- a/src/ipa/libipa/fc_queue.cpp +++ b/src/ipa/libipa/fc_queue.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * fc_queue.cpp - IPA Frame context queue + * IPA Frame context queue */ #include "fc_queue.h" diff --git a/src/ipa/libipa/fc_queue.h b/src/ipa/libipa/fc_queue.h index a589e7e1..a1d13652 100644 --- a/src/ipa/libipa/fc_queue.h +++ b/src/ipa/libipa/fc_queue.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * fc_queue.h - IPA Frame context queue + * IPA Frame context queue */ #pragma once @@ -25,6 +25,7 @@ struct FrameContext { private: template<typename T> friend class FCQueue; uint32_t frame; + bool initialised = false; }; template<typename FrameContext> @@ -38,8 +39,10 @@ public: void clear() { - for (FrameContext &ctx : contexts_) + for (FrameContext &ctx : contexts_) { + ctx.initialised = false; ctx.frame = 0; + } } FrameContext &alloc(const uint32_t frame) @@ -83,6 +86,21 @@ public: << " has been overwritten by " << frameContext.frame; + if (frame == 0 && !frameContext.initialised) { + /* + * If the IPA calls get() at start() time it will get an + * un-intialized FrameContext as the below "frame == + * frameContext.frame" check will return success because + * FrameContexts are zeroed at creation time. + * + * Make sure the FrameContext gets initialised if get() + * is called before alloc() by the IPA for frame#0. + */ + init(frameContext, frame); + + return frameContext; + } + if (frame == frameContext.frame) return frameContext; @@ -108,6 +126,7 @@ private: { frameContext = {}; frameContext.frame = frame; + frameContext.initialised = true; } std::vector<FrameContext> contexts_; diff --git a/src/ipa/libipa/fixedpoint.cpp b/src/ipa/libipa/fixedpoint.cpp new file mode 100644 index 00000000..6b698fc5 --- /dev/null +++ b/src/ipa/libipa/fixedpoint.cpp @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Fixed / floating point conversions + */ + +#include "fixedpoint.h" + +/** + * \file fixedpoint.h + */ + +namespace libcamera { + +namespace ipa { + +/** + * \fn R floatingToFixedPoint(T number) + * \brief Convert a floating point number to a fixed-point representation + * \tparam I Bit width of the integer part of the fixed-point + * \tparam F Bit width of the fractional part of the fixed-point + * \tparam R Return type of the fixed-point representation + * \tparam T Input type of the floating point representation + * \param number The floating point number to convert to fixed point + * \return The converted value + */ + +/** + * \fn R fixedToFloatingPoint(T number) + * \brief Convert a fixed-point number to a floating point representation + * \tparam I Bit width of the integer part of the fixed-point + * \tparam F Bit width of the fractional part of the fixed-point + * \tparam R Return type of the floating point representation + * \tparam T Input type of the fixed-point representation + * \param number The fixed point number to convert to floating point + * \return The converted value + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/fixedpoint.h b/src/ipa/libipa/fixedpoint.h new file mode 100644 index 00000000..709cf50f --- /dev/null +++ b/src/ipa/libipa/fixedpoint.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Fixed / floating point conversions + */ + +#pragma once + +#include <cmath> +#include <type_traits> + +namespace libcamera { + +namespace ipa { + +#ifndef __DOXYGEN__ +template<unsigned int I, unsigned int F, typename R, typename T, + std::enable_if_t<std::is_integral_v<R> && + std::is_floating_point_v<T>> * = nullptr> +#else +template<unsigned int I, unsigned int F, typename R, typename T> +#endif +constexpr R floatingToFixedPoint(T number) +{ + static_assert(sizeof(int) >= sizeof(R)); + static_assert(I + F <= sizeof(R) * 8); + + /* + * The intermediate cast to int is needed on arm platforms to properly + * cast negative values. See + * https://embeddeduse.com/2013/08/25/casting-a-negative-float-to-an-unsigned-int/ + */ + R mask = (1 << (F + I)) - 1; + R frac = static_cast<R>(static_cast<int>(std::round(number * (1 << F)))) & mask; + + return frac; +} + +#ifndef __DOXYGEN__ +template<unsigned int I, unsigned int F, typename R, typename T, + std::enable_if_t<std::is_floating_point_v<R> && + std::is_integral_v<T>> * = nullptr> +#else +template<unsigned int I, unsigned int F, typename R, typename T> +#endif +constexpr R fixedToFloatingPoint(T number) +{ + static_assert(sizeof(int) >= sizeof(T)); + static_assert(I + F <= sizeof(T) * 8); + + /* + * Recreate the upper bits in case of a negative number by shifting the sign + * bit from the fixed point to the first bit of the unsigned and then right shifting + * by the same amount which keeps the sign bit in place. + * This can be optimized by the compiler quite well. + */ + int remaining_bits = sizeof(int) * 8 - (I + F); + int t = static_cast<int>(static_cast<unsigned>(number) << remaining_bits) >> remaining_bits; + return static_cast<R>(t) / static_cast<R>(1 << F); +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/histogram.cpp b/src/ipa/libipa/histogram.cpp index 6b5cde8e..bcf26390 100644 --- a/src/ipa/libipa/histogram.cpp +++ b/src/ipa/libipa/histogram.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * histogram.cpp - histogram calculations + * histogram calculations */ #include "histogram.h" @@ -29,24 +29,46 @@ namespace ipa { */ /** + * \fn Histogram::Histogram() + * \brief Construct an empty Histogram + * + * This empty constructor exists largely to allow Histograms to be embedded in + * other classes which may be created before the contents of the Histogram are + * known. + */ + +/** * \brief Create a cumulative histogram - * \param[in] data A pre-sorted histogram to be passed + * \param[in] data A (non-cumulative) histogram */ Histogram::Histogram(Span<const uint32_t> data) { - cumulative_.reserve(data.size()); - cumulative_.push_back(0); - for (const uint32_t &value : data) - cumulative_.push_back(cumulative_.back() + value); + cumulative_.resize(data.size() + 1); + cumulative_[0] = 0; + for (const auto &[i, value] : utils::enumerate(data)) + cumulative_[i + 1] = cumulative_[i] + value; } /** + * \fn Histogram::Histogram(Span<const uint32_t> data, Transform transform) + * \brief Create a cumulative histogram + * \param[in] data A (non-cumulative) histogram + * \param[in] transform The transformation function to apply to every bin + */ + +/** * \fn Histogram::bins() * \brief Retrieve the number of bins currently used by the Histogram * \return Number of bins */ /** + * \fn Histogram::data() + * \brief Retrieve the internal data + * \return The data + */ + +/** * \fn Histogram::total() * \brief Retrieve the total number of values in the data set * \return Number of values @@ -108,7 +130,8 @@ double Histogram::quantile(double q, uint32_t first, uint32_t last) const if (cumulative_[first + 1] == cumulative_[first]) frac = 0; else - frac = (item - cumulative_[first]) / (cumulative_[first + 1] - cumulative_[first]); + frac = (q * total() - cumulative_[first]) + / (cumulative_[first + 1] - cumulative_[first]); return first + frac; } @@ -126,26 +149,37 @@ double Histogram::quantile(double q, uint32_t first, uint32_t last) const double Histogram::interQuantileMean(double lowQuantile, double highQuantile) const { ASSERT(highQuantile > lowQuantile); - /* Proportion of pixels which lies below lowQuantile */ - double lowPoint = quantile(lowQuantile); - /* Proportion of pixels which lies below highQuantile */ - double highPoint = quantile(highQuantile, static_cast<uint32_t>(lowPoint)); - double sumBinFreq = 0, cumulFreq = 0; - - for (double p_next = floor(lowPoint) + 1.0; - p_next <= ceil(highPoint); - lowPoint = p_next, p_next += 1.0) { - int bin = floor(lowPoint); + + /* Proportion of pixels which lies below lowQuantile and highQuantile. */ + const double lowPoint = quantile(lowQuantile); + const double highPoint = quantile(highQuantile, static_cast<uint32_t>(lowPoint)); + + double sumBinFreq = 0; + double cumulFreq = 0; + + /* + * Calculate the mean pixel value between the low and high points by + * summing all the pixels between the two points, and dividing the sum + * by the number of pixels. Given the discrete nature of the histogram + * data, the sum of the pixels is approximated by accumulating the + * product of the bin values (calculated as the mid point of the bin) by + * the number of pixels they contain, for each bin in the internal. + */ + for (unsigned bin = std::floor(lowPoint); bin < std::ceil(highPoint); bin++) { + const double lowBound = std::max<double>(bin, lowPoint); + const double highBound = std::min<double>(bin + 1, highPoint); + double freq = (cumulative_[bin + 1] - cumulative_[bin]) - * (std::min(p_next, highPoint) - lowPoint); + * (highBound - lowBound); /* Accumulate weighted bin */ - sumBinFreq += bin * freq; + sumBinFreq += (highBound + lowBound) / 2 * freq; + /* Accumulate weights */ cumulFreq += freq; } - /* add 0.5 to give an average for bin mid-points */ - return sumBinFreq / cumulFreq + 0.5; + + return sumBinFreq / cumulFreq; } } /* namespace ipa */ diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h index 05bb4b80..a926002c 100644 --- a/src/ipa/libipa/histogram.h +++ b/src/ipa/libipa/histogram.h @@ -2,18 +2,18 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * histogram.h - histogram calculation interface + * histogram calculation interface */ #pragma once -#include <assert.h> #include <limits.h> #include <stdint.h> - +#include <type_traits> #include <vector> #include <libcamera/base/span.h> +#include <libcamera/base/utils.h> namespace libcamera { @@ -22,8 +22,21 @@ namespace ipa { class Histogram { public: + Histogram() { cumulative_.push_back(0); } Histogram(Span<const uint32_t> data); + + template<typename Transform, + std::enable_if_t<std::is_invocable_v<Transform, uint32_t>> * = nullptr> + Histogram(Span<const uint32_t> data, Transform transform) + { + cumulative_.resize(data.size() + 1); + cumulative_[0] = 0; + for (const auto &[i, value] : utils::enumerate(data)) + cumulative_[i + 1] = cumulative_[i] + transform(value); + } + size_t bins() const { return cumulative_.size() - 1; } + const Span<const uint64_t> data() const { return cumulative_; } uint64_t total() const { return cumulative_[cumulative_.size() - 1]; } uint64_t cumulativeFrequency(double bin) const; double quantile(double q, uint32_t first = 0, uint32_t last = UINT_MAX) const; diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp new file mode 100644 index 00000000..f901a86e --- /dev/null +++ b/src/ipa/libipa/interpolator.cpp @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class for interpolating objects + */ +#include "interpolator.h" + +#include <algorithm> +#include <string> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "interpolator.h" + +/** + * \file interpolator.h + * \brief Helper class for linear interpolating a set of objects + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Interpolator) + +namespace ipa { + +/** + * \class Interpolator + * \brief Class for storing, retrieving, and interpolating objects + * \tparam T Type of objects stored in the interpolator + * + * The main use case is to pass a map from color temperatures to corresponding + * objects (eg. matrices for color correction), and then requesting a + * interpolated object for a specific color temperature. This class will + * abstract away the interpolation portion. + */ + +/** + * \fn Interpolator::Interpolator() + * \brief Construct an empty interpolator + */ + +/** + * \fn Interpolator::Interpolator(const std::map<unsigned int, T> &data) + * \brief Construct an interpolator from a map of objects + * \param data Map from which to construct the interpolator + */ + +/** + * \fn Interpolator::Interpolator(std::map<unsigned int, T> &&data) + * \brief Construct an interpolator from a map of objects + * \param data Map from which to construct the interpolator + */ + +/** + * \fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml, + const std::string &key_name, + const std::string &value_name) + * \brief Initialize an Interpolator instance from yaml + * \tparam T Type of data stored in the interpolator + * \param[in] yaml The yaml object that contains the map of unsigned integers to + * objects + * \param[in] key_name The name of the key in the yaml object + * \param[in] value_name The name of the value in the yaml object + * + * The yaml object is expected to be a list of maps. Each map has two or more + * pairs: one of \a key_name to the key value (usually color temperature), and + * one or more of \a value_name to the object. This is a bit difficult to + * explain, so here is an example (in python, as it is easier to parse than + * yaml): + * [ + * { + * 'ct': 2860, + * 'ccm': [ 2.12089, -0.52461, -0.59629, + * -0.85342, 2.80445, -0.95103, + * -0.26897, -1.14788, 2.41685 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * + * { + * 'ct': 2960, + * 'ccm': [ 2.26962, -0.54174, -0.72789, + * -0.77008, 2.60271, -0.83262, + * -0.26036, -1.51254, 2.77289 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * + * { + * 'ct': 3603, + * 'ccm': [ 2.18644, -0.66148, -0.52496, + * -0.77828, 2.69474, -0.91645, + * -0.25239, -0.83059, 2.08298 ], + * 'offsets': [ 0, 0, 0 ] + * }, + * ] + * + * In this case, \a key_name would be 'ct', and \a value_name can be either + * 'ccm' or 'offsets'. This way multiple interpolators can be defined in + * one set of color temperature ranges in the tuning file, and they can be + * retrieved separately with the \a value_name parameter. + * + * \return Zero on success, negative error code otherwise + */ + +/** + * \fn void Interpolator<T>::setQuantization(const unsigned int q) + * \brief Set the quantization value + * \param[in] q The quantization value + * + * Sets the quantization value. When this is set, 'key' gets quantized to this + * size, before doing the interpolation. This can help in reducing the number of + * updates pushed to the hardware. + * + * Note that normally a threshold needs to be combined with quantization. + * Otherwise a value that swings around the edge of the quantization step will + * lead to constant updates. + */ + +/** + * \fn void Interpolator<T>::setData(std::map<unsigned int, T> &&data) + * \brief Set the internal map + * + * Overwrites the internal map using move semantics. + */ + +/** + * \fn std::map<unsigned int, T> &Interpolator<T>::data() const + * \brief Access the internal map + * + * \return The internal map + */ + +/** + * \fn const T& Interpolator<T>::getInterpolated() + * \brief Retrieve an interpolated value for the given key + * \param[in] key The unsigned integer key of the object to retrieve + * \param[out] quantizedKey If provided, the key value after quantization + * \return The object corresponding to the key. The object is cached internally, + * so on successive calls with the same key (after quantization) interpolation + * is not recalculated. + */ + +/** + * \fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double lambda) + * \brief Interpolate between two instances of T + * \param a The first value to interpolate + * \param b The second value to interpolate + * \param dest The destination for the interpolated value + * \param lambda The interpolation factor (0..1) + * + * Interpolates between \a a and \a b according to \a lambda. It calculates + * dest = a * (1.0 - lambda) + b * lambda; + * + * If T supports multiplication with double and addition, this function can be + * used as is. For other types this function can be overwritten using partial + * template specialization. + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h new file mode 100644 index 00000000..7880db69 --- /dev/null +++ b/src/ipa/libipa/interpolator.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class for interpolating maps of objects + */ + +#pragma once + +#include <algorithm> +#include <cmath> +#include <map> +#include <string> +#include <tuple> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Interpolator) + +namespace ipa { + +template<typename T> +class Interpolator +{ +public: + Interpolator() = default; + Interpolator(const std::map<unsigned int, T> &data) + : data_(data) + { + } + Interpolator(std::map<unsigned int, T> &&data) + : data_(std::move(data)) + { + } + + ~Interpolator() = default; + + int readYaml(const libcamera::YamlObject &yaml, + const std::string &key_name, + const std::string &value_name) + { + data_.clear(); + lastInterpolatedKey_.reset(); + + if (!yaml.isList()) { + LOG(Interpolator, Error) << "yaml object must be a list"; + return -EINVAL; + } + + for (const auto &value : yaml.asList()) { + unsigned int ct = std::stoul(value[key_name].get<std::string>("")); + std::optional<T> data = + value[value_name].get<T>(); + if (!data) { + return -EINVAL; + } + + data_[ct] = *data; + } + + if (data_.size() < 1) { + LOG(Interpolator, Error) << "Need at least one element"; + return -EINVAL; + } + + return 0; + } + + void setQuantization(const unsigned int q) + { + quantization_ = q; + } + + void setData(std::map<unsigned int, T> &&data) + { + data_ = std::move(data); + lastInterpolatedKey_.reset(); + } + + const std::map<unsigned int, T> &data() const + { + return data_; + } + + const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr) + { + ASSERT(data_.size() > 0); + + if (quantization_ > 0) + key = std::lround(key / static_cast<double>(quantization_)) * quantization_; + + if (quantizedKey) + *quantizedKey = key; + + if (lastInterpolatedKey_.has_value() && + *lastInterpolatedKey_ == key) + return lastInterpolatedValue_; + + auto it = data_.lower_bound(key); + + if (it == data_.begin()) + return it->second; + + if (it == data_.end()) + return std::prev(it)->second; + + if (it->first == key) + return it->second; + + auto it2 = std::prev(it); + double lambda = (key - it2->first) / static_cast<double>(it->first - it2->first); + interpolate(it2->second, it->second, lastInterpolatedValue_, lambda); + lastInterpolatedKey_ = key; + + return lastInterpolatedValue_; + } + + void interpolate(const T &a, const T &b, T &dest, double lambda) + { + dest = a * (1.0 - lambda) + b * lambda; + } + +private: + std::map<unsigned int, T> data_; + T lastInterpolatedValue_; + std::optional<unsigned int> lastInterpolatedKey_; + unsigned int quantization_ = 0; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp new file mode 100644 index 00000000..f607d86c --- /dev/null +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Polynomial class to represent lens shading correction + */ + +#include "lsc_polynomial.h" + +#include <libcamera/base/log.h> + +/** + * \file lsc_polynomial.h + * \brief LscPolynomial class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LscPolynomial) + +namespace ipa { + +/** + * \class LscPolynomial + * \brief Class for handling even polynomials used in lens shading correction + * + * Shading artifacts of camera lenses can be modeled using even radial + * polynomials. This class implements a polynomial with 5 coefficients which + * follows the definition of the FixVignetteRadial opcode in the Adobe DNG + * specification. + */ + +/** + * \fn LscPolynomial::LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, + double k4 = 0.0) + * \brief Construct a polynomial using the given coefficients + * \param cx Center-x relative to the image in normalized coordinates (0..1) + * \param cy Center-y relative to the image in normalized coordinates (0..1) + * \param k0 Coefficient of the polynomial + * \param k1 Coefficient of the polynomial + * \param k2 Coefficient of the polynomial + * \param k3 Coefficient of the polynomial + * \param k4 Coefficient of the polynomial + */ + +/** + * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y) + * \brief Sample the polynomial at the given normalized pixel position + * + * This functions samples the polynomial at the given pixel position divided by + * the value returned by getM(). + * + * \param x x position in normalized coordinates + * \param y y position in normalized coordinates + * \return The sampled value + */ + +/** + * \fn LscPolynomial::getM() + * \brief Get the value m as described in the dng specification + * + * Returns m according to dng spec. m represents the Euclidean distance + * (in pixels) from the optical center to the farthest pixel in the + * image. + * + * \return The sampled value + */ + +/** + * \fn LscPolynomial::setReferenceImageSize(const Size &size) + * \brief Set the reference image size + * + * Set the reference image size that is used for subsequent calls to getM() and + * sampleAtNormalizedPixelPos() + * + * \param size The size of the reference image + */ + +} // namespace ipa +} // namespace libcamera diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h new file mode 100644 index 00000000..c898faeb --- /dev/null +++ b/src/ipa/libipa/lsc_polynomial.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Helper for radial polynomial used in lens shading correction. + */ +#pragma once + +#include <algorithm> +#include <array> +#include <assert.h> +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/base/span.h> + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LscPolynomial) + +namespace ipa { + +class LscPolynomial +{ +public: + LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, + double k4 = 0.0) + : cx_(cx), cy_(cy), cnx_(0), cny_(0), + coefficients_({ k0, k1, k2, k3, k4 }) + { + } + + double sampleAtNormalizedPixelPos(double x, double y) const + { + double dx = x - cnx_; + double dy = y - cny_; + double r = sqrt(dx * dx + dy * dy); + double res = 1.0; + for (unsigned int i = 0; i < coefficients_.size(); i++) { + res += coefficients_[i] * std::pow(r, (i + 1) * 2); + } + return res; + } + + double getM() const + { + double cpx = imageSize_.width * cx_; + double cpy = imageSize_.height * cy_; + double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); + double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); + + return sqrt(mx * mx + my * my); + } + + void setReferenceImageSize(const Size &size) + { + assert(!size.isNull()); + imageSize_ = size; + + /* Calculate normalized centers */ + double m = getM(); + cnx_ = (size.width * cx_) / m; + cny_ = (size.height * cy_) / m; + } + +private: + double cx_; + double cy_; + double cnx_; + double cny_; + std::array<double, 5> coefficients_; + + Size imageSize_; +}; + +} /* namespace ipa */ + +#ifndef __DOXYGEN__ + +template<> +struct YamlObject::Getter<ipa::LscPolynomial> { + std::optional<ipa::LscPolynomial> get(const YamlObject &obj) const + { + std::optional<double> cx = obj["cx"].get<double>(); + std::optional<double> cy = obj["cy"].get<double>(); + std::optional<double> k0 = obj["k0"].get<double>(); + std::optional<double> k1 = obj["k1"].get<double>(); + std::optional<double> k2 = obj["k2"].get<double>(); + std::optional<double> k3 = obj["k3"].get<double>(); + std::optional<double> k4 = obj["k4"].get<double>(); + + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) + LOG(LscPolynomial, Error) + << "Polynomial is missing a parameter"; + + return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + } +}; + +#endif + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lux.cpp b/src/ipa/libipa/lux.cpp new file mode 100644 index 00000000..899e8824 --- /dev/null +++ b/src/ipa/libipa/lux.cpp @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class that implements lux estimation + */ +#include "lux.h" + +#include <algorithm> +#include <chrono> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "histogram.h" + +/** + * \file lux.h + * \brief Helper class that implements lux estimation + * + * Estimating the lux level of an image is a common operation that can for + * instance be used to adjust the target Y value in AGC or for Bayesian AWB + * estimation. + */ + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +LOG_DEFINE_CATEGORY(Lux) + +namespace ipa { + +/** + * \class Lux + * \brief Class that implements lux estimation + * + * IPAs that wish to use lux estimation should create a Lux algorithm module + * that lightly wraps this module by providing the platform-specific luminance + * histogram. The Lux entry in the tuning file must then precede the algorithms + * that depend on the estimated lux value. + */ + +/** + * \var Lux::referenceExposureTime_ + * \brief The exposure time of the reference image, in microseconds + */ + +/** + * \var Lux::referenceAnalogueGain_ + * \brief The analogue gain of the reference image + */ + +/** + * \var Lux::referenceDigitalGain_ + * \brief The analogue gain of the reference image + */ + +/** + * \var Lux::referenceY_ + * \brief The measured luminance of the reference image, normalized to 1 + * + */ + +/** + * \var Lux::referenceLux_ + * \brief The estimated lux level of the reference image + */ + +/** + * \brief Construct the Lux helper module + */ +Lux::Lux() +{ +} + +/** + * \brief Parse tuning data + * \param[in] tuningData The YamlObject representing the tuning data + * + * This function parses yaml tuning data for the common Lux module. It requires + * reference exposure time, analogue gain, digital gain, and lux values. + * + * \code{.unparsed} + * algorithms: + * - Lux: + * referenceExposureTime: 10000 + * referenceAnalogueGain: 4.0 + * referenceDigitalGain: 1.0 + * referenceY: 0.1831 + * referenceLux: 1000 + * \endcode + * + * \return 0 on success or a negative error code + */ +int Lux::parseTuningData(const YamlObject &tuningData) +{ + auto value = tuningData["referenceExposureTime"].get<double>(); + if (!value) { + LOG(Lux, Error) << "Missing tuning parameter: " + << "'referenceExposureTime'"; + return -EINVAL; + } + referenceExposureTime_ = *value * 1.0us; + + value = tuningData["referenceAnalogueGain"].get<double>(); + if (!value) { + LOG(Lux, Error) << "Missing tuning parameter: " + << "'referenceAnalogueGain'"; + return -EINVAL; + } + referenceAnalogueGain_ = *value; + + value = tuningData["referenceDigitalGain"].get<double>(); + if (!value) { + LOG(Lux, Error) << "Missing tuning parameter: " + << "'referenceDigitalGain'"; + return -EINVAL; + } + referenceDigitalGain_ = *value; + + value = tuningData["referenceY"].get<double>(); + if (!value) { + LOG(Lux, Error) << "Missing tuning parameter: " + << "'referenceY'"; + return -EINVAL; + } + referenceY_ = *value; + + value = tuningData["referenceLux"].get<double>(); + if (!value) { + LOG(Lux, Error) << "Missing tuning parameter: " + << "'referenceLux'"; + return -EINVAL; + } + referenceLux_ = *value; + + return 0; +} + +/** + * \brief Estimate lux given runtime values + * \param[in] exposureTime Exposure time applied to the frame + * \param[in] aGain Analogue gain applied to the frame + * \param[in] dGain Digital gain applied to the frame + * \param[in] yHist Histogram from the ISP statistics + * + * Estimate the lux given the exposure time, gain, and histogram. + * + * \return Estimated lux value + */ +double Lux::estimateLux(utils::Duration exposureTime, + double aGain, double dGain, + const Histogram &yHist) const +{ + double currentY = yHist.interQuantileMean(0, 1); + double exposureTimeRatio = referenceExposureTime_ / exposureTime; + double aGainRatio = referenceAnalogueGain_ / aGain; + double dGainRatio = referenceDigitalGain_ / dGain; + double yRatio = (currentY / yHist.bins()) / referenceY_; + + double estimatedLux = exposureTimeRatio * aGainRatio * dGainRatio * + yRatio * referenceLux_; + + LOG(Lux, Debug) << "Estimated lux " << estimatedLux; + return estimatedLux; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lux.h b/src/ipa/libipa/lux.h new file mode 100644 index 00000000..d95bcdaf --- /dev/null +++ b/src/ipa/libipa/lux.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Helper class that implements lux estimation + */ + +#pragma once + +#include <libcamera/base/utils.h> + +namespace libcamera { + +class YamlObject; + +namespace ipa { + +class Histogram; + +class Lux +{ +public: + Lux(); + + int parseTuningData(const YamlObject &tuningData); + double estimateLux(utils::Duration exposureTime, + double aGain, double dGain, + const Histogram &yHist) const; + +private: + utils::Duration referenceExposureTime_; + double referenceAnalogueGain_; + double referenceDigitalGain_; + double referenceY_; + double referenceLux_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 016b8e0e..660be940 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -1,19 +1,41 @@ # SPDX-License-Identifier: CC0-1.0 libipa_headers = files([ + 'agc_mean_luminance.h', 'algorithm.h', + 'awb_bayes.h', + 'awb_grey.h', + 'awb.h', 'camera_sensor_helper.h', + 'colours.h', + 'exposure_mode_helper.h', 'fc_queue.h', + 'fixedpoint.h', 'histogram.h', + 'interpolator.h', + 'lsc_polynomial.h', + 'lux.h', 'module.h', + 'pwl.h', ]) libipa_sources = files([ + 'agc_mean_luminance.cpp', 'algorithm.cpp', + 'awb_bayes.cpp', + 'awb_grey.cpp', + 'awb.cpp', 'camera_sensor_helper.cpp', + 'colours.cpp', + 'exposure_mode_helper.cpp', 'fc_queue.cpp', + 'fixedpoint.cpp', 'histogram.cpp', + 'interpolator.cpp', + 'lsc_polynomial.cpp', + 'lux.cpp', 'module.cpp', + 'pwl.cpp', ]) libipa_includes = include_directories('..') @@ -21,3 +43,7 @@ libipa_includes = include_directories('..') libipa = static_library('ipa', [libipa_sources, libipa_headers], include_directories : ipa_includes, dependencies : libcamera_private) + +libipa_dep = declare_dependency(sources : libipa_headers, + include_directories : libipa_includes, + link_with : libipa) diff --git a/src/ipa/libipa/module.cpp b/src/ipa/libipa/module.cpp index ee01f12a..64ca9141 100644 --- a/src/ipa/libipa/module.cpp +++ b/src/ipa/libipa/module.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas On Board * - * module.cpp - IPA Module + * IPA Module */ #include "module.h" diff --git a/src/ipa/libipa/module.h b/src/ipa/libipa/module.h index 4149a353..0fb51916 100644 --- a/src/ipa/libipa/module.h +++ b/src/ipa/libipa/module.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas On Board * - * module.h - IPA module + * IPA module */ #pragma once diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp new file mode 100644 index 00000000..3fa005ba --- /dev/null +++ b/src/ipa/libipa/pwl.cpp @@ -0,0 +1,462 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * Copyright (C) 2024, Ideas on Board Oy + * + * Piecewise linear functions + */ + +#include "pwl.h" + +#include <cmath> +#include <sstream> + +/** + * \file pwl.h + * \brief Piecewise linear functions + */ + +namespace libcamera { + +namespace ipa { + +/** + * \class Pwl + * \brief Describe a univariate piecewise linear function in two-dimensional + * real space + * + * A piecewise linear function is a univariate function that maps reals to + * reals, and it is composed of multiple straight-line segments. + * + * While a mathematical piecewise linear function would usually be defined by + * a list of linear functions and for which values of the domain they apply, + * this Pwl class is instead defined by a list of points at which these line + * segments intersect. These intersecting points are known as knots. + * + * https://en.wikipedia.org/wiki/Piecewise_linear_function + * + * A consequence of the Pwl class being defined by knots instead of linear + * functions is that the values of the piecewise linear function past the ends + * of the function are constants as opposed to linear functions. In a + * mathematical piecewise linear function that is defined by multiple linear + * functions, the ends of the function are also linear functions and hence grow + * to infinity (or negative infinity). However, since this Pwl class is defined + * by knots, the y-value of the leftmost and rightmost knots will hold for all + * x values to negative infinity and positive infinity, respectively. + */ + +/** + * \typedef Pwl::Point + * \brief Describe a point in two-dimensional real space + */ + +/** + * \class Pwl::Interval + * \brief Describe an interval in one-dimensional real space + */ + +/** + * \fn Pwl::Interval::Interval(double _start, double _end) + * \brief Construct an interval + * \param[in] _start Start of the interval + * \param[in] _end End of the interval + */ + +/** + * \fn Pwl::Interval::contains + * \brief Check if a given value falls within the interval + * \param[in] value Value to check + * \return True if the value falls within the interval, including its bounds, + * or false otherwise + */ + +/** + * \fn Pwl::Interval::clamp + * \brief Clamp a value such that it is within the interval + * \param[in] value Value to clamp + * \return The clamped value + */ + +/** + * \fn Pwl::Interval::length + * \brief Compute the length of the interval + * \return The length of the interval + */ + +/** + * \var Pwl::Interval::start + * \brief Start of the interval + */ + +/** + * \var Pwl::Interval::end + * \brief End of the interval + */ + +/** + * \brief Construct an empty piecewise linear function + */ +Pwl::Pwl() +{ +} + +/** + * \brief Construct a piecewise linear function from a list of 2D points + * \param[in] points Vector of points from which to construct the piecewise + * linear function + * + * \a points must be in ascending order of x-value. + */ +Pwl::Pwl(const std::vector<Point> &points) + : points_(points) +{ +} + +/** + * \copydoc Pwl::Pwl(const std::vector<Point> &points) + * + * The contents of the \a points vector is moved to the newly constructed Pwl + * instance. + */ +Pwl::Pwl(std::vector<Point> &&points) + : points_(std::move(points)) +{ +} + +/** + * \brief Append a point to the end of the piecewise linear function + * \param[in] x x-coordinate of the point to add to the piecewise linear function + * \param[in] y y-coordinate of the point to add to the piecewise linear function + * \param[in] eps Epsilon for the minimum x distance between points (optional) + * + * The point's x-coordinate must be greater than the x-coordinate of the last + * (= greatest) point already in the piecewise linear function. + */ +void Pwl::append(double x, double y, const double eps) +{ + if (points_.empty() || points_.back().x() + eps < x) + points_.push_back(Point({ x, y })); +} + +/** + * \brief Prepend a point to the beginning of the piecewise linear function + * \param[in] x x-coordinate of the point to add to the piecewise linear function + * \param[in] y y-coordinate of the point to add to the piecewise linear function + * \param[in] eps Epsilon for the minimum x distance between points (optional) + * + * The point's x-coordinate must be less than the x-coordinate of the first + * (= smallest) point already in the piecewise linear function. + */ +void Pwl::prepend(double x, double y, const double eps) +{ + if (points_.empty() || points_.front().x() - eps > x) + points_.insert(points_.begin(), Point({ x, y })); +} + +/** + * \fn Pwl::empty() const + * \brief Check if the piecewise linear function is empty + * \return True if there are no points in the function, false otherwise + */ + +/** + * \fn Pwl::clear() + * \brief Clear the piecewise linear function + */ + +/** + * \fn Pwl::size() const + * \brief Retrieve the number of points in the piecewise linear function + * \return The number of points in the piecewise linear function + */ + +/** + * \brief Get the domain of the piecewise linear function + * \return An interval representing the domain + */ +Pwl::Interval Pwl::domain() const +{ + return Interval(points_[0].x(), points_[points_.size() - 1].x()); +} + +/** + * \brief Get the range of the piecewise linear function + * \return An interval representing the range + */ +Pwl::Interval Pwl::range() const +{ + double lo = points_[0].y(), hi = lo; + for (auto &p : points_) + lo = std::min(lo, p.y()), hi = std::max(hi, p.y()); + return Interval(lo, hi); +} + +/** + * \brief Evaluate the piecewise linear function + * \param[in] x The x value to input into the function + * \param[inout] span Initial guess for span + * \param[in] updateSpan Set to true to update span + * + * Evaluate Pwl, optionally supplying an initial guess for the + * "span". The "span" may be optionally be updated. If you want to know + * the "span" value but don't have an initial guess you can set it to + * -1. + * + * \return The result of evaluating the piecewise linear function at position \a x + */ +double Pwl::eval(double x, int *span, bool updateSpan) const +{ + int index = findSpan(x, span && *span != -1 + ? *span + : points_.size() / 2 - 1); + if (span && updateSpan) + *span = index; + return points_[index].y() + + (x - points_[index].x()) * (points_[index + 1].y() - points_[index].y()) / + (points_[index + 1].x() - points_[index].x()); +} + +int Pwl::findSpan(double x, int span) const +{ + /* + * Pwls are generally small, so linear search may well be faster than + * binary, though could review this if large Pwls start turning up. + */ + int lastSpan = points_.size() - 2; + /* + * some algorithms may call us with span pointing directly at the last + * control point + */ + span = std::max(0, std::min(lastSpan, span)); + while (span < lastSpan && x >= points_[span + 1].x()) + span++; + while (span && x < points_[span].x()) + span--; + return span; +} + +/** + * \brief Compute the inverse function + * \param[in] eps Epsilon for the minimum x distance between points (optional) + * + * The output includes whether the resulting inverse function is a proper + * (true) inverse, or only a best effort (e.g. input was non-monotonic). + * + * \return A pair of the inverse piecewise linear function, and whether or not + * the result is a proper/true inverse + */ +std::pair<Pwl, bool> Pwl::inverse(const double eps) const +{ + bool appended = false, prepended = false, neither = false; + Pwl inverse; + + for (Point const &p : points_) { + if (inverse.empty()) { + inverse.append(p.y(), p.x(), eps); + } else if (std::abs(inverse.points_.back().x() - p.y()) <= eps || + std::abs(inverse.points_.front().x() - p.y()) <= eps) { + /* do nothing */; + } else if (p.y() > inverse.points_.back().x()) { + inverse.append(p.y(), p.x(), eps); + appended = true; + } else if (p.y() < inverse.points_.front().x()) { + inverse.prepend(p.y(), p.x(), eps); + prepended = true; + } else { + neither = true; + } + } + + /* + * This is not a proper inverse if we found ourselves putting points + * onto both ends of the inverse, or if there were points that couldn't + * go on either. + */ + bool trueInverse = !(neither || (appended && prepended)); + + return { inverse, trueInverse }; +} + +/** + * \brief Compose two piecewise linear functions together + * \param[in] other The "other" piecewise linear function + * \param[in] eps Epsilon for the minimum x distance between points (optional) + * + * The "this" function is done first, and "other" after. + * + * \return The composed piecewise linear function + */ +Pwl Pwl::compose(Pwl const &other, const double eps) const +{ + double thisX = points_[0].x(), thisY = points_[0].y(); + int thisSpan = 0, otherSpan = other.findSpan(thisY, 0); + Pwl result({ Point({ thisX, other.eval(thisY, &otherSpan, false) }) }); + + while (thisSpan != (int)points_.size() - 1) { + double dx = points_[thisSpan + 1].x() - points_[thisSpan].x(), + dy = points_[thisSpan + 1].y() - points_[thisSpan].y(); + if (std::abs(dy) > eps && + otherSpan + 1 < (int)other.points_.size() && + points_[thisSpan + 1].y() >= other.points_[otherSpan + 1].x() + eps) { + /* + * next control point in result will be where this + * function's y reaches the next span in other + */ + thisX = points_[thisSpan].x() + + (other.points_[otherSpan + 1].x() - + points_[thisSpan].y()) * + dx / dy; + thisY = other.points_[++otherSpan].x(); + } else if (std::abs(dy) > eps && otherSpan > 0 && + points_[thisSpan + 1].y() <= + other.points_[otherSpan - 1].x() - eps) { + /* + * next control point in result will be where this + * function's y reaches the previous span in other + */ + thisX = points_[thisSpan].x() + + (other.points_[otherSpan + 1].x() - + points_[thisSpan].y()) * + dx / dy; + thisY = other.points_[--otherSpan].x(); + } else { + /* we stay in the same span in other */ + thisSpan++; + thisX = points_[thisSpan].x(), + thisY = points_[thisSpan].y(); + } + result.append(thisX, other.eval(thisY, &otherSpan, false), + eps); + } + return result; +} + +/** + * \brief Apply function to (x, y) values at every control point + * \param[in] f Function to be applied + */ +void Pwl::map(std::function<void(double x, double y)> f) const +{ + for (auto &pt : points_) + f(pt.x(), pt.y()); +} + +/** + * \brief Apply function to (x, y0, y1) values wherever either Pwl has a + * control point. + * \param[in] pwl0 First piecewise linear function + * \param[in] pwl1 Second piecewise linear function + * \param[in] f Function to be applied + * + * This applies the function \a f to every parameter (x, y0, y1), where x is + * the combined list of x-values from \a pwl0 and \a pwl1, y0 is the y-value + * for the given x in \a pwl0, and y1 is the y-value for the same x in \a pwl1. + */ +void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, + std::function<void(double x, double y0, double y1)> f) +{ + int span0 = 0, span1 = 0; + double x = std::min(pwl0.points_[0].x(), pwl1.points_[0].x()); + f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); + + while (span0 < (int)pwl0.points_.size() - 1 || + span1 < (int)pwl1.points_.size() - 1) { + if (span0 == (int)pwl0.points_.size() - 1) + x = pwl1.points_[++span1].x(); + else if (span1 == (int)pwl1.points_.size() - 1) + x = pwl0.points_[++span0].x(); + else if (pwl0.points_[span0 + 1].x() > pwl1.points_[span1 + 1].x()) + x = pwl1.points_[++span1].x(); + else + x = pwl0.points_[++span0].x(); + f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); + } +} + +/** + * \brief Combine two Pwls + * \param[in] pwl0 First piecewise linear function + * \param[in] pwl1 Second piecewise linear function + * \param[in] f Function to be applied + * \param[in] eps Epsilon for the minimum x distance between points (optional) + * + * Create a new Pwl where the y values are given by running \a f wherever + * either pwl has a knot. + * + * \return The combined pwl + */ +Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, + std::function<double(double x, double y0, double y1)> f, + const double eps) +{ + Pwl result; + map2(pwl0, pwl1, [&](double x, double y0, double y1) { + result.append(x, f(x, y0, y1), eps); + }); + return result; +} + +/** + * \brief Multiply the piecewise linear function + * \param[in] d Scalar multiplier to multiply the function by + * \return This function, after it has been multiplied by \a d + */ +Pwl &Pwl::operator*=(double d) +{ + for (auto &pt : points_) + pt[1] *= d; + return *this; +} + +/** + * \brief Assemble and return a string describing the piecewise linear function + * \return A string describing the piecewise linear function + */ +std::string Pwl::toString() const +{ + std::stringstream ss; + ss << "Pwl { "; + for (auto &p : points_) + ss << "(" << p.x() << ", " << p.y() << ") "; + ss << "}"; + return ss.str(); +} + +} /* namespace ipa */ + +#ifndef __DOXYGEN__ +/* + * The YAML data shall be a list of numerical values with an even number of + * elements. They are parsed in pairs into x and y points in the piecewise + * linear function, and added in order. x must be monotonically increasing. + */ +template<> +std::optional<ipa::Pwl> +YamlObject::Getter<ipa::Pwl>::get(const YamlObject &obj) const +{ + if (!obj.size() || obj.size() % 2) + return std::nullopt; + + ipa::Pwl pwl; + + const auto &list = obj.asList(); + + for (auto it = list.begin(); it != list.end(); it++) { + auto x = it->get<double>(); + if (!x) + return std::nullopt; + auto y = (++it)->get<double>(); + if (!y) + return std::nullopt; + + pwl.append(*x, *y); + } + + if (pwl.size() != obj.size() / 2) + return std::nullopt; + + return pwl; +} +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h new file mode 100644 index 00000000..c1496c30 --- /dev/null +++ b/src/ipa/libipa/pwl.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2019, Raspberry Pi Ltd + * + * Piecewise linear functions interface + */ +#pragma once + +#include <algorithm> +#include <functional> +#include <string> +#include <utility> +#include <vector> + +#include "libcamera/internal/vector.h" + +namespace libcamera { + +namespace ipa { + +class Pwl +{ +public: + using Point = Vector<double, 2>; + + struct Interval { + Interval(double _start, double _end) + : start(_start), end(_end) {} + + bool contains(double value) + { + return value >= start && value <= end; + } + + double clamp(double value) + { + return std::clamp(value, start, end); + } + + double length() const { return end - start; } + + double start, end; + }; + + Pwl(); + Pwl(const std::vector<Point> &points); + Pwl(std::vector<Point> &&points); + + void append(double x, double y, double eps = 1e-6); + + bool empty() const { return points_.empty(); } + void clear() { points_.clear(); } + size_t size() const { return points_.size(); } + + Interval domain() const; + Interval range() const; + + double eval(double x, int *span = nullptr, + bool updateSpan = true) const; + + std::pair<Pwl, bool> inverse(double eps = 1e-6) const; + Pwl compose(const Pwl &other, double eps = 1e-6) const; + + void map(std::function<void(double x, double y)> f) const; + + static Pwl + combine(const Pwl &pwl0, const Pwl &pwl1, + std::function<double(double x, double y0, double y1)> f, + double eps = 1e-6); + + Pwl &operator*=(double d); + + std::string toString() const; + +private: + static void map2(const Pwl &pwl0, const Pwl &pwl1, + std::function<void(double x, double y0, double y1)> f); + void prepend(double x, double y, double eps = 1e-6); + int findSpan(double x, int span) const; + + std::vector<Point> points_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp new file mode 100644 index 00000000..70667db3 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -0,0 +1,410 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * agc.cpp - AGC/AEC mean-based control algorithm + */ + +#include "agc.h" + +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> +#include <libcamera/property_ids.h> + +#include "libipa/colours.h" +#include "libipa/fixedpoint.h" + +namespace libcamera { + +using namespace std::literals::chrono_literals; + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Agc) + +/* + * Number of histogram bins. This is only true for the specific configuration we + * set to the ISP; 4 separate histograms of 256 bins each. If that configuration + * ever changes then this constant will need updating. + */ +static constexpr unsigned int kNumHistogramBins = 256; + +/* + * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8 + * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual + * max value which is 8191 * 2^-8. + */ +static constexpr double kMinDigitalGain = 1.0; +static constexpr double kMaxDigitalGain = 31.99609375; + +uint32_t AgcStatistics::decodeBinValue(uint16_t binVal) +{ + int exponent = (binVal & 0xf000) >> 12; + int mantissa = binVal & 0xfff; + + if (!exponent) + return mantissa * 2; + else + return (mantissa + 4096) * std::pow(2, exponent); +} + +/* + * We configure the ISP to give us 4 histograms of 256 bins each, with + * a single histogram per colour channel (R/Gr/Gb/B). The memory space + * containing the data is a single block containing all 4 histograms + * with the position of each colour's histogram within it dependent on + * the bayer pattern of the data input to the ISP. + * + * NOTE: The validity of this function depends on the parameters we have + * configured. With different skip/offset x, y values not all of the + * colour channels would be populated, and they may not be in the same + * planes as calculated here. + */ +int AgcStatistics::setBayerOrderIndices(BayerFormat::Order bayerOrder) +{ + switch (bayerOrder) { + case BayerFormat::Order::RGGB: + rIndex_ = 0; + grIndex_ = 1; + gbIndex_ = 2; + bIndex_ = 3; + break; + case BayerFormat::Order::GRBG: + grIndex_ = 0; + rIndex_ = 1; + bIndex_ = 2; + gbIndex_ = 3; + break; + case BayerFormat::Order::GBRG: + gbIndex_ = 0; + bIndex_ = 1; + rIndex_ = 2; + grIndex_ = 3; + break; + case BayerFormat::Order::BGGR: + bIndex_ = 0; + gbIndex_ = 1; + grIndex_ = 2; + rIndex_ = 3; + break; + default: + LOG(MaliC55Agc, Error) + << "Invalid bayer format " << bayerOrder; + return -EINVAL; + } + + return 0; +} + +void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats) +{ + uint32_t r[256], g[256], b[256], y[256]; + + /* + * We need to decode the bin values for each histogram from their 16-bit + * compressed values to a 32-bit value. We also take the average of the + * Gr/Gb values into a single green histogram. + */ + for (unsigned int i = 0; i < 256; i++) { + r[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * rIndex_)]); + g[i] = (decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * grIndex_)]) + + decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * gbIndex_)])) / 2; + b[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * bIndex_)]); + + y[i] = rec601LuminanceFromRGB({ { static_cast<double>(r[i]), + static_cast<double>(g[i]), + static_cast<double>(b[i]) } }); + } + + rHist = Histogram(Span<uint32_t>(r, kNumHistogramBins)); + gHist = Histogram(Span<uint32_t>(g, kNumHistogramBins)); + bHist = Histogram(Span<uint32_t>(b, kNumHistogramBins)); + yHist = Histogram(Span<uint32_t>(y, kNumHistogramBins)); +} + +Agc::Agc() + : AgcMeanLuminance() +{ +} + +int Agc::init(IPAContext &context, const YamlObject &tuningData) +{ + int ret = parseTuningData(tuningData); + if (ret) + return ret; + + context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); + context.ctrlMap[&controls::DigitalGain] = ControlInfo( + static_cast<float>(kMinDigitalGain), + static_cast<float>(kMaxDigitalGain), + static_cast<float>(kMinDigitalGain) + ); + context.ctrlMap.merge(controls()); + + return 0; +} + +int Agc::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + int ret = statistics_.setBayerOrderIndices(context.configuration.sensor.bayerOrder); + if (ret) + return ret; + + /* + * Defaults; we use whatever the sensor's default exposure is and the + * minimum analogue gain. AEGC is _active_ by default. + */ + context.activeState.agc.autoEnabled = true; + context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; + context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; + context.activeState.agc.automatic.ispGain = kMinDigitalGain; + context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; + context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; + context.activeState.agc.manual.ispGain = kMinDigitalGain; + context.activeState.agc.constraintMode = constraintModes().begin()->first; + context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + + /* \todo Run this again when FrameDurationLimits is passed in */ + setLimits(context.configuration.agc.minShutterSpeed, + context.configuration.agc.maxShutterSpeed, + context.configuration.agc.minAnalogueGain, + context.configuration.agc.maxAnalogueGain); + + resetFrameCount(); + + return 0; +} + +void Agc::queueRequest(IPAContext &context, const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &agc = context.activeState.agc; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + agc.constraintMode = constraintMode.value_or(agc.constraintMode); + + const auto &exposureMode = controls.get(controls::AeExposureMode); + agc.exposureMode = exposureMode.value_or(agc.exposureMode); + + const auto &agcEnable = controls.get(controls::AeEnable); + if (agcEnable && *agcEnable != agc.autoEnabled) { + agc.autoEnabled = *agcEnable; + + LOG(MaliC55Agc, Info) + << (agc.autoEnabled ? "Enabling" : "Disabling") + << " AGC"; + } + + /* + * If the automatic exposure and gain is enabled we have no further work + * to do here... + */ + if (agc.autoEnabled) + return; + + /* + * ...otherwise we need to look for exposure and gain controls and use + * those to set the activeState. + */ + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure) { + agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; + + LOG(MaliC55Agc, Debug) + << "Exposure set to " << agc.manual.exposure + << " on request sequence " << frame; + } + + const auto &analogueGain = controls.get(controls::AnalogueGain); + if (analogueGain) { + agc.manual.sensorGain = *analogueGain; + + LOG(MaliC55Agc, Debug) + << "Analogue gain set to " << agc.manual.sensorGain + << " on request sequence " << frame; + } + + const auto &digitalGain = controls.get(controls::DigitalGain); + if (digitalGain) { + agc.manual.ispGain = *digitalGain; + + LOG(MaliC55Agc, Debug) + << "Digital gain set to " << agc.manual.ispGain + << " on request sequence " << frame; + } +} + +size_t Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, + mali_c55_params_block block) +{ + IPAActiveState &activeState = context.activeState; + double gain; + + if (activeState.agc.autoEnabled) + gain = activeState.agc.automatic.ispGain; + else + gain = activeState.agc.manual.ispGain; + + block.header->type = MALI_C55_PARAM_BLOCK_DIGITAL_GAIN; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_digital_gain); + + block.digital_gain->gain = floatingToFixedPoint<5, 8, uint16_t, double>(gain); + frameContext.agc.ispGain = gain; + + return block.header->size; +} + +size_t Agc::fillParamsBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type) +{ + block.header->type = type; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_aexp_hist); + + /* Collect every 3rd pixel horizontally */ + block.aexp_hist->skip_x = 1; + /* Start from first column */ + block.aexp_hist->offset_x = 0; + /* Collect every pixel vertically */ + block.aexp_hist->skip_y = 0; + /* Start from the first row */ + block.aexp_hist->offset_y = 0; + /* 1x scaling (i.e. none) */ + block.aexp_hist->scale_bottom = 0; + block.aexp_hist->scale_top = 0; + /* Collect all Bayer planes into 4 separate histograms */ + block.aexp_hist->plane_mode = 1; + /* Tap the data immediately after the digital gain block */ + block.aexp_hist->tap_point = MALI_C55_AEXP_HIST_TAP_FS; + + return block.header->size; +} + +size_t Agc::fillWeightsArrayBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type) +{ + block.header->type = type; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_aexp_weights); + + /* We use every zone - a 15x15 grid */ + block.aexp_weights->nodes_used_horiz = 15; + block.aexp_weights->nodes_used_vert = 15; + + /* + * We uniformly weight the zones to 1 - this results in the collected + * histograms containing a true pixel count, which we can then use to + * approximate colour channel averages for the image. + */ + Span<uint8_t> weights{ + block.aexp_weights->zone_weights, + MALI_C55_MAX_ZONES + }; + std::fill(weights.begin(), weights.end(), 1); + + return block.header->size; +} + +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, mali_c55_params_buffer *params) +{ + mali_c55_params_block block; + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillGainParamBlock(context, frameContext, block); + + if (frame > 0) + return; + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillParamsBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_HIST); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillWeightsArrayBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillParamsBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_IHIST); + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillWeightsArrayBuffer(block, + MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS); +} + +double Agc::estimateLuminance(const double gain) const +{ + double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; + double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; + double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; + double yAvg = rec601LuminanceFromRGB({ { rAvg, gAvg, bAvg } }); + + return yAvg / kNumHistogramBins; +} + +void Agc::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + [[maybe_unused]] ControlList &metadata) +{ + IPASessionConfiguration &configuration = context.configuration; + IPAActiveState &activeState = context.activeState; + + if (!stats) { + LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; + return; + } + + statistics_.parseStatistics(stats); + context.activeState.agc.temperatureK = estimateCCT({ { statistics_.rHist.interQuantileMean(0, 1), + statistics_.gHist.interQuantileMean(0, 1), + statistics_.bHist.interQuantileMean(0, 1) } }); + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + uint32_t exposure = frameContext.agc.exposure; + double analogueGain = frameContext.agc.sensorGain; + double digitalGain = frameContext.agc.ispGain; + double totalGain = analogueGain * digitalGain; + utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; + utils::Duration effectiveExposureValue = currentShutter * totalGain; + + utils::Duration shutterTime; + double aGain, dGain; + std::tie(shutterTime, aGain, dGain) = + calculateNewEv(activeState.agc.constraintMode, + activeState.agc.exposureMode, statistics_.yHist, + effectiveExposureValue); + + dGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain); + + LOG(MaliC55Agc, Debug) + << "Divided up shutter, analogue gain and digital gain are " + << shutterTime << ", " << aGain << " and " << dGain; + + activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; + activeState.agc.automatic.sensorGain = aGain; + activeState.agc.automatic.ispGain = dGain; + + metadata.set(controls::ExposureTime, currentShutter.get<std::micro>()); + metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); + metadata.set(controls::DigitalGain, frameContext.agc.ispGain); + metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); +} + +REGISTER_IPA_ALGORITHM(Agc, "Agc") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h new file mode 100644 index 00000000..c5c574e5 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Ideas on Board Oy + * + * agc.h - Mali C55 AGC/AEC mean-based control algorithm + */ + +#pragma once + +#include <libcamera/base/utils.h> + +#include "libcamera/internal/bayer_format.h" + +#include "libipa/agc_mean_luminance.h" +#include "libipa/histogram.h" + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class AgcStatistics +{ +public: + AgcStatistics() + { + } + + int setBayerOrderIndices(BayerFormat::Order bayerOrder); + uint32_t decodeBinValue(uint16_t binVal); + void parseStatistics(const mali_c55_stats_buffer *stats); + + Histogram rHist; + Histogram gHist; + Histogram bHist; + Histogram yHist; +private: + unsigned int rIndex_; + unsigned int grIndex_; + unsigned int gbIndex_; + unsigned int bIndex_; +}; + +class Agc : public Algorithm, public AgcMeanLuminance +{ +public: + Agc(); + ~Agc() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + mali_c55_params_buffer *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + double estimateLuminance(const double gain) const override; + size_t fillGainParamBlock(IPAContext &context, + IPAFrameContext &frameContext, + mali_c55_params_block block); + size_t fillParamsBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type); + size_t fillWeightsArrayBuffer(mali_c55_params_block block, + enum mali_c55_param_block_type type); + + AgcStatistics statistics_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/algorithm.h b/src/ipa/mali-c55/algorithms/algorithm.h new file mode 100644 index 00000000..36a3bff0 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/algorithm.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * algorithm.h - Mali-C55 control algorithm interface + */ + +#pragma once + +#include <linux/mali-c55-config.h> + +#include <libipa/algorithm.h> + +#include "module.h" + +namespace libcamera { + +namespace ipa::mali_c55 { + +class Algorithm : public libcamera::ipa::Algorithm<Module> +{ +}; + +union mali_c55_params_block { + struct mali_c55_params_block_header *header; + struct mali_c55_params_sensor_off_preshading *sensor_offs; + struct mali_c55_params_aexp_hist *aexp_hist; + struct mali_c55_params_aexp_weights *aexp_weights; + struct mali_c55_params_digital_gain *digital_gain; + struct mali_c55_params_awb_gains *awb_gains; + struct mali_c55_params_awb_config *awb_config; + struct mali_c55_params_mesh_shading_config *shading_config; + struct mali_c55_params_mesh_shading_selection *shading_selection; + __u8 *data; +}; + +} /* namespace ipa::mali_c55 */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp new file mode 100644 index 00000000..050b191b --- /dev/null +++ b/src/ipa/mali-c55/algorithms/awb.cpp @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * awb.cpp - Mali C55 grey world auto white balance algorithm + */ + +#include "awb.h" + +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +#include "libipa/fixedpoint.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Awb) + +/* Number of frames at which we should run AWB at full speed */ +static constexpr uint32_t kNumStartupFrames = 4; + +Awb::Awb() +{ +} + +int Awb::configure([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + /* + * Initially we have no idea what the colour balance will be like, so + * for the first frame we will make no assumptions and leave the R/B + * channels unmodified. + */ + context.activeState.awb.rGain = 1.0; + context.activeState.awb.bGain = 1.0; + + return 0; +} + +size_t Awb::fillGainsParamBlock(mali_c55_params_block block, IPAContext &context, + IPAFrameContext &frameContext) +{ + block.header->type = MALI_C55_PARAM_BLOCK_AWB_GAINS; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_awb_gains); + + double rGain = context.activeState.awb.rGain; + double bGain = context.activeState.awb.bGain; + + /* + * The gains here map as follows: + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * This holds true regardless of the bayer order of the input data, as + * the mapping is done internally in the ISP. + */ + block.awb_gains->gain00 = floatingToFixedPoint<4, 8, uint16_t, double>(rGain); + block.awb_gains->gain01 = floatingToFixedPoint<4, 8, uint16_t, double>(1.0); + block.awb_gains->gain10 = floatingToFixedPoint<4, 8, uint16_t, double>(1.0); + block.awb_gains->gain11 = floatingToFixedPoint<4, 8, uint16_t, double>(bGain); + + frameContext.awb.rGain = rGain; + frameContext.awb.bGain = bGain; + + return sizeof(struct mali_c55_params_awb_gains); +} + +size_t Awb::fillConfigParamBlock(mali_c55_params_block block) +{ + block.header->type = MALI_C55_PARAM_BLOCK_AWB_CONFIG; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_awb_config); + + /* Tap the stats after the purple fringe block */ + block.awb_config->tap_point = MALI_C55_AWB_STATS_TAP_PF; + + /* Get R/G and B/G ratios as statistics */ + block.awb_config->stats_mode = MALI_C55_AWB_MODE_RGBG; + + /* Default white level */ + block.awb_config->white_level = 1023; + + /* Default black level */ + block.awb_config->black_level = 0; + + /* + * By default pixels are included who's colour ratios are bounded in a + * region (on a cr ratio x cb ratio graph) defined by four points: + * (0.25, 0.25) + * (0.25, 1.99609375) + * (1.99609375, 1.99609375) + * (1.99609375, 0.25) + * + * The ratios themselves are stored in Q4.8 format. + * + * \todo should these perhaps be tunable? + */ + block.awb_config->cr_max = 511; + block.awb_config->cr_min = 64; + block.awb_config->cb_max = 511; + block.awb_config->cb_min = 64; + + /* We use the full 15x15 zoning scheme */ + block.awb_config->nodes_used_horiz = 15; + block.awb_config->nodes_used_vert = 15; + + /* + * We set the trimming boundaries equivalent to the main boundaries. In + * other words; no trimming. + */ + block.awb_config->cr_high = 511; + block.awb_config->cr_low = 64; + block.awb_config->cb_high = 511; + block.awb_config->cb_low = 64; + + return sizeof(struct mali_c55_params_awb_config); +} + +void Awb::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, mali_c55_params_buffer *params) +{ + mali_c55_params_block block; + block.data = ¶ms->data[params->total_size]; + + params->total_size += fillGainsParamBlock(block, context, frameContext); + + if (frame > 0) + return; + + block.data = ¶ms->data[params->total_size]; + params->total_size += fillConfigParamBlock(block); +} + +void Awb::process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, + [[maybe_unused]] ControlList &metadata) +{ + const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; + + /* + * The ISP produces average R:G and B:G ratios for zones. We take the + * average of all the zones with data and simply invert them to provide + * gain figures that we can apply to approximate a grey world. + */ + unsigned int counted_zones = 0; + double rgSum = 0, bgSum = 0; + + for (unsigned int i = 0; i < 225; i++) { + if (!awb_ratios[i].num_pixels) + continue; + + /* + * The statistics are in Q4.8 format, so we convert to double + * here. + */ + rgSum += fixedToFloatingPoint<4, 8, double, uint16_t>(awb_ratios[i].avg_rg_gr); + bgSum += fixedToFloatingPoint<4, 8, double, uint16_t>(awb_ratios[i].avg_bg_br); + counted_zones++; + } + + /* + * Sometimes the first frame's statistics have no valid pixels, in which + * case we'll just assume a grey world until they say otherwise. + */ + double rgAvg, bgAvg; + if (!counted_zones) { + rgAvg = 1.0; + bgAvg = 1.0; + } else { + rgAvg = rgSum / counted_zones; + bgAvg = bgSum / counted_zones; + } + + /* + * The statistics are generated _after_ white balancing is performed in + * the ISP. To get the true ratio we therefore have to adjust the stats + * figure by the gains that were applied when the statistics for this + * frame were generated. + */ + double rRatio = rgAvg / frameContext.awb.rGain; + double bRatio = bgAvg / frameContext.awb.bGain; + + /* + * And then we can simply invert the ratio to find the gain we should + * apply. + */ + double rGain = 1 / rRatio; + double bGain = 1 / bRatio; + + /* + * Running at full speed, this algorithm results in oscillations in the + * colour balance. To remove those we dampen the speed at which it makes + * changes in gain, unless we're in the startup phase in which case we + * want to fix the miscolouring as quickly as possible. + */ + double speed = frame < kNumStartupFrames ? 1.0 : 0.2; + rGain = speed * rGain + context.activeState.awb.rGain * (1.0 - speed); + bGain = speed * bGain + context.activeState.awb.bGain * (1.0 - speed); + + context.activeState.awb.rGain = rGain; + context.activeState.awb.bGain = bGain; + + metadata.set(controls::ColourGains, { + static_cast<float>(frameContext.awb.rGain), + static_cast<float>(frameContext.awb.bGain), + }); + + LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " + << "Average R/G Ratio: " << rgAvg + << ", Average B/G Ratio: " << bgAvg + << "\nrGain applied to this frame: " << frameContext.awb.rGain + << ", bGain applied to this frame: " << frameContext.awb.bGain + << "\nrGain to apply: " << context.activeState.awb.rGain + << ", bGain to apply: " << context.activeState.awb.bGain; +} + +REGISTER_IPA_ALGORITHM(Awb, "Awb") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h new file mode 100644 index 00000000..800c2e83 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/awb.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * awb.h - Mali C55 grey world auto white balance algorithm + */ + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class Awb : public Algorithm +{ +public: + Awb(); + ~Awb() = default; + + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + mali_c55_params_buffer *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + size_t fillGainsParamBlock(mali_c55_params_block block, + IPAContext &context, + IPAFrameContext &frameContext); + size_t fillConfigParamBlock(mali_c55_params_block block); +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/blc.cpp b/src/ipa/mali-c55/algorithms/blc.cpp new file mode 100644 index 00000000..2a54c86a --- /dev/null +++ b/src/ipa/mali-c55/algorithms/blc.cpp @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Mali-C55 sensor offset (black level) correction + */ + +#include "blc.h" + +#include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + +#include "libcamera/internal/yaml_parser.h" + +/** + * \file blc.h + */ + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +/** + * \class BlackLevelCorrection + * \brief MaliC55 Black Level Correction control + */ + +LOG_DEFINE_CATEGORY(MaliC55Blc) + +BlackLevelCorrection::BlackLevelCorrection() + : tuningParameters_(false) +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + offset00 = tuningData["offset00"].get<uint32_t>(0); + offset01 = tuningData["offset01"].get<uint32_t>(0); + offset10 = tuningData["offset10"].get<uint32_t>(0); + offset11 = tuningData["offset11"].get<uint32_t>(0); + + if (offset00 > kMaxOffset || offset01 > kMaxOffset || + offset10 > kMaxOffset || offset11 > kMaxOffset) { + LOG(MaliC55Blc, Error) << "Invalid black level offsets"; + return -EINVAL; + } + + tuningParameters_ = true; + + LOG(MaliC55Blc, Debug) + << "Black levels: 00 " << offset00 << ", 01 " << offset01 + << ", 10 " << offset10 << ", 11 " << offset11; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int BlackLevelCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + /* + * If no Black Levels were passed in through tuning data then we could + * use the value from the CameraSensorHelper if one is available. + */ + if (context.configuration.sensor.blackLevel && + !(offset00 + offset01 + offset10 + offset11)) { + offset00 = context.configuration.sensor.blackLevel; + offset01 = context.configuration.sensor.blackLevel; + offset10 = context.configuration.sensor.blackLevel; + offset11 = context.configuration.sensor.blackLevel; + } + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + mali_c55_params_buffer *params) +{ + mali_c55_params_block block; + block.data = ¶ms->data[params->total_size]; + + if (frame > 0) + return; + + if (!tuningParameters_) + return; + + block.header->type = MALI_C55_PARAM_BLOCK_SENSOR_OFFS; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(mali_c55_params_sensor_off_preshading); + + block.sensor_offs->chan00 = offset00; + block.sensor_offs->chan01 = offset01; + block.sensor_offs->chan10 = offset10; + block.sensor_offs->chan11 = offset11; + + params->total_size += block.header->size; +} + +void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + /* + * Black Level Offsets in tuning data need to be 20-bit, whereas the + * metadata expects values from a 16-bit range. Right-shift to remove + * the 4 least significant bits. + * + * The black levels should be reported in the order R, Gr, Gb, B. We + * ignore that here given we're using matching values so far, but it + * would be safer to check the sensor's bayer order. + * + * \todo Account for bayer order. + */ + metadata.set(controls::SensorBlackLevels, { + static_cast<int32_t>(offset00 >> 4), + static_cast<int32_t>(offset01 >> 4), + static_cast<int32_t>(offset10 >> 4), + static_cast<int32_t>(offset11 >> 4), + }); +} + +REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/blc.h b/src/ipa/mali-c55/algorithms/blc.h new file mode 100644 index 00000000..9696e8e9 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/blc.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * Mali-C55 sensor offset (black level) correction + */ + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class BlackLevelCorrection : public Algorithm +{ +public: + BlackLevelCorrection(); + ~BlackLevelCorrection() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + mali_c55_params_buffer *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + static constexpr uint32_t kMaxOffset = 0xfffff; + + bool tuningParameters_; + uint32_t offset00; + uint32_t offset01; + uint32_t offset10; + uint32_t offset11; +}; + +} /* namespace ipa::mali_c55::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp new file mode 100644 index 00000000..c5afc04d --- /dev/null +++ b/src/ipa/mali-c55/algorithms/lsc.cpp @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * lsc.cpp - Mali-C55 Lens shading correction algorithm + */ + +#include "lsc.h" + +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Lsc) + +int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + if (!tuningData.contains("meshScale")) { + LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData"; + return -EINVAL; + } + + meshScale_ = tuningData["meshScale"].get<uint32_t>(0); + + const YamlObject &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid"; + return -EINVAL; + } + + size_t tableSize = 0; + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (!ct) { + LOG(MaliC55Lsc, Error) << "Invalid colour temperature"; + return -EINVAL; + } + + if (std::count(colourTemperatures_.begin(), + colourTemperatures_.end(), ct)) { + LOG(MaliC55Lsc, Error) + << "Multiple sets found for colour temperature"; + return -EINVAL; + } + + std::vector<uint8_t> rTable = + yamlSet["r"].getList<uint8_t>().value_or(std::vector<uint8_t>{}); + std::vector<uint8_t> gTable = + yamlSet["g"].getList<uint8_t>().value_or(std::vector<uint8_t>{}); + std::vector<uint8_t> bTable = + yamlSet["b"].getList<uint8_t>().value_or(std::vector<uint8_t>{}); + + /* + * Some validation to do; only 16x16 and 32x32 tables of + * coefficients are acceptable, and all tables across all of the + * sets must be the same size. The first time we encounter a + * table we check that it is an acceptable size and if so make + * sure all other tables are of equal size. + */ + if (!tableSize) { + if (rTable.size() != 256 && rTable.size() != 1024) { + LOG(MaliC55Lsc, Error) + << "Invalid table size for colour temperature " << ct; + return -EINVAL; + } + tableSize = rTable.size(); + } + + if (rTable.size() != tableSize || + gTable.size() != tableSize || + bTable.size() != tableSize) { + LOG(MaliC55Lsc, Error) + << "Invalid or mismatched table size for colour temperature " << ct; + return -EINVAL; + } + + if (colourTemperatures_.size() >= 3) { + LOG(MaliC55Lsc, Error) + << "A maximum of 3 colour temperatures are supported"; + return -EINVAL; + } + + for (unsigned int i = 0; i < tableSize; i++) { + mesh_[kRedOffset + i] |= + (rTable[i] << (colourTemperatures_.size() * 8)); + mesh_[kGreenOffset + i] |= + (gTable[i] << (colourTemperatures_.size() * 8)); + mesh_[kBlueOffset + i] |= + (bTable[i] << (colourTemperatures_.size() * 8)); + } + + colourTemperatures_.push_back(ct); + } + + /* + * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it + * is based on the number of values in the tuning data's table. + */ + if (tableSize == 256) + meshSize_ = 15; + else + meshSize_ = 31; + + return 0; +} + +size_t Lsc::fillConfigParamsBlock(mali_c55_params_block block) const +{ + block.header->type = MALI_C55_PARAM_MESH_SHADING_CONFIG; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_mesh_shading_config); + + block.shading_config->mesh_show = false; + block.shading_config->mesh_scale = meshScale_; + block.shading_config->mesh_page_r = 0; + block.shading_config->mesh_page_g = 1; + block.shading_config->mesh_page_b = 2; + block.shading_config->mesh_width = meshSize_; + block.shading_config->mesh_height = meshSize_; + + std::copy(mesh_.begin(), mesh_.end(), block.shading_config->mesh); + + return block.header->size; +} + +size_t Lsc::fillSelectionParamsBlock(mali_c55_params_block block, uint8_t bank, + uint8_t alpha) const +{ + block.header->type = MALI_C55_PARAM_MESH_SHADING_SELECTION; + block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE; + block.header->size = sizeof(struct mali_c55_params_mesh_shading_selection); + + block.shading_selection->mesh_alpha_bank_r = bank; + block.shading_selection->mesh_alpha_bank_g = bank; + block.shading_selection->mesh_alpha_bank_b = bank; + block.shading_selection->mesh_alpha_r = alpha; + block.shading_selection->mesh_alpha_g = alpha; + block.shading_selection->mesh_alpha_b = alpha; + block.shading_selection->mesh_strength = 0x1000; /* Otherwise known as 1.0 */ + + return block.header->size; +} + +std::tuple<uint8_t, uint8_t> Lsc::findBankAndAlpha(uint32_t ct) const +{ + unsigned int i; + + ct = std::clamp<uint32_t>(ct, colourTemperatures_.front(), + colourTemperatures_.back()); + + for (i = 0; i < colourTemperatures_.size() - 1; i++) { + if (ct >= colourTemperatures_[i] && + ct <= colourTemperatures_[i + 1]) + break; + } + + /* + * With the clamping, we're guaranteed an index into colourTemperatures_ + * that's <= colourTemperatures_.size() - 1. + */ + uint8_t alpha = (255 * (ct - colourTemperatures_[i])) / + (colourTemperatures_[i + 1] - colourTemperatures_[i]); + + return { i, alpha }; +} + +void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + mali_c55_params_buffer *params) +{ + /* + * For each frame we assess the colour temperature of the **last** frame + * and then select an appropriately blended table of coefficients based + * on that ct. As a bit of a shortcut, if we've only a single table the + * handling is somewhat simpler; if it's the first frame we just select + * that table and if we're past the first frame then we can just do + * nothing - the config will never change. + */ + uint32_t temperatureK = context.activeState.agc.temperatureK; + uint8_t bank, alpha; + + if (colourTemperatures_.size() == 1) { + if (frame > 0) + return; + + bank = 0; + alpha = 0; + } else { + std::tie(bank, alpha) = findBankAndAlpha(temperatureK); + } + + mali_c55_params_block block; + block.data = ¶ms->data[params->total_size]; + + params->total_size += fillSelectionParamsBlock(block, bank, alpha); + + if (frame > 0) + return; + + /* + * If this is the first frame, we need to load the parsed coefficient + * tables from tuning data to the ISP. + */ + block.data = ¶ms->data[params->total_size]; + params->total_size += fillConfigParamsBlock(block); +} + +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h new file mode 100644 index 00000000..e613277a --- /dev/null +++ b/src/ipa/mali-c55/algorithms/lsc.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board Oy + * + * lsc.h - Mali-C55 Lens shading correction algorithm + */ + +#include <map> +#include <tuple> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class Lsc : public Algorithm +{ +public: + Lsc() = default; + ~Lsc() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + mali_c55_params_buffer *params) override; +private: + static constexpr unsigned int kRedOffset = 0; + static constexpr unsigned int kGreenOffset = 1024; + static constexpr unsigned int kBlueOffset = 2048; + + size_t fillConfigParamsBlock(mali_c55_params_block block) const; + size_t fillSelectionParamsBlock(mali_c55_params_block block, + uint8_t bank, uint8_t alpha) const; + std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const; + + std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072); + std::vector<uint32_t> colourTemperatures_; + uint32_t meshScale_; + uint32_t meshSize_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build new file mode 100644 index 00000000..1665da07 --- /dev/null +++ b/src/ipa/mali-c55/algorithms/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: CC0-1.0 + +mali_c55_ipa_algorithms = files([ + 'agc.cpp', + 'awb.cpp', + 'blc.cpp', + 'lsc.cpp', +]) diff --git a/src/ipa/mali-c55/data/imx415.yaml b/src/ipa/mali-c55/data/imx415.yaml new file mode 100644 index 00000000..126b427a --- /dev/null +++ b/src/ipa/mali-c55/data/imx415.yaml @@ -0,0 +1,325 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +version: 1 +algorithms: + - Agc: + - Awb: + - BlackLevelCorrection: + offset00: 51200 + offset01: 51200 + offset10: 51200 + offset11: 51200 + - Lsc: + meshScale: 4 # 1.0 - 2.0 Gain + sets: + - ct: 2500 + r: [ + 21, 20, 19, 17, 15, 14, 12, 11, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 13, 13, 10, 10, 13, 16, 17, 18, 21, 22, + 21, 20, 18, 16, 14, 13, 12, 11, 10, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 15, 17, 18, 21, 21, + 20, 19, 17, 16, 14, 13, 12, 11, 10, 9, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 8, 8, 8, 11, 15, 17, 18, 21, 21, + 19, 19, 17, 15, 14, 13, 12, 11, 10, 8, 8, 7, 7, 7, 6, 6, 7, 7, 8, 8, 8, 8, 8, 7, 7, 8, 10, 14, 17, 18, 20, 22, + 19, 18, 17, 15, 14, 13, 11, 11, 9, 8, 8, 7, 7, 6, 5, 5, 5, 5, 6, 7, 8, 7, 7, 6, 7, 7, 10, 12, 16, 18, 20, 22, + 18, 18, 16, 15, 14, 12, 11, 10, 9, 8, 6, 6, 5, 5, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 8, 12, 16, 18, 19, 20, + 18, 18, 16, 14, 13, 12, 11, 9, 9, 7, 6, 5, 5, 5, 4, 4, 4, 5, 5, 4, 4, 5, 5, 5, 5, 6, 8, 11, 15, 18, 18, 19, + 18, 17, 15, 14, 13, 12, 11, 9, 8, 7, 6, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 6, 7, 9, 14, 17, 18, 18, + 18, 17, 15, 14, 13, 12, 11, 9, 8, 7, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 6, 8, 12, 17, 18, 18, + 18, 16, 15, 13, 12, 11, 10, 9, 8, 7, 5, 4, 4, 4, 4, 3, 3, 4, 4, 4, 3, 3, 4, 4, 4, 5, 6, 8, 12, 16, 19, 19, + 17, 16, 15, 13, 12, 11, 10, 8, 7, 6, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 4, 5, 6, 9, 12, 16, 19, 20, + 17, 15, 15, 13, 12, 11, 10, 8, 6, 6, 5, 4, 3, 3, 3, 2, 3, 3, 4, 4, 3, 3, 2, 3, 4, 5, 6, 9, 11, 16, 19, 20, + 17, 15, 15, 14, 11, 11, 10, 8, 6, 5, 5, 4, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 2, 3, 4, 5, 6, 8, 11, 16, 18, 19, + 16, 16, 15, 13, 11, 11, 10, 7, 6, 5, 4, 4, 3, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 3, 4, 6, 8, 11, 14, 17, 18, + 16, 16, 14, 13, 11, 10, 9, 7, 6, 5, 4, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 4, 6, 8, 10, 14, 17, 18, + 16, 15, 14, 13, 13, 10, 9, 7, 6, 4, 4, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 6, 8, 11, 17, 18, 19, + 16, 15, 14, 14, 13, 12, 9, 8, 7, 5, 4, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 8, 12, 17, 19, 20, + 17, 15, 15, 14, 13, 12, 9, 8, 7, 5, 3, 2, 1, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 6, 8, 13, 16, 19, 21, + 17, 16, 15, 13, 13, 12, 9, 8, 7, 5, 3, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5, 5, 7, 8, 13, 16, 19, 20, + 17, 16, 15, 14, 13, 12, 9, 8, 7, 5, 3, 2, 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 6, 7, 9, 13, 17, 19, 20, + 18, 16, 15, 14, 13, 12, 9, 8, 7, 5, 4, 2, 1, 0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 4, 5, 6, 8, 9, 13, 17, 20, 20, + 18, 16, 16, 15, 14, 12, 10, 9, 7, 6, 5, 3, 2, 1, 0, 0, 0, 1, 2, 3, 3, 3, 4, 5, 6, 7, 9, 10, 14, 18, 20, 20, + 18, 17, 16, 15, 14, 12, 10, 9, 8, 7, 6, 5, 3, 3, 1, 0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 9, 12, 15, 19, 20, 20, + 18, 18, 17, 16, 14, 13, 11, 10, 9, 8, 7, 6, 5, 5, 3, 1, 1, 1, 2, 3, 5, 5, 5, 6, 7, 9, 12, 15, 17, 19, 20, 20, + 18, 18, 17, 16, 15, 13, 12, 10, 10, 9, 8, 7, 6, 5, 4, 2, 1, 2, 3, 4, 5, 5, 6, 6, 8, 10, 13, 16, 18, 20, 20, 21, + 19, 18, 17, 16, 15, 14, 13, 11, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 3, 3, 5, 5, 6, 7, 10, 11, 14, 17, 19, 20, 21, 22, + 20, 19, 18, 17, 16, 15, 13, 12, 11, 10, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 5, 6, 6, 7, 10, 12, 14, 18, 20, 21, 22, 23, + 21, 20, 19, 18, 17, 16, 14, 13, 12, 11, 10, 10, 9, 7, 7, 5, 5, 4, 4, 5, 6, 6, 7, 8, 11, 13, 16, 19, 21, 22, 22, 22, + 22, 21, 20, 19, 18, 17, 16, 14, 13, 12, 12, 10, 9, 8, 7, 6, 6, 5, 5, 6, 7, 7, 8, 9, 12, 14, 18, 20, 21, 22, 22, 22, + 23, 22, 21, 20, 19, 17, 16, 15, 14, 14, 13, 12, 10, 9, 8, 7, 6, 5, 5, 6, 7, 8, 8, 10, 12, 15, 18, 20, 21, 22, 22, 22, + 24, 23, 22, 21, 20, 18, 17, 16, 15, 15, 14, 14, 13, 11, 9, 8, 6, 6, 6, 6, 7, 8, 9, 11, 14, 17, 19, 20, 21, 21, 21, 21, + 24, 24, 23, 21, 20, 19, 17, 16, 15, 15, 15, 14, 14, 14, 11, 9, 6, 5, 5, 6, 8, 8, 10, 12, 15, 17, 20, 20, 21, 21, 21, 21, + ] + g: [ + 19, 18, 17, 15, 13, 12, 10, 9, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 12, 12, 9, 9, 11, 15, 15, 16, 19, 20, + 19, 18, 16, 15, 12, 12, 10, 10, 8, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 7, 7, 11, 14, 15, 16, 19, 19, + 18, 17, 16, 14, 12, 12, 10, 10, 8, 7, 7, 6, 6, 5, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 10, 14, 15, 17, 19, 19, + 17, 17, 16, 14, 12, 12, 10, 10, 8, 7, 6, 6, 6, 5, 5, 4, 5, 5, 6, 6, 6, 7, 7, 5, 6, 6, 9, 13, 15, 17, 18, 20, + 17, 17, 15, 14, 12, 11, 10, 9, 8, 7, 6, 5, 5, 4, 4, 4, 4, 4, 5, 6, 6, 6, 5, 5, 5, 6, 8, 11, 15, 17, 18, 20, + 17, 17, 15, 13, 12, 11, 9, 9, 8, 7, 5, 5, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 7, 11, 15, 17, 18, 18, + 17, 16, 15, 13, 12, 11, 9, 8, 8, 6, 5, 4, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 4, 4, 4, 5, 6, 9, 14, 17, 17, 18, + 17, 16, 14, 13, 12, 11, 9, 8, 7, 6, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 6, 8, 13, 16, 17, 17, + 17, 15, 14, 13, 12, 11, 9, 8, 7, 6, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, 7, 12, 16, 17, 17, + 17, 15, 14, 12, 11, 10, 9, 8, 7, 6, 4, 4, 3, 3, 3, 2, 2, 3, 3, 3, 2, 2, 3, 3, 3, 4, 5, 7, 11, 15, 18, 18, + 16, 14, 13, 12, 11, 10, 9, 7, 7, 5, 4, 3, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 4, 5, 8, 11, 15, 18, 19, + 16, 14, 13, 12, 11, 10, 9, 7, 5, 5, 4, 3, 3, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 3, 4, 5, 8, 10, 15, 18, 19, + 16, 14, 14, 13, 11, 10, 9, 7, 5, 5, 4, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 3, 3, 5, 7, 10, 15, 17, 18, + 16, 15, 14, 12, 11, 10, 9, 7, 5, 5, 4, 3, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 3, 5, 6, 10, 14, 17, 17, + 15, 15, 13, 12, 11, 10, 9, 7, 5, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 3, 5, 7, 10, 14, 17, 18, + 15, 14, 13, 12, 12, 10, 9, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 7, 10, 17, 18, 18, + 15, 14, 14, 13, 12, 11, 9, 7, 6, 4, 3, 2, 1, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 7, 12, 17, 19, 19, + 16, 14, 14, 13, 12, 12, 9, 7, 6, 4, 3, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 4, 4, 5, 8, 12, 17, 19, 20, + 16, 15, 14, 13, 12, 12, 9, 7, 7, 4, 3, 1, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 6, 8, 12, 17, 19, 20, + 17, 15, 14, 13, 12, 12, 9, 7, 7, 5, 3, 1, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 8, 12, 17, 19, 20, + 18, 15, 15, 14, 13, 12, 9, 8, 7, 5, 4, 2, 1, 0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 4, 5, 6, 7, 9, 13, 17, 19, 20, + 18, 16, 15, 14, 13, 12, 9, 9, 7, 6, 5, 3, 2, 1, 0, 0, 0, 1, 2, 3, 3, 3, 4, 5, 6, 7, 9, 10, 14, 18, 20, 20, + 18, 16, 16, 15, 13, 12, 10, 9, 8, 7, 6, 5, 3, 3, 1, 0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 9, 12, 15, 19, 20, 20, + 18, 18, 16, 16, 14, 13, 10, 10, 9, 8, 7, 6, 5, 5, 3, 1, 1, 1, 2, 3, 5, 5, 5, 6, 6, 8, 11, 15, 17, 19, 20, 20, + 18, 18, 16, 16, 14, 13, 12, 10, 9, 9, 8, 7, 6, 5, 4, 3, 1, 2, 3, 5, 5, 5, 5, 6, 7, 10, 12, 15, 18, 20, 20, 20, + 18, 18, 17, 16, 15, 14, 12, 11, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 3, 3, 5, 5, 5, 6, 9, 11, 14, 16, 19, 20, 20, 21, + 19, 19, 18, 17, 16, 15, 13, 12, 11, 10, 10, 9, 8, 7, 5, 4, 4, 3, 3, 3, 5, 5, 6, 7, 10, 12, 14, 18, 20, 20, 21, 22, + 21, 20, 18, 18, 17, 16, 14, 12, 11, 11, 10, 10, 8, 7, 7, 5, 4, 4, 4, 5, 6, 6, 6, 7, 11, 13, 16, 19, 20, 21, 21, 22, + 22, 21, 20, 19, 18, 16, 15, 14, 12, 12, 12, 10, 9, 8, 7, 6, 5, 4, 5, 6, 6, 7, 7, 8, 12, 13, 17, 19, 21, 21, 21, 21, + 23, 22, 21, 20, 18, 17, 16, 16, 14, 14, 13, 12, 10, 10, 8, 7, 6, 5, 5, 6, 7, 7, 8, 9, 12, 15, 18, 19, 21, 21, 21, 20, + 23, 22, 22, 21, 20, 18, 17, 16, 15, 15, 15, 14, 13, 11, 9, 8, 6, 6, 6, 6, 7, 8, 9, 10, 13, 16, 19, 20, 20, 21, 21, 20, + 24, 23, 22, 21, 20, 19, 17, 17, 16, 16, 15, 15, 14, 14, 11, 9, 6, 6, 6, 6, 8, 8, 10, 12, 15, 17, 19, 20, 20, 21, 21, 20, + ] + b: [ + 11, 9, 9, 7, 6, 5, 4, 4, 3, 3, 3, 3, 3, 4, 5, 5, 5, 4, 5, 4, 4, 4, 7, 7, 3, 3, 5, 8, 8, 9, 11, 11, + 11, 10, 8, 7, 5, 5, 5, 4, 3, 3, 3, 3, 3, 3, 4, 4, 5, 4, 5, 4, 4, 4, 4, 4, 2, 2, 5, 8, 8, 9, 11, 11, + 10, 10, 7, 7, 5, 5, 5, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3, 1, 1, 5, 7, 9, 9, 11, 11, + 10, 9, 8, 7, 6, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 2, 1, 1, 4, 7, 9, 10, 12, 13, + 9, 9, 8, 7, 6, 5, 5, 5, 4, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 3, 4, 3, 2, 1, 1, 1, 4, 6, 9, 10, 12, 13, + 9, 9, 9, 7, 6, 6, 5, 4, 4, 3, 3, 3, 2, 2, 2, 2, 3, 3, 2, 1, 2, 2, 2, 1, 1, 1, 2, 6, 9, 10, 11, 12, + 8, 9, 9, 7, 7, 6, 5, 4, 4, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 1, 1, 1, 1, 0, 0, 1, 2, 5, 9, 11, 11, 11, + 8, 9, 9, 7, 7, 6, 5, 4, 4, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 1, 1, 1, 1, 0, 0, 0, 1, 3, 8, 11, 11, 11, + 9, 9, 8, 7, 7, 7, 6, 4, 4, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 1, 1, 1, 1, 0, 0, 0, 1, 3, 7, 11, 11, 11, + 9, 9, 8, 7, 7, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 0, 0, 0, 0, 0, 1, 3, 7, 10, 12, 13, + 9, 9, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 2, 1, 2, 2, 2, 3, 2, 1, 0, 0, 0, 0, 0, 0, 1, 4, 7, 10, 13, 13, + 9, 8, 8, 8, 7, 7, 6, 5, 3, 3, 2, 1, 1, 1, 1, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 1, 2, 4, 7, 11, 13, 13, + 9, 8, 8, 7, 7, 6, 6, 5, 3, 3, 2, 2, 2, 1, 1, 1, 2, 3, 3, 2, 1, 0, 0, 0, 0, 1, 2, 4, 6, 11, 13, 13, + 9, 8, 8, 7, 7, 6, 6, 4, 3, 3, 2, 2, 1, 1, 1, 1, 2, 3, 3, 3, 1, 1, 0, 0, 1, 1, 2, 3, 6, 10, 12, 13, + 9, 8, 8, 7, 7, 6, 6, 4, 3, 3, 2, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 2, 2, 4, 6, 10, 13, 13, + 9, 9, 8, 8, 8, 6, 6, 4, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 3, 3, 1, 1, 1, 1, 2, 2, 3, 4, 6, 13, 14, 14, + 9, 9, 8, 8, 8, 8, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 3, 3, 5, 9, 13, 15, 15, + 10, 9, 9, 9, 10, 10, 6, 6, 5, 3, 2, 1, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 9, 13, 15, 16, + 10, 10, 9, 9, 10, 10, 7, 6, 5, 3, 2, 1, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 9, 13, 15, 16, + 11, 10, 9, 9, 10, 10, 7, 6, 6, 3, 2, 1, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 6, 9, 13, 15, 16, + 12, 10, 10, 10, 10, 10, 7, 6, 6, 4, 3, 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 5, 5, 6, 7, 10, 14, 15, 16, + 12, 11, 10, 10, 10, 10, 8, 7, 6, 6, 4, 3, 2, 1, 1, 1, 1, 2, 2, 3, 4, 4, 4, 4, 5, 5, 6, 8, 11, 15, 16, 16, + 12, 12, 11, 11, 10, 10, 9, 8, 7, 6, 6, 4, 4, 3, 2, 2, 2, 2, 3, 4, 4, 4, 5, 5, 5, 6, 7, 10, 13, 15, 16, 15, + 12, 12, 12, 12, 11, 10, 10, 8, 8, 7, 7, 6, 5, 5, 3, 2, 2, 2, 3, 4, 5, 5, 5, 5, 6, 7, 10, 13, 14, 16, 16, 16, + 12, 12, 13, 12, 12, 11, 11, 9, 8, 8, 8, 7, 6, 6, 5, 4, 3, 3, 3, 5, 6, 6, 6, 6, 7, 9, 11, 14, 15, 17, 17, 16, + 13, 13, 13, 13, 12, 12, 11, 11, 10, 10, 9, 8, 7, 7, 6, 5, 5, 4, 4, 4, 5, 6, 6, 6, 9, 10, 12, 14, 16, 17, 17, 18, + 13, 13, 14, 13, 13, 13, 12, 12, 11, 10, 10, 9, 8, 7, 6, 6, 6, 5, 4, 4, 6, 6, 6, 7, 9, 11, 12, 16, 17, 17, 17, 18, + 15, 15, 15, 15, 14, 13, 13, 13, 12, 11, 11, 10, 9, 9, 8, 7, 6, 5, 5, 5, 6, 7, 7, 8, 10, 12, 14, 17, 17, 18, 17, 17, + 16, 16, 16, 16, 15, 14, 14, 14, 13, 13, 12, 11, 11, 9, 8, 7, 7, 6, 6, 6, 7, 7, 8, 8, 10, 12, 16, 17, 18, 18, 17, 17, + 18, 17, 17, 16, 16, 15, 14, 14, 14, 14, 14, 13, 11, 11, 9, 8, 7, 7, 6, 6, 7, 7, 8, 9, 10, 13, 16, 17, 17, 18, 17, 16, + 18, 17, 17, 17, 16, 15, 15, 15, 15, 15, 15, 14, 14, 12, 10, 9, 7, 7, 6, 6, 7, 7, 8, 9, 11, 14, 16, 16, 17, 17, 16, 15, + 18, 18, 17, 17, 17, 16, 15, 15, 15, 16, 15, 15, 14, 14, 12, 9, 7, 6, 6, 6, 7, 7, 9, 10, 12, 14, 15, 16, 16, 16, 15, 15, + ] + - ct: 5500 + r: [ + 19, 18, 17, 16, 15, 13, 11, 10, 9, 9, 9, 8, 8, 8, 8, 8, 8, 9, 10, 10, 8, 8, 9, 9, 9, 11, 14, 15, 16, 16, 18, 18, + 18, 18, 17, 15, 14, 13, 11, 11, 9, 9, 8, 8, 7, 7, 7, 7, 7, 8, 9, 8, 8, 8, 9, 9, 8, 8, 11, 14, 16, 17, 17, 18, + 18, 17, 17, 15, 14, 13, 12, 11, 9, 9, 8, 7, 7, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 7, 7, 10, 13, 16, 17, 18, 18, + 17, 17, 16, 15, 14, 12, 11, 11, 9, 8, 7, 7, 6, 5, 5, 5, 5, 6, 8, 7, 8, 8, 8, 6, 6, 6, 9, 12, 16, 16, 18, 19, + 17, 17, 16, 14, 13, 12, 11, 10, 10, 8, 7, 7, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 6, 6, 6, 6, 8, 11, 15, 16, 17, 19, + 18, 17, 16, 14, 13, 12, 11, 10, 10, 8, 7, 6, 5, 5, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 10, 14, 16, 17, 18, + 18, 17, 16, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 3, 4, 5, 5, 5, 5, 5, 6, 10, 13, 16, 16, 17, + 18, 16, 15, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 4, 4, 5, 6, 8, 12, 16, 16, 16, + 17, 16, 15, 13, 12, 11, 10, 9, 8, 7, 5, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 5, 7, 11, 16, 16, 16, + 17, 16, 15, 12, 12, 12, 10, 8, 7, 7, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 5, 7, 10, 16, 16, 16, + 16, 16, 14, 12, 12, 11, 10, 8, 7, 6, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 2, 2, 2, 3, 3, 4, 4, 7, 10, 14, 16, 16, + 16, 15, 14, 13, 12, 11, 10, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 2, 2, 3, 3, 3, 5, 7, 10, 14, 15, 16, + 16, 15, 15, 13, 11, 11, 11, 9, 7, 5, 5, 4, 3, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 5, 6, 9, 14, 15, 16, + 16, 15, 15, 13, 11, 11, 11, 10, 7, 5, 4, 4, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 3, 3, 5, 6, 10, 13, 15, 17, + 15, 15, 14, 12, 11, 11, 11, 10, 7, 4, 4, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 3, 3, 5, 6, 9, 13, 16, 17, + 15, 15, 14, 12, 11, 11, 10, 9, 7, 4, 4, 3, 1, 1, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3, 3, 3, 5, 7, 10, 15, 17, 17, + 15, 15, 14, 12, 11, 11, 10, 9, 7, 4, 4, 3, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 5, 7, 11, 16, 17, 18, + 16, 15, 15, 12, 12, 11, 10, 9, 6, 4, 4, 3, 1, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 7, 12, 15, 17, 18, + 16, 16, 15, 12, 12, 11, 10, 8, 6, 5, 4, 3, 1, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 8, 12, 15, 17, 18, + 15, 15, 15, 13, 12, 12, 10, 8, 7, 5, 4, 3, 2, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 4, 5, 7, 9, 12, 16, 17, 18, + 15, 15, 15, 13, 13, 12, 10, 8, 7, 5, 5, 3, 2, 1, 0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 7, 9, 13, 16, 18, 18, + 15, 15, 15, 14, 13, 12, 10, 8, 7, 6, 5, 5, 3, 2, 1, 0, 1, 1, 1, 2, 3, 3, 3, 5, 5, 6, 8, 10, 14, 17, 18, 19, + 16, 16, 16, 15, 13, 12, 10, 9, 8, 7, 6, 6, 5, 4, 2, 1, 1, 1, 1, 2, 3, 3, 5, 5, 6, 7, 9, 12, 15, 18, 18, 19, + 17, 16, 16, 16, 13, 12, 11, 10, 9, 8, 7, 7, 6, 5, 4, 2, 1, 1, 2, 3, 4, 4, 5, 5, 6, 8, 11, 14, 16, 18, 19, 19, + 18, 18, 17, 16, 14, 13, 12, 10, 9, 8, 8, 7, 7, 5, 5, 3, 2, 2, 4, 4, 5, 5, 5, 5, 7, 10, 12, 16, 17, 19, 19, 20, + 18, 18, 17, 16, 15, 13, 12, 10, 10, 9, 9, 9, 7, 6, 5, 4, 3, 3, 4, 4, 5, 5, 5, 6, 7, 11, 15, 17, 18, 19, 19, 20, + 19, 18, 18, 17, 16, 14, 13, 11, 10, 10, 9, 9, 8, 6, 5, 5, 4, 4, 4, 4, 6, 6, 6, 7, 9, 11, 15, 17, 19, 19, 20, 21, + 20, 19, 19, 19, 17, 16, 13, 12, 11, 11, 10, 9, 9, 7, 6, 5, 5, 4, 4, 5, 6, 7, 7, 8, 9, 12, 15, 18, 19, 19, 20, 20, + 21, 20, 20, 19, 19, 16, 16, 13, 12, 12, 11, 11, 9, 8, 7, 6, 6, 5, 5, 6, 7, 7, 8, 8, 10, 13, 17, 19, 19, 20, 20, 20, + 22, 21, 20, 20, 19, 17, 16, 14, 13, 13, 14, 12, 11, 9, 8, 7, 6, 5, 5, 6, 7, 7, 8, 9, 11, 15, 17, 19, 19, 20, 20, 20, + 22, 22, 21, 20, 19, 18, 16, 15, 14, 14, 15, 15, 13, 11, 9, 8, 7, 5, 5, 6, 7, 8, 9, 10, 13, 16, 18, 18, 19, 20, 19, 19, + 22, 22, 21, 20, 19, 19, 16, 16, 15, 15, 15, 15, 15, 13, 10, 9, 7, 5, 5, 6, 8, 8, 10, 11, 14, 16, 18, 18, 19, 19, 19, 19, + ] + g: [ + 16, 16, 15, 14, 13, 11, 10, 9, 8, 7, 7, 7, 6, 6, 6, 6, 6, 7, 8, 8, 6, 6, 7, 7, 7, 9, 12, 13, 13, 14, 14, 14, + 16, 16, 15, 14, 13, 11, 10, 9, 8, 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 7, 6, 6, 10, 12, 13, 14, 14, 14, + 16, 15, 15, 13, 13, 11, 10, 9, 8, 7, 7, 6, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 5, 5, 8, 12, 14, 15, 15, 16, + 15, 15, 14, 13, 12, 11, 10, 10, 8, 7, 6, 6, 5, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 5, 5, 5, 7, 11, 14, 15, 15, 17, + 16, 15, 14, 13, 12, 11, 10, 10, 9, 7, 6, 6, 4, 4, 4, 3, 3, 4, 4, 6, 6, 5, 5, 4, 4, 5, 6, 10, 14, 14, 16, 16, + 16, 15, 15, 13, 12, 11, 10, 10, 9, 7, 6, 6, 4, 4, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 9, 13, 14, 15, 16, + 16, 15, 15, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 5, 8, 13, 14, 15, 15, + 16, 15, 14, 12, 11, 10, 9, 8, 7, 6, 6, 5, 4, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3, 4, 5, 7, 11, 14, 15, 15, + 16, 15, 14, 12, 11, 10, 9, 8, 7, 6, 5, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3, 3, 4, 6, 10, 14, 15, 14, + 16, 15, 14, 12, 11, 11, 9, 8, 7, 6, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 4, 6, 10, 15, 15, 15, + 15, 15, 13, 12, 11, 11, 10, 8, 7, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3, 6, 9, 13, 15, 15, + 15, 14, 13, 12, 11, 11, 10, 8, 7, 5, 5, 3, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 4, 6, 9, 13, 14, 15, + 15, 14, 14, 13, 11, 11, 10, 8, 7, 5, 5, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 1, 2, 2, 2, 4, 5, 8, 13, 14, 15, + 15, 14, 14, 13, 11, 11, 11, 10, 7, 5, 4, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 1, 2, 2, 2, 4, 5, 8, 13, 14, 15, + 15, 14, 13, 12, 11, 11, 10, 9, 7, 4, 4, 3, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 5, 8, 13, 15, 16, + 15, 15, 13, 12, 11, 11, 10, 9, 7, 4, 4, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 6, 8, 15, 16, 16, + 15, 15, 14, 12, 11, 11, 10, 9, 7, 4, 4, 2, 1, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 6, 11, 15, 16, 17, + 15, 15, 15, 12, 12, 11, 10, 10, 7, 4, 4, 2, 1, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 6, 11, 15, 17, 17, + 15, 15, 15, 12, 12, 12, 10, 9, 7, 5, 4, 2, 1, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 5, 7, 11, 15, 17, 17, + 15, 15, 15, 13, 12, 12, 10, 9, 7, 5, 4, 3, 2, 0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 6, 8, 12, 15, 17, 17, + 15, 15, 15, 13, 13, 12, 10, 9, 7, 6, 5, 3, 2, 1, 0, 0, 1, 1, 1, 2, 3, 3, 3, 3, 4, 5, 6, 9, 13, 16, 17, 18, + 15, 15, 15, 14, 13, 13, 10, 9, 8, 6, 6, 5, 3, 2, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 10, 14, 17, 18, 18, + 15, 16, 16, 15, 13, 13, 11, 9, 8, 7, 6, 6, 5, 4, 2, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 12, 15, 17, 18, 18, + 16, 16, 16, 16, 13, 13, 11, 10, 9, 8, 7, 7, 6, 6, 4, 2, 2, 2, 2, 3, 5, 5, 5, 5, 6, 8, 11, 14, 16, 18, 18, 18, + 17, 17, 17, 16, 14, 13, 13, 11, 10, 9, 8, 8, 7, 6, 5, 4, 2, 2, 4, 5, 5, 5, 5, 5, 7, 9, 12, 15, 17, 18, 18, 18, + 18, 18, 17, 16, 15, 14, 13, 11, 10, 10, 9, 9, 7, 6, 5, 5, 4, 3, 4, 4, 5, 5, 5, 6, 7, 10, 15, 16, 18, 18, 18, 19, + 19, 18, 18, 17, 16, 14, 13, 12, 11, 10, 10, 9, 9, 7, 6, 5, 5, 4, 4, 4, 6, 6, 6, 6, 8, 10, 15, 16, 18, 18, 18, 19, + 20, 19, 19, 19, 17, 16, 14, 13, 12, 11, 11, 10, 9, 7, 7, 6, 5, 4, 4, 5, 6, 6, 6, 7, 9, 11, 15, 17, 18, 18, 18, 18, + 22, 20, 20, 20, 19, 17, 16, 14, 13, 13, 12, 11, 10, 9, 7, 6, 6, 5, 5, 6, 7, 7, 7, 8, 9, 12, 16, 18, 18, 19, 18, 18, + 22, 22, 21, 20, 19, 18, 17, 16, 14, 14, 15, 13, 11, 10, 9, 8, 7, 6, 5, 6, 7, 8, 8, 9, 10, 14, 17, 18, 18, 19, 18, 17, + 22, 22, 22, 21, 20, 19, 17, 17, 16, 16, 16, 16, 14, 12, 10, 8, 7, 6, 6, 7, 8, 8, 8, 10, 13, 16, 18, 18, 18, 18, 18, 17, + 22, 22, 22, 21, 20, 20, 18, 17, 16, 16, 17, 17, 16, 14, 11, 10, 8, 6, 6, 7, 8, 8, 10, 11, 14, 17, 18, 18, 18, 18, 18, 17, + ] + b: [ + 13, 12, 12, 12, 11, 9, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 6, 7, 7, 6, 6, 5, 5, 5, 6, 9, 9, 9, 10, 9, 8, + 13, 13, 12, 11, 11, 9, 9, 8, 7, 7, 7, 6, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 5, 5, 4, 4, 7, 9, 9, 10, 10, 9, + 13, 13, 12, 11, 11, 9, 9, 8, 7, 7, 6, 6, 5, 4, 4, 4, 4, 5, 6, 6, 6, 5, 5, 5, 3, 3, 6, 9, 10, 11, 10, 11, + 13, 13, 12, 11, 11, 10, 9, 9, 7, 7, 6, 5, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 4, 3, 3, 5, 8, 10, 11, 11, 12, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 7, 6, 6, 4, 4, 3, 3, 3, 4, 4, 5, 5, 5, 4, 3, 2, 3, 4, 8, 11, 11, 11, 12, + 13, 13, 13, 11, 11, 10, 9, 9, 8, 7, 6, 6, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 7, 11, 11, 11, 12, + 14, 14, 13, 11, 11, 10, 9, 8, 8, 7, 6, 6, 4, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 3, 6, 11, 11, 11, 11, + 14, 14, 13, 11, 11, 10, 9, 8, 7, 7, 6, 5, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 11, 11, 11, + 14, 14, 13, 12, 11, 11, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 8, 12, 12, 11, + 14, 13, 13, 12, 12, 11, 10, 8, 7, 6, 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 8, 12, 12, 13, + 13, 13, 13, 12, 12, 12, 11, 9, 7, 6, 4, 3, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 4, 8, 12, 12, 13, + 14, 13, 13, 12, 12, 11, 11, 9, 7, 6, 5, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 4, 8, 12, 13, 13, + 14, 13, 13, 13, 12, 11, 11, 9, 7, 6, 5, 4, 2, 1, 1, 1, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 4, 7, 12, 13, 13, + 14, 13, 13, 13, 12, 12, 12, 11, 7, 5, 5, 4, 2, 1, 1, 1, 1, 3, 3, 3, 2, 2, 2, 2, 2, 3, 3, 4, 7, 12, 13, 13, + 14, 14, 13, 13, 12, 12, 11, 11, 7, 5, 4, 3, 1, 1, 1, 1, 1, 2, 3, 3, 2, 2, 2, 3, 3, 3, 4, 4, 7, 12, 13, 14, + 14, 14, 13, 13, 12, 12, 11, 11, 8, 5, 4, 3, 1, 1, 0, 0, 1, 1, 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, 8, 13, 15, 15, + 14, 15, 14, 13, 13, 12, 11, 11, 8, 5, 4, 3, 1, 0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 10, 15, 15, 16, + 15, 15, 15, 14, 13, 13, 12, 11, 8, 5, 4, 3, 1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 6, 11, 15, 16, 16, + 15, 15, 15, 14, 14, 14, 12, 11, 8, 6, 5, 3, 1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 6, 7, 11, 15, 16, 16, + 15, 15, 15, 15, 14, 14, 12, 11, 9, 7, 5, 3, 2, 1, 0, 1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 5, 6, 8, 12, 15, 16, 16, + 15, 16, 15, 15, 15, 14, 13, 11, 9, 7, 6, 5, 3, 2, 1, 1, 1, 1, 2, 3, 3, 4, 4, 4, 5, 6, 7, 9, 12, 16, 16, 16, + 15, 16, 16, 15, 15, 15, 13, 11, 10, 8, 7, 6, 4, 3, 2, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 10, 14, 16, 16, 16, + 16, 16, 17, 16, 15, 15, 14, 12, 11, 9, 8, 8, 6, 5, 3, 2, 2, 2, 3, 3, 5, 5, 6, 6, 7, 8, 9, 12, 15, 16, 16, 16, + 16, 17, 18, 17, 16, 15, 14, 13, 11, 10, 9, 9, 8, 6, 5, 3, 3, 3, 3, 4, 6, 6, 6, 6, 7, 8, 11, 14, 16, 16, 16, 16, + 17, 18, 18, 18, 17, 16, 16, 14, 12, 11, 10, 9, 8, 7, 6, 5, 3, 3, 5, 6, 6, 6, 6, 6, 8, 10, 12, 16, 17, 17, 17, 16, + 18, 18, 18, 18, 18, 17, 16, 14, 13, 12, 11, 11, 8, 8, 6, 6, 5, 4, 5, 5, 6, 6, 6, 7, 8, 11, 15, 16, 17, 17, 16, 16, + 18, 19, 19, 19, 19, 17, 17, 15, 14, 13, 12, 11, 11, 8, 7, 6, 6, 5, 5, 5, 7, 7, 7, 8, 9, 11, 15, 17, 17, 17, 16, 16, + 20, 20, 20, 20, 19, 19, 17, 17, 15, 14, 14, 12, 11, 9, 8, 7, 7, 6, 6, 6, 8, 8, 8, 8, 9, 12, 15, 18, 18, 16, 16, 16, + 22, 22, 22, 22, 21, 20, 19, 18, 17, 16, 15, 14, 12, 11, 10, 8, 8, 7, 7, 7, 8, 8, 8, 9, 10, 13, 17, 18, 18, 16, 16, 15, + 23, 22, 22, 22, 22, 21, 20, 20, 18, 18, 19, 16, 14, 13, 11, 10, 9, 8, 7, 7, 8, 9, 9, 10, 11, 15, 17, 18, 17, 17, 16, 14, + 23, 23, 23, 23, 23, 22, 21, 21, 20, 20, 20, 19, 18, 15, 12, 11, 10, 8, 8, 8, 9, 9, 10, 11, 13, 17, 17, 17, 17, 16, 15, 13, + 23, 23, 24, 24, 23, 23, 22, 21, 21, 21, 20, 20, 19, 17, 14, 12, 11, 9, 8, 9, 9, 10, 10, 12, 15, 17, 17, 17, 16, 16, 15, 13, + ] + - ct: 8500 + r: [ + 18, 17, 16, 15, 13, 12, 10, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 8, 8, 10, 13, 14, 15, 17, 18, + 17, 17, 16, 14, 12, 11, 10, 10, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 10, 13, 14, 15, 17, 17, + 17, 16, 15, 13, 12, 11, 10, 10, 8, 8, 7, 7, 7, 6, 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 6, 6, 9, 12, 14, 15, 17, 17, + 16, 16, 15, 13, 12, 11, 10, 10, 9, 7, 7, 7, 6, 6, 5, 5, 6, 6, 7, 7, 7, 7, 7, 5, 5, 5, 8, 11, 14, 15, 17, 19, + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 7, 6, 6, 5, 5, 5, 5, 5, 5, 6, 6, 6, 5, 5, 5, 5, 7, 10, 13, 15, 17, 19, + 15, 15, 14, 13, 12, 11, 9, 9, 8, 7, 6, 5, 5, 5, 4, 4, 5, 5, 5, 4, 4, 4, 4, 4, 4, 5, 6, 9, 13, 15, 16, 17, + 15, 15, 13, 12, 11, 11, 9, 8, 8, 6, 5, 5, 4, 4, 4, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 5, 8, 13, 15, 15, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 5, 7, 11, 14, 15, 15, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 2, 3, 3, 3, 3, 4, 6, 10, 14, 15, 15, + 15, 13, 13, 11, 11, 10, 9, 8, 7, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3, 4, 6, 10, 13, 16, 16, + 14, 13, 12, 11, 10, 10, 9, 7, 7, 5, 4, 3, 3, 3, 3, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 3, 4, 6, 9, 13, 16, 17, + 14, 13, 12, 11, 10, 10, 9, 7, 6, 5, 4, 3, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 1, 2, 2, 3, 4, 6, 9, 13, 16, 17, + 14, 13, 12, 12, 10, 10, 9, 7, 5, 5, 4, 3, 3, 2, 2, 2, 2, 3, 3, 3, 2, 2, 1, 1, 2, 3, 4, 6, 9, 13, 15, 17, + 14, 13, 12, 11, 10, 9, 9, 7, 5, 5, 4, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 1, 1, 2, 3, 4, 6, 9, 12, 15, 16, + 13, 13, 12, 11, 10, 9, 8, 7, 5, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 3, 4, 6, 8, 12, 15, 16, + 13, 13, 12, 11, 11, 9, 8, 7, 6, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 3, 3, 4, 6, 9, 15, 16, 16, + 13, 13, 12, 12, 11, 10, 9, 7, 6, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 6, 10, 15, 17, 18, + 14, 13, 13, 12, 12, 11, 9, 7, 6, 4, 3, 2, 1, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 7, 11, 15, 17, 18, + 14, 13, 13, 12, 12, 11, 9, 7, 7, 4, 3, 1, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 5, 7, 11, 15, 17, 18, + 15, 13, 13, 12, 12, 11, 8, 7, 6, 4, 3, 1, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5, 6, 7, 11, 15, 17, 18, + 15, 14, 13, 13, 12, 11, 8, 8, 7, 5, 4, 2, 1, 0, 0, 0, 0, 1, 2, 2, 2, 2, 3, 3, 5, 5, 7, 8, 11, 15, 17, 18, + 15, 14, 14, 13, 12, 11, 9, 8, 7, 6, 5, 3, 2, 1, 0, 0, 0, 1, 2, 2, 3, 3, 3, 5, 5, 6, 8, 9, 13, 16, 18, 18, + 15, 14, 14, 13, 12, 11, 9, 9, 8, 7, 6, 5, 3, 3, 1, 0, 0, 1, 2, 2, 3, 4, 4, 5, 5, 6, 8, 11, 14, 17, 18, 18, + 16, 16, 15, 14, 13, 12, 10, 9, 8, 8, 7, 6, 5, 5, 3, 1, 1, 1, 2, 3, 4, 4, 5, 5, 6, 8, 10, 13, 15, 17, 18, 18, + 16, 16, 15, 14, 14, 12, 11, 10, 9, 9, 8, 7, 6, 5, 4, 3, 1, 1, 2, 4, 4, 5, 5, 5, 7, 9, 11, 14, 16, 18, 18, 19, + 16, 16, 15, 15, 14, 13, 12, 11, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 3, 3, 4, 5, 5, 6, 9, 10, 13, 15, 18, 18, 19, 19, + 17, 17, 16, 15, 15, 14, 12, 11, 11, 10, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 5, 5, 6, 6, 9, 11, 13, 17, 18, 19, 19, 20, + 19, 18, 17, 17, 15, 15, 13, 12, 11, 11, 10, 10, 9, 8, 7, 5, 5, 4, 4, 5, 6, 6, 6, 7, 10, 12, 15, 18, 19, 19, 19, 20, + 19, 19, 18, 17, 17, 16, 14, 13, 12, 12, 11, 10, 9, 8, 7, 6, 6, 5, 5, 6, 6, 6, 7, 8, 11, 12, 16, 18, 19, 19, 19, 19, + 20, 19, 19, 18, 17, 16, 15, 14, 13, 13, 12, 12, 10, 9, 8, 7, 6, 5, 5, 6, 6, 7, 8, 9, 11, 14, 17, 18, 19, 19, 19, 19, + 20, 20, 19, 18, 18, 17, 15, 15, 14, 14, 14, 13, 12, 11, 9, 7, 6, 5, 5, 6, 7, 7, 8, 9, 12, 15, 17, 18, 18, 19, 18, 18, + 21, 20, 20, 19, 18, 18, 15, 15, 14, 14, 14, 14, 13, 13, 11, 9, 6, 5, 5, 6, 7, 7, 9, 10, 13, 15, 18, 18, 18, 18, 18, 18, + ] + g: [ + 16, 16, 15, 13, 12, 10, 9, 8, 7, 7, 7, 6, 6, 6, 7, 7, 7, 6, 6, 6, 6, 6, 11, 11, 6, 6, 9, 12, 12, 13, 15, 15, + 16, 15, 14, 13, 11, 10, 9, 9, 8, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 9, 12, 12, 13, 15, 15, + 15, 15, 14, 12, 11, 11, 9, 9, 8, 7, 6, 6, 6, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 5, 4, 5, 8, 11, 13, 14, 15, 15, + 15, 15, 14, 12, 11, 10, 9, 9, 8, 6, 6, 6, 5, 5, 4, 4, 4, 4, 6, 6, 6, 6, 6, 4, 4, 4, 7, 11, 13, 14, 15, 17, + 15, 15, 13, 12, 11, 10, 9, 9, 8, 6, 6, 5, 5, 4, 4, 3, 3, 4, 4, 5, 5, 5, 4, 4, 3, 4, 6, 9, 13, 14, 15, 17, + 15, 14, 13, 12, 11, 10, 9, 8, 8, 6, 5, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 5, 8, 13, 14, 14, 15, + 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 4, 7, 12, 14, 14, 14, + 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3, 3, 4, 6, 11, 14, 14, 14, + 14, 13, 12, 11, 11, 10, 9, 8, 7, 6, 4, 4, 3, 3, 3, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3, 3, 5, 10, 14, 14, 14, + 15, 13, 12, 11, 11, 10, 9, 8, 7, 6, 4, 4, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 5, 10, 13, 15, 16, + 14, 13, 12, 11, 11, 10, 9, 7, 7, 5, 4, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 1, 2, 2, 2, 2, 3, 6, 9, 13, 16, 16, + 14, 13, 12, 11, 10, 10, 9, 7, 5, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 4, 6, 8, 13, 16, 16, + 14, 13, 13, 12, 10, 10, 9, 7, 5, 5, 4, 3, 2, 2, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 4, 5, 8, 13, 15, 16, + 14, 13, 13, 12, 10, 10, 9, 7, 5, 5, 4, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 3, 5, 8, 12, 14, 15, + 14, 13, 12, 11, 10, 10, 9, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 3, 3, 5, 8, 12, 15, 15, + 14, 13, 12, 11, 11, 10, 9, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2, 3, 4, 5, 8, 14, 16, 16, + 14, 13, 13, 12, 12, 10, 9, 7, 6, 4, 3, 2, 1, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 10, 15, 16, 17, + 14, 13, 13, 13, 12, 12, 9, 7, 7, 4, 3, 2, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 6, 11, 15, 17, 17, + 14, 14, 13, 12, 12, 12, 9, 8, 7, 5, 3, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 7, 11, 15, 17, 17, + 15, 14, 13, 13, 12, 12, 9, 8, 7, 5, 3, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 7, 11, 15, 17, 17, + 16, 14, 14, 13, 13, 12, 9, 8, 7, 5, 4, 2, 1, 0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 3, 5, 5, 6, 8, 12, 16, 17, 17, + 16, 15, 14, 14, 13, 12, 10, 9, 7, 6, 5, 4, 2, 1, 0, 0, 0, 1, 2, 3, 3, 3, 3, 5, 5, 6, 8, 9, 13, 17, 17, 17, + 16, 15, 15, 14, 13, 12, 10, 10, 8, 7, 6, 5, 4, 3, 2, 0, 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 8, 11, 14, 17, 17, 17, + 16, 16, 15, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 5, 3, 2, 1, 1, 2, 3, 5, 5, 5, 5, 6, 7, 10, 13, 16, 17, 17, 17, + 17, 17, 15, 15, 14, 13, 12, 11, 10, 9, 9, 7, 6, 6, 5, 3, 2, 2, 3, 4, 5, 5, 5, 6, 6, 9, 11, 14, 16, 18, 18, 18, + 17, 17, 16, 15, 15, 14, 13, 12, 11, 10, 10, 9, 7, 7, 5, 4, 3, 2, 3, 4, 5, 5, 5, 6, 9, 10, 13, 15, 18, 18, 18, 18, + 18, 17, 17, 16, 15, 15, 14, 12, 11, 11, 11, 9, 8, 7, 6, 5, 4, 3, 3, 4, 5, 5, 6, 6, 9, 11, 13, 17, 18, 18, 18, 18, + 20, 19, 18, 18, 16, 16, 14, 13, 12, 11, 11, 10, 9, 8, 7, 5, 5, 4, 4, 5, 6, 6, 6, 7, 10, 11, 15, 18, 18, 18, 18, 18, + 20, 20, 19, 18, 18, 17, 15, 14, 13, 13, 12, 11, 10, 9, 8, 6, 6, 5, 5, 6, 6, 7, 7, 8, 11, 12, 16, 18, 18, 18, 18, 18, + 22, 21, 20, 19, 18, 17, 17, 17, 15, 15, 14, 13, 11, 10, 8, 7, 6, 6, 6, 6, 7, 7, 8, 8, 11, 14, 17, 18, 18, 18, 18, 17, + 22, 22, 21, 20, 19, 18, 17, 17, 17, 16, 16, 15, 14, 12, 10, 8, 7, 6, 6, 7, 7, 8, 8, 10, 13, 16, 18, 18, 18, 18, 18, 16, + 22, 22, 22, 21, 20, 19, 18, 17, 17, 17, 16, 16, 15, 15, 12, 10, 7, 6, 6, 7, 8, 8, 9, 11, 14, 16, 18, 18, 18, 18, 17, 16, + ] + b: [ + 13, 13, 13, 11, 10, 9, 9, 8, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 4, 4, 7, 9, 9, 9, 10, 10, + 13, 13, 12, 11, 10, 9, 9, 8, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 7, 9, 9, 10, 10, 10, + 13, 13, 12, 11, 10, 10, 9, 9, 7, 7, 6, 6, 6, 5, 5, 5, 5, 6, 6, 6, 5, 5, 5, 4, 3, 3, 6, 9, 10, 11, 11, 11, + 13, 13, 12, 11, 10, 10, 9, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, 4, 5, 5, 5, 5, 5, 3, 2, 3, 5, 9, 10, 11, 11, 13, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 7, 6, 5, 5, 4, 4, 3, 3, 4, 4, 5, 5, 5, 3, 2, 2, 3, 5, 8, 11, 11, 12, 13, + 13, 12, 12, 11, 11, 10, 9, 8, 8, 7, 5, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 7, 11, 11, 11, 11, + 13, 13, 12, 11, 11, 10, 9, 8, 8, 7, 5, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 3, 6, 11, 11, 11, 11, + 13, 13, 12, 11, 11, 11, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 5, 10, 12, 12, 11, + 13, 13, 13, 11, 11, 11, 10, 8, 7, 6, 5, 4, 3, 3, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 12, 12, 12, + 14, 13, 13, 12, 11, 11, 10, 8, 7, 6, 4, 4, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 12, 13, 14, + 13, 13, 12, 12, 11, 11, 10, 8, 7, 6, 4, 3, 2, 2, 2, 1, 2, 3, 3, 2, 2, 1, 2, 2, 2, 2, 2, 4, 8, 12, 14, 14, + 13, 13, 12, 12, 11, 11, 11, 8, 7, 6, 5, 3, 2, 2, 1, 1, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 4, 8, 12, 14, 14, + 13, 13, 12, 12, 12, 11, 11, 8, 7, 6, 5, 3, 2, 1, 1, 1, 1, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 4, 8, 12, 14, 14, + 13, 13, 13, 12, 12, 11, 11, 8, 6, 6, 5, 3, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 7, 12, 13, 14, + 13, 13, 13, 12, 12, 11, 10, 8, 6, 5, 4, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 5, 7, 12, 14, 14, + 14, 14, 13, 13, 13, 11, 10, 8, 7, 5, 4, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 4, 4, 5, 8, 14, 16, 16, + 14, 14, 13, 13, 13, 12, 11, 9, 7, 5, 4, 2, 1, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, 6, 10, 15, 16, 16, + 15, 14, 14, 14, 15, 15, 11, 9, 8, 5, 4, 2, 1, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 7, 11, 15, 16, 17, + 15, 15, 14, 14, 15, 15, 12, 10, 9, 6, 4, 2, 1, 0, 0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 11, 15, 16, 16, + 15, 15, 15, 15, 15, 15, 12, 10, 9, 6, 5, 2, 1, 0, 0, 0, 1, 1, 1, 3, 3, 3, 4, 4, 5, 6, 6, 7, 11, 15, 16, 17, + 16, 16, 15, 16, 15, 15, 12, 11, 10, 7, 5, 3, 2, 1, 0, 0, 1, 1, 2, 3, 4, 4, 4, 5, 6, 6, 7, 9, 12, 16, 16, 16, + 16, 16, 16, 16, 16, 15, 13, 12, 10, 9, 6, 5, 3, 2, 1, 1, 1, 2, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 14, 16, 17, 16, + 16, 16, 16, 16, 16, 15, 14, 12, 11, 10, 8, 6, 5, 4, 2, 1, 1, 2, 3, 4, 5, 5, 6, 6, 6, 7, 9, 12, 15, 16, 16, 16, + 17, 17, 18, 17, 16, 16, 14, 13, 12, 11, 9, 8, 6, 5, 4, 2, 2, 2, 3, 4, 6, 6, 6, 6, 7, 8, 11, 14, 16, 17, 16, 16, + 17, 17, 18, 18, 17, 16, 16, 14, 13, 12, 11, 9, 8, 7, 5, 4, 2, 3, 4, 6, 6, 6, 6, 7, 8, 10, 12, 15, 17, 17, 17, 16, + 18, 18, 18, 18, 18, 17, 16, 15, 14, 13, 12, 11, 9, 8, 6, 5, 4, 4, 4, 5, 6, 6, 7, 7, 10, 11, 14, 16, 17, 17, 17, 17, + 18, 18, 19, 19, 19, 18, 17, 16, 15, 14, 14, 11, 10, 9, 7, 6, 6, 5, 5, 5, 6, 7, 7, 7, 10, 12, 14, 17, 17, 17, 17, 17, + 20, 20, 20, 20, 20, 19, 18, 17, 16, 15, 14, 13, 11, 10, 9, 7, 6, 6, 6, 6, 7, 7, 7, 8, 11, 12, 15, 18, 18, 17, 17, 16, + 22, 21, 21, 21, 21, 21, 20, 19, 17, 16, 16, 14, 13, 11, 10, 8, 7, 7, 7, 7, 8, 8, 8, 9, 11, 13, 17, 18, 18, 17, 16, 15, + 23, 22, 22, 22, 22, 21, 21, 20, 19, 19, 18, 16, 14, 13, 11, 9, 8, 8, 8, 8, 8, 8, 9, 10, 12, 15, 18, 18, 18, 17, 16, 14, + 23, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 18, 15, 13, 11, 9, 8, 8, 8, 9, 9, 10, 11, 13, 16, 18, 18, 17, 17, 15, 14, + 24, 24, 24, 24, 24, 23, 22, 22, 22, 22, 20, 20, 19, 18, 15, 13, 10, 9, 9, 9, 9, 10, 11, 12, 15, 17, 18, 18, 17, 16, 15, 13, + ] +... diff --git a/src/ipa/mali-c55/data/meson.build b/src/ipa/mali-c55/data/meson.build new file mode 100644 index 00000000..8a5fdd36 --- /dev/null +++ b/src/ipa/mali-c55/data/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: CC0-1.0 + +conf_files = files([ + 'imx415.yaml', + 'uncalibrated.yaml' +]) + +install_data(conf_files, + install_dir : ipa_data_dir / 'mali-c55') diff --git a/src/ipa/mali-c55/data/uncalibrated.yaml b/src/ipa/mali-c55/data/uncalibrated.yaml new file mode 100644 index 00000000..6dcc0295 --- /dev/null +++ b/src/ipa/mali-c55/data/uncalibrated.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +version: 1 +algorithms: + - Agc: +... diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp new file mode 100644 index 00000000..99f76ecd --- /dev/null +++ b/src/ipa/mali-c55/ipa_context.cpp @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * ipa_context.cpp - MaliC55 IPA Context + */ + +#include "ipa_context.h" + +/** + * \file ipa_context.h + * \brief Context and state information shared between the algorithms + */ + +namespace libcamera::ipa::mali_c55 { + +/** + * \struct IPASessionConfiguration + * \brief Session configuration for the IPA module + * + * The session configuration contains all IPA configuration parameters that + * remain constant during the capture session, from IPA module start to stop. + * It is typically set during the configure() operation of the IPA module, but + * may also be updated in the start() operation. + */ + +/** + * \struct IPAActiveState + * \brief Active state for algorithms + * + * The active state contains all algorithm-specific data that needs to be + * maintained by algorithms across frames. Unlike the session configuration, + * the active state is mutable and constantly updated by algorithms. The active + * state is accessible through the IPAContext structure. + * + * The active state stores two distinct categories of information: + * + * - The consolidated value of all algorithm controls. Requests passed to + * the queueRequest() function store values for controls that the + * application wants to modify for that particular frame, and the + * queueRequest() function updates the active state with those values. + * The active state thus contains a consolidated view of the value of all + * controls handled by the algorithm. + * + * - The value of parameters computed by the algorithm when running in auto + * mode. Algorithms running in auto mode compute new parameters every + * time statistics buffers are received (either synchronously, or + * possibly in a background thread). The latest computed value of those + * parameters is stored in the active state in the process() function. + * + * Each of the members in the active state belongs to a specific algorithm. A + * member may be read by any algorithm, but shall only be written by its owner. + */ + +/** + * \struct IPAFrameContext + * \brief Per-frame context for algorithms + * + * The frame context stores two distinct categories of information: + * + * - The value of the controls to be applied to the frame. These values are + * typically set in the queueRequest() function, from the consolidated + * control values stored in the active state. The frame context thus stores + * values for all controls related to the algorithm, not limited to the + * controls specified in the corresponding request, but consolidated from all + * requests that have been queued so far. + * + * For controls that can be set manually or computed by an algorithm + * (depending on the algorithm operation mode), such as for instance the + * colour gains for the AWB algorithm, the control value will be stored in + * the frame context in the queueRequest() function only when operating in + * manual mode. When operating in auto mode, the values are computed by the + * algorithm in process(), stored in the active state, and copied to the + * frame context in prepare(), just before being stored in the ISP parameters + * buffer. + * + * The queueRequest() function can also store ancillary data in the frame + * context, such as flags to indicate if (and what) control values have + * changed compared to the previous request. + * + * - Status information computed by the algorithm for a frame. For instance, + * the colour temperature estimated by the AWB algorithm from ISP statistics + * calculated on a frame is stored in the frame context for that frame in + * the process() function. + */ + +/** + * \struct IPAContext + * \brief Global IPA context data shared between all algorithms + * + * \var IPAContext::configuration + * \brief The IPA session configuration, immutable during the session + * + * \var IPAContext::activeState + * \brief The IPA active state, storing the latest state for all algorithms + * + * \var IPAContext::frameContexts + * \brief Ring buffer of per-frame contexts + */ + +} /* namespace libcamera::ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h new file mode 100644 index 00000000..5e3e2fbd --- /dev/null +++ b/src/ipa/mali-c55/ipa_context.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * ipa_context.h - Mali-C55 IPA Context + */ + +#pragma once + +#include <libcamera/base/utils.h> +#include <libcamera/controls.h> + +#include "libcamera/internal/bayer_format.h" + +#include <libipa/fc_queue.h> + +namespace libcamera { + +namespace ipa::mali_c55 { + +struct IPASessionConfiguration { + struct { + utils::Duration minShutterSpeed; + utils::Duration maxShutterSpeed; + uint32_t defaultExposure; + double minAnalogueGain; + double maxAnalogueGain; + } agc; + + struct { + BayerFormat::Order bayerOrder; + utils::Duration lineDuration; + uint32_t blackLevel; + } sensor; +}; + +struct IPAActiveState { + struct { + struct { + uint32_t exposure; + double sensorGain; + double ispGain; + } automatic; + struct { + uint32_t exposure; + double sensorGain; + double ispGain; + } manual; + bool autoEnabled; + uint32_t constraintMode; + uint32_t exposureMode; + uint32_t temperatureK; + } agc; + + struct { + double rGain; + double bGain; + } awb; +}; + +struct IPAFrameContext : public FrameContext { + struct { + uint32_t exposure; + double sensorGain; + double ispGain; + } agc; + + struct { + double rGain; + double bGain; + } awb; +}; + +struct IPAContext { + IPAContext(unsigned int frameContextSize) + : frameContexts(frameContextSize) + { + } + + IPASessionConfiguration configuration; + IPAActiveState activeState; + + FCQueue<IPAFrameContext> frameContexts; + + ControlInfoMap::Map ctrlMap; +}; + +} /* namespace ipa::mali_c55 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp new file mode 100644 index 00000000..c6941a95 --- /dev/null +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Ideas on Board Oy + * + * mali-c55.cpp - Mali-C55 ISP image processing algorithms + */ + +#include <map> +#include <string.h> +#include <vector> + +#include <linux/mali-c55-config.h> +#include <linux/v4l2-controls.h> + +#include <libcamera/base/file.h> +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> +#include <libcamera/ipa/ipa_interface.h> +#include <libcamera/ipa/ipa_module_info.h> +#include <libcamera/ipa/mali-c55_ipa_interface.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/mapped_framebuffer.h" +#include "libcamera/internal/yaml_parser.h" + +#include "algorithms/algorithm.h" +#include "libipa/camera_sensor_helper.h" + +#include "ipa_context.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPAMaliC55) + +using namespace std::literals::chrono_literals; + +namespace ipa::mali_c55 { + +/* Maximum number of frame contexts to be held */ +static constexpr uint32_t kMaxFrameContexts = 16; + +class IPAMaliC55 : public IPAMaliC55Interface, public Module +{ +public: + IPAMaliC55(); + + int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, + ControlInfoMap *ipaControls) override; + int start() override; + void stop() override; + int configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, + ControlInfoMap *ipaControls) override; + void mapBuffers(const std::vector<IPABuffer> &buffers, bool readOnly) override; + void unmapBuffers(const std::vector<IPABuffer> &buffers) override; + void queueRequest(const uint32_t request, const ControlList &controls) override; + void fillParams(unsigned int request, uint32_t bufferId) override; + void processStats(unsigned int request, unsigned int bufferId, + const ControlList &sensorControls) override; + +protected: + std::string logPrefix() const override; + +private: + void updateSessionConfiguration(const IPACameraSensorInfo &info, + const ControlInfoMap &sensorControls, + BayerFormat::Order bayerOrder); + void updateControls(const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls); + void setControls(); + + std::map<unsigned int, MappedFrameBuffer> buffers_; + + ControlInfoMap sensorControls_; + + /* Interface to the Camera Helper */ + std::unique_ptr<CameraSensorHelper> camHelper_; + + /* Local parameter storage */ + struct IPAContext context_; +}; + +namespace { + +} /* namespace */ + +IPAMaliC55::IPAMaliC55() + : context_(kMaxFrameContexts) +{ +} + +std::string IPAMaliC55::logPrefix() const +{ + return "mali-c55"; +} + +int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, + ControlInfoMap *ipaControls) +{ + camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!camHelper_) { + LOG(IPAMaliC55, Error) + << "Failed to create camera sensor helper for " + << settings.sensorModel; + return -ENODEV; + } + + File file(settings.configurationFile); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + int ret = file.error(); + LOG(IPAMaliC55, Error) + << "Failed to open configuration file " + << settings.configurationFile << ": " << strerror(-ret); + return ret; + } + + std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file); + if (!data) + return -EINVAL; + + if (!data->contains("algorithms")) { + LOG(IPAMaliC55, Error) + << "Tuning file doesn't contain any algorithm"; + return -EINVAL; + } + + int ret = createAlgorithms(context_, (*data)["algorithms"]); + if (ret) + return ret; + + updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls); + + return 0; +} + +void IPAMaliC55::setControls() +{ + IPAActiveState &activeState = context_.activeState; + uint32_t exposure; + uint32_t gain; + + if (activeState.agc.autoEnabled) { + exposure = activeState.agc.automatic.exposure; + gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); + } else { + exposure = activeState.agc.manual.exposure; + gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); + } + + ControlList ctrls(sensorControls_); + ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure)); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain)); + + setSensorControls.emit(ctrls); +} + +int IPAMaliC55::start() +{ + return 0; +} + +void IPAMaliC55::stop() +{ + context_.frameContexts.clear(); +} + +void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, + const ControlInfoMap &sensorControls, + BayerFormat::Order bayerOrder) +{ + context_.configuration.sensor.bayerOrder = bayerOrder; + + const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get<int32_t>(); + int32_t maxExposure = v4l2Exposure.max().get<int32_t>(); + int32_t defExposure = v4l2Exposure.def().get<int32_t>(); + + const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + int32_t minGain = v4l2Gain.min().get<int32_t>(); + int32_t maxGain = v4l2Gain.max().get<int32_t>(); + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for shutter speed and analogue gain. + * As it depends on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum shutter speed + */ + context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; + context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; + context_.configuration.agc.defaultExposure = defExposure; + context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); + context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); + + if (camHelper_->blackLevel().has_value()) { + /* + * The black level from CameraSensorHelper is a 16-bit value. + * The Mali-C55 ISP expects 20-bit settings, so we shift it to + * the appropriate width + */ + context_.configuration.sensor.blackLevel = + camHelper_->blackLevel().value() << 4; + } +} + +void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls) +{ + ControlInfoMap::Map ctrlMap; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get<int32_t>(); + uint32_t lineLength = sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; + std::array<uint32_t, 3> frameHeights{ + v4l2VBlank.min().get<int32_t>() + sensorInfo.outputSize.height, + v4l2VBlank.max().get<int32_t>() + sensorInfo.outputSize.height, + v4l2VBlank.def().get<int32_t>() + sensorInfo.outputSize.height, + }; + + std::array<int64_t, 3> frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); + } + + ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], + frameDurations[1], + frameDurations[2]); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + double lineDuration = sensorInfo.minLineLength / sensorInfo.pixelRate; + + const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration; + int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration; + int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration; + ctrlMap[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, defExposure); + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); + float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); + float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); + ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); + + /* + * Merge in any controls that we support either statically or from the + * algorithms. + */ + ctrlMap.merge(context_.ctrlMap); + + *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); +} + +int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, + ControlInfoMap *ipaControls) +{ + sensorControls_ = ipaConfig.sensorControls; + + /* Clear the IPA context before the streaming session. */ + context_.configuration = {}; + context_.activeState = {}; + context_.frameContexts.clear(); + + const IPACameraSensorInfo &info = ipaConfig.sensorInfo; + + updateSessionConfiguration(info, ipaConfig.sensorControls, + static_cast<BayerFormat::Order>(bayerOrder)); + updateControls(info, ipaConfig.sensorControls, ipaControls); + + for (auto const &a : algorithms()) { + Algorithm *algo = static_cast<Algorithm *>(a.get()); + + int ret = algo->configure(context_, info); + if (ret) + return ret; + } + + return 0; +} + +void IPAMaliC55::mapBuffers(const std::vector<IPABuffer> &buffers, bool readOnly) +{ + for (const IPABuffer &buffer : buffers) { + const FrameBuffer fb(buffer.planes); + buffers_.emplace( + buffer.id, + MappedFrameBuffer( + &fb, + readOnly ? MappedFrameBuffer::MapFlag::Read + : MappedFrameBuffer::MapFlag::ReadWrite)); + } +} + +void IPAMaliC55::unmapBuffers(const std::vector<IPABuffer> &buffers) +{ + for (const IPABuffer &buffer : buffers) { + auto it = buffers_.find(buffer.id); + if (it == buffers_.end()) + continue; + + buffers_.erase(buffer.id); + } +} + +void IPAMaliC55::queueRequest(const uint32_t request, const ControlList &controls) +{ + IPAFrameContext &frameContext = context_.frameContexts.alloc(request); + + for (auto const &a : algorithms()) { + Algorithm *algo = static_cast<Algorithm *>(a.get()); + + algo->queueRequest(context_, request, frameContext, controls); + } +} + +void IPAMaliC55::fillParams(unsigned int request, + [[maybe_unused]] uint32_t bufferId) +{ + struct mali_c55_params_buffer *params; + IPAFrameContext &frameContext = context_.frameContexts.get(request); + + params = reinterpret_cast<mali_c55_params_buffer *>( + buffers_.at(bufferId).planes()[0].data()); + memset(params, 0, sizeof(mali_c55_params_buffer)); + + params->version = MALI_C55_PARAM_BUFFER_V1; + + for (auto const &algo : algorithms()) { + algo->prepare(context_, request, frameContext, params); + + ASSERT(params->total_size <= MALI_C55_PARAMS_MAX_SIZE); + } + + paramsComputed.emit(request); +} + +void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, + const ControlList &sensorControls) +{ + IPAFrameContext &frameContext = context_.frameContexts.get(request); + const mali_c55_stats_buffer *stats = nullptr; + + stats = reinterpret_cast<mali_c55_stats_buffer *>( + buffers_.at(bufferId).planes()[0].data()); + + frameContext.agc.exposure = + sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); + frameContext.agc.sensorGain = + camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); + + ControlList metadata(controls::controls); + + for (auto const &a : algorithms()) { + Algorithm *algo = static_cast<Algorithm *>(a.get()); + + algo->process(context_, request, frameContext, stats, metadata); + } + + setControls(); + + statsProcessed.emit(request, metadata); +} + +} /* namespace ipa::mali_c55 */ + +/* + * External IPA module interface + */ +extern "C" { +const struct IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 1, + "mali-c55", + "mali-c55", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::mali_c55::IPAMaliC55(); +} + +} /* extern "C" */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/meson.build b/src/ipa/mali-c55/meson.build new file mode 100644 index 00000000..864d90ec --- /dev/null +++ b/src/ipa/mali-c55/meson.build @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: CC0-1.0 + +subdir('algorithms') +subdir('data') + +ipa_name = 'ipa_mali_c55' + +mali_c55_ipa_sources = files([ + 'ipa_context.cpp', + 'mali-c55.cpp' +]) + +mali_c55_ipa_sources += mali_c55_ipa_algorithms + +mod = shared_module(ipa_name, + mali_c55_ipa_sources, + name_prefix : '', + include_directories : [ipa_includes, libipa_includes], + dependencies : libcamera_private, + link_with : libipa, + install : true, + install_dir : ipa_install_dir) + +if ipa_sign_module + custom_target(ipa_name + '.so.sign', + input : mod, + output : ipa_name + '.so.sign', + command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], + install : false, + build_by_default : true) +endif + +ipa_names += ipa_name diff --git a/src/ipa/mali-c55/module.h b/src/ipa/mali-c55/module.h new file mode 100644 index 00000000..1d85ec1f --- /dev/null +++ b/src/ipa/mali-c55/module.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * module.h - Mali-C55 IPA Module + */ + +#pragma once + +#include <linux/mali-c55-config.h> + +#include <libcamera/ipa/mali-c55_ipa_interface.h> + +#include <libipa/module.h> + +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::mali_c55 { + +using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo, + mali_c55_params_buffer, mali_c55_stats_buffer>; + +} /* namespace ipa::mali_c55 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/meson.build b/src/ipa/meson.build index 903eb52b..68f64b9a 100644 --- a/src/ipa/meson.build +++ b/src/ipa/meson.build @@ -4,7 +4,7 @@ ipa_includes = [ libcamera_includes, ] -ipa_install_dir = libcamera_libdir +ipa_install_dir = libcamera_libdir / 'ipa' ipa_data_dir = libcamera_datadir / 'ipa' ipa_sysconf_dir = libcamera_sysconfdir / 'ipa' @@ -41,6 +41,9 @@ ipa_names = [] subdirs = [] foreach pipeline : pipelines + # The current implementation expects the IPA module name to match the + # pipeline name. + # \todo Make the IPA naming scheme more flexible. if not ipa_modules.contains(pipeline) continue endif @@ -72,5 +75,6 @@ if ipa_sign_module # install time, which invalidates the signatures. meson.add_install_script('ipa-sign-install.sh', ipa_priv_key.full_path(), - enabled_ipa_modules) + enabled_ipa_modules, + install_tag : 'runtime') endif diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index e5aeb342..137a0750 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * agc.cpp - AGC/AEC mean-based control algorithm + * AGC/AEC mean-based control algorithm */ #include "agc.h" @@ -10,6 +10,8 @@ #include <algorithm> #include <chrono> #include <cmath> +#include <tuple> +#include <vector> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> @@ -17,6 +19,8 @@ #include <libcamera/control_ids.h> #include <libcamera/ipa/core_ipa_interface.h> +#include "libcamera/internal/yaml_parser.h" + #include "libipa/histogram.h" /** @@ -36,35 +40,129 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -/* Minimum limit for analogue gain value */ -static constexpr double kMinAnalogueGain = 1.0; +int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData) +{ + if (!tuningData.isDictionary()) + LOG(RkISP1Agc, Warning) + << "'AeMeteringMode' parameter not found in tuning file"; + + for (const auto &[key, value] : tuningData.asDict()) { + if (controls::AeMeteringModeNameValueMap.find(key) == + controls::AeMeteringModeNameValueMap.end()) { + LOG(RkISP1Agc, Warning) + << "Skipping unknown metering mode '" << key << "'"; + continue; + } -/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxShutterSpeed = 60ms; + std::vector<uint8_t> weights = + value.getList<uint8_t>().value_or(std::vector<uint8_t>{}); + if (weights.size() != context.hw->numHistogramWeights) { + LOG(RkISP1Agc, Warning) + << "Failed to read metering mode'" << key << "'"; + continue; + } -/* Number of frames to wait before calculating stats on minimum exposure */ -static constexpr uint32_t kNumStartupFrames = 10; + meteringModes_[controls::AeMeteringModeNameValueMap.at(key)] = weights; + } -/* Target value to reach for the top 2% of the histogram */ -static constexpr double kEvGainTarget = 0.5; + if (meteringModes_.empty()) { + LOG(RkISP1Agc, Warning) + << "No metering modes read from tuning file; defaulting to matrix"; + std::vector<uint8_t> weights(context.hw->numHistogramWeights, 1); -/* - * Relative luminance target. - * - * It's a number that's chosen so that, when the camera points at a grey - * target, the resulting image brightness is considered right. - * - * \todo Why is the value different between IPU3 and RkISP1 ? - */ -static constexpr double kRelativeLuminanceTarget = 0.4; + meteringModes_[controls::MeteringMatrix] = weights; + } + + std::vector<ControlValue> meteringModes; + std::vector<int> meteringModeKeys = utils::map_keys(meteringModes_); + std::transform(meteringModeKeys.begin(), meteringModeKeys.end(), + std::back_inserter(meteringModes), + [](int x) { return ControlValue(x); }); + context.ctrlMap[&controls::AeMeteringMode] = ControlInfo(meteringModes); + + return 0; +} + +uint8_t Agc::computeHistogramPredivider(const Size &size, + enum rkisp1_cif_isp_histogram_mode mode) +{ + /* + * The maximum number of pixels that could potentially be in one bin is + * if all the pixels of the image are in it, multiplied by 3 for the + * three color channels. The counter for each bin is 16 bits wide, so + * `factor` thus contains the number of times we'd wrap around. This is + * obviously the number of pixels that we need to skip to make sure + * that we don't wrap around, but we compute the square root of it + * instead, as the skip that we need to program is for both the x and y + * directions. + * + * Even though it looks like dividing into a counter of 65536 would + * overflow by 1, this is apparently fine according to the hardware + * documentation, and this successfully gets the expected documented + * predivider size for cases where: + * (width / predivider) * (height / predivider) * 3 == 65536. + * + * There's a bit of extra rounding math to make sure the rounding goes + * the correct direction so that the square of the step is big enough + * to encompass the `factor` number of pixels that we need to skip. + * + * \todo Take into account weights. That is, if the weights are low + * enough we can potentially reduce the predivider to increase + * precision. This needs some investigation however, as this hardware + * behavior is undocumented and is only an educated guess. + */ + int count = mode == RKISP1_CIF_ISP_HISTOGRAM_MODE_RGB_COMBINED ? 3 : 1; + double factor = size.width * size.height * count / 65536.0; + double root = std::sqrt(factor); + uint8_t predivider = static_cast<uint8_t>(std::ceil(root)); + + return std::clamp<uint8_t>(predivider, 3, 127); +} Agc::Agc() - : frameCount_(0), numCells_(0), numHistBins_(0), filteredExposure_(0s) { supportsRaw_ = true; } /** + * \brief Initialise the AGC algorithm from tuning files + * \param[in] context The shared IPA context + * \param[in] tuningData The YamlObject containing Agc tuning data + * + * This function calls the base class' tuningData parsers to discover which + * control values are supported. + * + * \return 0 on success or errors from the base class + */ +int Agc::init(IPAContext &context, const YamlObject &tuningData) +{ + int ret; + + ret = parseTuningData(tuningData); + if (ret) + return ret; + + const YamlObject &yamlMeteringModes = tuningData["AeMeteringMode"]; + ret = parseMeteringModes(context, yamlMeteringModes); + if (ret) + return ret; + + context.ctrlMap[&controls::ExposureTimeMode] = + ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), + ControlValue(controls::ExposureTimeModeManual) } }, + ControlValue(controls::ExposureTimeModeAuto)); + context.ctrlMap[&controls::AnalogueGainMode] = + ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), + ControlValue(controls::AnalogueGainModeManual) } }, + ControlValue(controls::AnalogueGainModeAuto)); + /* \todo Move this to the Camera class */ + context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); + context.ctrlMap.merge(controls()); + + return 0; +} + +/** * \brief Configure the AGC given a configInfo * \param[in] context The shared IPA context * \param[in] configInfo The IPA configuration data @@ -79,35 +177,33 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) 10ms / context.configuration.sensor.lineDuration; context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; - context.activeState.agc.autoEnabled = !context.configuration.raw; + context.activeState.agc.autoExposureEnabled = !context.configuration.raw; + context.activeState.agc.autoGainEnabled = !context.configuration.raw; - /* - * According to the RkISP1 documentation: - * - versions < V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries, - * - versions >= V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries. - */ - if (context.configuration.hw.revision < RKISP1_V12) { - numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V10; - numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10; - } else { - numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V12; - numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12; - } + context.activeState.agc.constraintMode = + static_cast<controls::AeConstraintModeEnum>(constraintModes().begin()->first); + context.activeState.agc.exposureMode = + static_cast<controls::AeExposureModeEnum>(exposureModeHelpers().begin()->first); + context.activeState.agc.meteringMode = + static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first); - /* - * Define the measurement window for AGC as a centered rectangle - * covering 3/4 of the image width and height. - */ - context.configuration.agc.measureWindow.h_offs = configInfo.outputSize.width / 8; - context.configuration.agc.measureWindow.v_offs = configInfo.outputSize.height / 8; - context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; - context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; + /* Limit the frame duration to match current initialisation */ + ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; + context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>()); + context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>()); + + context.configuration.agc.measureWindow.h_offs = 0; + context.configuration.agc.measureWindow.v_offs = 0; + context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; + context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; + + setLimits(context.configuration.sensor.minExposureTime, + context.configuration.sensor.maxExposureTime, + context.configuration.sensor.minAnalogueGain, + context.configuration.sensor.maxAnalogueGain); + + resetFrameCount(); - /* - * \todo Use the upcoming per-frame context API that will provide a - * frame index - */ - frameCount_ = 0; return 0; } @@ -122,18 +218,47 @@ void Agc::queueRequest(IPAContext &context, auto &agc = context.activeState.agc; if (!context.configuration.raw) { - const auto &agcEnable = controls.get(controls::AeEnable); - if (agcEnable && *agcEnable != agc.autoEnabled) { - agc.autoEnabled = *agcEnable; + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { + agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(RkISP1Agc, Debug) + << (agc.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.agc.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { + agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); LOG(RkISP1Agc, Debug) - << (agc.autoEnabled ? "Enabling" : "Disabling") - << " AGC"; + << (agc.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.agc.autoGainModeChange = true; } } const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !agc.autoEnabled) { + if (exposure && !agc.autoExposureEnabled) { agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; @@ -142,183 +267,150 @@ void Agc::queueRequest(IPAContext &context, } const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !agc.autoEnabled) { + if (gain && !agc.autoGainEnabled) { agc.manual.gain = *gain; LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; } - frameContext.agc.autoEnabled = agc.autoEnabled; + frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; + frameContext.agc.autoGainEnabled = agc.autoGainEnabled; - if (!frameContext.agc.autoEnabled) { + if (!frameContext.agc.autoExposureEnabled) frameContext.agc.exposure = agc.manual.exposure; + if (!frameContext.agc.autoGainEnabled) frameContext.agc.gain = agc.manual.gain; + + const auto &meteringMode = controls.get(controls::AeMeteringMode); + if (meteringMode) { + frameContext.agc.updateMetering = agc.meteringMode != *meteringMode; + agc.meteringMode = + static_cast<controls::AeMeteringModeEnum>(*meteringMode); } + frameContext.agc.meteringMode = agc.meteringMode; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + agc.exposureMode = + static_cast<controls::AeExposureModeEnum>(*exposureMode); + frameContext.agc.exposureMode = agc.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + agc.constraintMode = + static_cast<controls::AeConstraintModeEnum>(*constraintMode); + frameContext.agc.constraintMode = agc.constraintMode; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; + int64_t minFrameDuration = + std::clamp((*frameDurationLimits).front(), + limits.min().get<int64_t>(), + limits.max().get<int64_t>()); + int64_t maxFrameDuration = + std::clamp((*frameDurationLimits).back(), + limits.min().get<int64_t>(), + limits.max().get<int64_t>()); + + agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); + agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); + } + frameContext.agc.minFrameDuration = agc.minFrameDuration; + frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** * \copydoc libcamera::ipa::Algorithm::prepare */ void Agc::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) -{ - if (frameContext.agc.autoEnabled) { - frameContext.agc.exposure = context.activeState.agc.automatic.exposure; - frameContext.agc.gain = context.activeState.agc.automatic.gain; - } - - if (frame > 0) - return; - - /* Configure the measurement window. */ - params->meas.aec_config.meas_window = context.configuration.agc.measureWindow; - /* Use a continuous method for measure. */ - params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; - /* Estimate Y as (R + G + B) x (85/256). */ - params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; - - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC; - - /* Configure histogram. */ - params->meas.hst_config.meas_window = context.configuration.agc.measureWindow; - /* Produce the luminance histogram. */ - params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM; - /* Set an average weighted histogram. */ - for (unsigned int histBin = 0; histBin < numHistBins_; histBin++) - params->meas.hst_config.hist_weight[histBin] = 1; - /* Step size can't be less than 3. */ - params->meas.hst_config.histogram_predivider = 4; - - /* Update the configuration for histogram. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST; - /* Enable the histogram measure unit. */ - params->module_ens |= RKISP1_CIF_ISP_MODULE_HST; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST; -} - -/** - * \brief Apply a filter on the exposure value to limit the speed of changes - * \param[in] exposureValue The target exposure from the AGC algorithm - * - * The speed of the filter is adaptive, and will produce the target quicker - * during startup, or when the target exposure is within 20% of the most recent - * filter output. - * - * \return The filtered exposure - */ -utils::Duration Agc::filterExposure(utils::Duration exposureValue) + IPAFrameContext &frameContext, RkISP1Params *params) { - double speed = 0.2; + uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; + double activeAutoGain = context.activeState.agc.automatic.gain; - /* Adapt instantly if we are in startup phase. */ - if (frameCount_ < kNumStartupFrames) - speed = 1.0; + /* Populate exposure and gain in auto mode */ + if (frameContext.agc.autoExposureEnabled) + frameContext.agc.exposure = activeAutoExposure; + if (frameContext.agc.autoGainEnabled) + frameContext.agc.gain = activeAutoGain; /* - * If we are close to the desired result, go faster to avoid making - * multiple micro-adjustments. - * \todo Make this customisable? + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual */ - if (filteredExposure_ < 1.2 * exposureValue && - filteredExposure_ > 0.8 * exposureValue) - speed = sqrt(speed); - - filteredExposure_ = speed * exposureValue + - filteredExposure_ * (1.0 - speed); - - LOG(RkISP1Agc, Debug) << "After filtering, exposure " << filteredExposure_; - - return filteredExposure_; -} - -/** - * \brief Estimate the new exposure and gain values - * \param[inout] context The shared IPA Context - * \param[in] frameContext The FrameContext for this frame - * \param[in] yGain The gain calculated on the current brightness level - * \param[in] iqMeanGain The gain calculated based on the relative luminance target - */ -void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext, - double yGain, double iqMeanGain) -{ - IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - - /* Get the effective exposure and gain applied on the sensor. */ - uint32_t exposure = frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - - /* Use the highest of the two gain estimates. */ - double evGain = std::max(yGain, iqMeanGain); - - utils::Duration minShutterSpeed = configuration.sensor.minShutterSpeed; - utils::Duration maxShutterSpeed = std::min(configuration.sensor.maxShutterSpeed, - kMaxShutterSpeed); - - double minAnalogueGain = std::max(configuration.sensor.minAnalogueGain, - kMinAnalogueGain); - double maxAnalogueGain = configuration.sensor.maxAnalogueGain; + if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { + context.activeState.agc.manual.exposure = activeAutoExposure; + frameContext.agc.exposure = activeAutoExposure; + } + if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { + context.activeState.agc.manual.gain = activeAutoGain; + frameContext.agc.gain = activeAutoGain; + } - /* Consider within 1% of the target as correctly exposed. */ - if (utils::abs_diff(evGain, 1.0) < 0.01) + if (frame > 0 && !frameContext.agc.updateMetering) return; - /* extracted from Rpi::Agc::computeTargetExposure. */ - - /* Calculate the shutter time in seconds. */ - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - - /* - * Update the exposure value for the next computation using the values - * of exposure and gain really used by the sensor. - */ - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - - LOG(RkISP1Agc, Debug) << "Actual total exposure " << currentShutter * analogueGain - << " Shutter speed " << currentShutter - << " Gain " << analogueGain - << " Needed ev gain " << evGain; - /* - * Calculate the current exposure value for the scene as the latest - * exposure value applied multiplied by the new estimated gain. + * Configure the AEC measurements. Set the window, measure + * continuously, and estimate Y as (R + G + B) x (85/256). */ - utils::Duration exposureValue = effectiveExposureValue * evGain; + auto aecConfig = params->block<BlockType::Aec>(); + aecConfig.setEnabled(true); - /* Clamp the exposure value to the min and max authorized. */ - utils::Duration maxTotalExposure = maxShutterSpeed * maxAnalogueGain; - exposureValue = std::min(exposureValue, maxTotalExposure); - LOG(RkISP1Agc, Debug) << "Target total exposure " << exposureValue - << ", maximum is " << maxTotalExposure; + aecConfig->meas_window = context.configuration.agc.measureWindow; + aecConfig->autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; + aecConfig->mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; /* - * Divide the exposure value as new exposure and gain values. - * \todo estimate if we need to desaturate + * Configure the histogram measurement. Set the window, produce a + * luminance histogram, and set the weights and predivider. */ - exposureValue = filterExposure(exposureValue); - - /* - * Push the shutter time up to the maximum first, and only then - * increase the gain. - */ - utils::Duration shutterTime = std::clamp<utils::Duration>(exposureValue / minAnalogueGain, - minShutterSpeed, maxShutterSpeed); - double stepGain = std::clamp(exposureValue / shutterTime, - minAnalogueGain, maxAnalogueGain); - LOG(RkISP1Agc, Debug) << "Divided up shutter and gain are " - << shutterTime << " and " - << stepGain; + auto hstConfig = params->block<BlockType::Hst>(); + hstConfig.setEnabled(true); + + hstConfig->meas_window = context.configuration.agc.measureWindow; + hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM; + + Span<uint8_t> weights{ + hstConfig->hist_weight, + context.hw->numHistogramWeights + }; + std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode); + std::copy(modeWeights.begin(), modeWeights.end(), weights.begin()); + + struct rkisp1_cif_isp_window window = hstConfig->meas_window; + Size windowSize = { window.h_size, window.v_size }; + hstConfig->histogram_predivider = + computeHistogramPredivider(windowSize, + static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode)); +} - /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; - activeState.agc.automatic.gain = stepGain; +void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, + ControlList &metadata) +{ + utils::Duration exposureTime = context.configuration.sensor.lineDuration + * frameContext.sensor.exposure; + metadata.set(controls::AnalogueGain, frameContext.sensor.gain); + metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); + metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>()); + metadata.set(controls::ExposureTimeMode, + frameContext.agc.autoExposureEnabled + ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + metadata.set(controls::AnalogueGainMode, + frameContext.agc.autoGainEnabled + ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); + + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); + metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); } /** * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] ae The RkISP1 statistics and ISP results * \param[in] gain The gain to apply to the frame * * This function estimates the average relative luminance of the frame that @@ -332,8 +424,6 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext, * YUV doesn't take into account the fact that the R, G and B components * contribute differently to the relative luminance. * - * \todo Have a dedicated YUV algorithm ? - * * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a * theoretical perfect reflector of 100% reference white. * @@ -342,46 +432,43 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext, * * \return The relative luminance */ -double Agc::estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, - double gain) +double Agc::estimateLuminance(double gain) const { + ASSERT(expMeans_.size() == weights_.size()); double ySum = 0.0; + double wSum = 0.0; /* Sum the averages, saturated to 255. */ - for (unsigned int aeCell = 0; aeCell < numCells_; aeCell++) - ySum += std::min(ae->exp_mean[aeCell] * gain, 255.0); + for (unsigned i = 0; i < expMeans_.size(); i++) { + double w = weights_[i]; + ySum += std::min(expMeans_[i] * gain, 255.0) * w; + wSum += w; + } /* \todo Weight with the AWB gains */ - return ySum / numCells_ / 255; + return ySum / wSum / 255; } /** - * \brief Estimate the mean value of the top 2% of the histogram - * \param[in] hist The histogram statistics computed by the ImgU - * \return The mean value of the top 2% of the histogram + * \brief Process frame duration and compute vblank + * \param[in] context The shared IPA context + * \param[in] frameContext The current frame context + * \param[in] frameDuration The target frame duration + * + * Compute and populate vblank from the target frame duration. */ -double Agc::measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const +void Agc::processFrameDuration(IPAContext &context, + IPAFrameContext &frameContext, + utils::Duration frameDuration) { - Histogram histogram{ Span<const uint32_t>(hist->hist_bins, numHistBins_) }; - /* Estimate the quantile mean of the top 2% of the histogram. */ - return histogram.interQuantileMean(0.98, 1.0); -} + IPACameraSensorInfo &sensorInfo = context.sensorInfo; + utils::Duration lineDuration = context.configuration.sensor.lineDuration; -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata) -{ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); + frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; - /* \todo Use VBlank value calculated from each frame exposure. */ - uint32_t vTotal = context.configuration.sensor.size.height - + context.configuration.sensor.defVBlank; - utils::Duration frameDuration = context.configuration.sensor.lineDuration - * vTotal; - metadata.set(controls::FrameDuration, frameDuration.get<std::micro>()); + /* Update frame duration accounting for line length quantization. */ + frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; } /** @@ -400,10 +487,20 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, ControlList &metadata) { if (!stats) { + processFrameDuration(context, frameContext, + frameContext.agc.minFrameDuration); + fillMetadata(context, frameContext, metadata); + return; + } + + if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { fillMetadata(context, frameContext, metadata); + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; return; } + const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; + /* * \todo Verify that the exposure and gain applied by the sensor for * this frame match what has been requested. This isn't a hard @@ -413,40 +510,77 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, */ const rkisp1_cif_isp_stat *params = &stats->params; - ASSERT(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP); - const rkisp1_cif_isp_ae_stat *ae = ¶ms->ae; - const rkisp1_cif_isp_hist_stat *hist = ¶ms->hist; - - double iqMean = measureBrightness(hist); - double iqMeanGain = kEvGainTarget * numHistBins_ / iqMean; + /* The lower 4 bits are fractional and meant to be discarded. */ + Histogram hist({ params->hist.hist_bins, context.hw->numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + expMeans_ = { params->ae.exp_mean, context.hw->numAeCells }; + std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode); + weights_ = { modeWeights.data(), modeWeights.size() }; /* - * Estimate the gain needed to achieve a relative luminance target. To - * account for non-linearity caused by saturation, the value needs to be - * estimated in an iterative process, as multiplying by a gain will not - * increase the relative luminance by the same factor if some image - * regions are saturated. + * Set the AGC limits using the fixed exposure time and/or gain in + * manual mode, or the sensor limits in auto mode. */ - double yGain = 1.0; - double yTarget = kRelativeLuminanceTarget; - - for (unsigned int i = 0; i < 8; i++) { - double yValue = estimateLuminance(ae, yGain); - double extra_gain = std::min(10.0, yTarget / (yValue + .001)); - - yGain *= extra_gain; - LOG(RkISP1Agc, Debug) << "Y value: " << yValue - << ", Y target: " << yTarget - << ", gives gain " << yGain; - if (extra_gain < 1.01) - break; + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + + if (frameContext.agc.autoExposureEnabled) { + minExposureTime = context.configuration.sensor.minExposureTime; + maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, + context.configuration.sensor.minExposureTime, + context.configuration.sensor.maxExposureTime); + } else { + minExposureTime = context.configuration.sensor.lineDuration + * frameContext.agc.exposure; + maxExposureTime = minExposureTime; } - computeExposure(context, frameContext, yGain, iqMeanGain); - frameCount_++; + if (frameContext.agc.autoGainEnabled) { + minAnalogueGain = context.configuration.sensor.minAnalogueGain; + maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; + } else { + minAnalogueGain = frameContext.agc.gain; + maxAnalogueGain = frameContext.agc.gain; + } + + setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain); + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; + double analogueGain = frameContext.sensor.gain; + utils::Duration effectiveExposureValue = exposureTime * analogueGain; + + utils::Duration newExposureTime; + double aGain, dGain; + std::tie(newExposureTime, aGain, dGain) = + calculateNewEv(frameContext.agc.constraintMode, + frameContext.agc.exposureMode, + hist, effectiveExposureValue); + + LOG(RkISP1Agc, Debug) + << "Divided up exposure time, analogue gain and digital gain are " + << newExposureTime << ", " << aGain << " and " << dGain; + + IPAActiveState &activeState = context.activeState; + /* Update the estimated exposure and gain. */ + activeState.agc.automatic.exposure = newExposureTime / lineDuration; + activeState.agc.automatic.gain = aGain; + + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + processFrameDuration(context, frameContext, + std::max(frameContext.agc.minFrameDuration, newExposureTime)); fillMetadata(context, frameContext, metadata); + expMeans_ = {}; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 8a222637..7867eed9 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -2,29 +2,33 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * agc.h - RkISP1 AGC/AEC mean-based control algorithm + * RkISP1 AGC/AEC mean-based control algorithm */ #pragma once #include <linux/rkisp1-config.h> +#include <libcamera/base/span.h> #include <libcamera/base/utils.h> #include <libcamera/geometry.h> +#include "libipa/agc_mean_luminance.h" + #include "algorithm.h" namespace libcamera { namespace ipa::rkisp1::algorithms { -class Agc : public Algorithm +class Agc : public Algorithm, public AgcMeanLuminance { public: Agc(); ~Agc() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; void queueRequest(IPAContext &context, const uint32_t frame, @@ -32,27 +36,28 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; private: - void computeExposure(IPAContext &Context, IPAFrameContext &frameContext, - double yGain, double iqMeanGain); - utils::Duration filterExposure(utils::Duration exposureValue); - double estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, double gain); - double measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const; + int parseMeteringModes(IPAContext &context, const YamlObject &tuningData); + uint8_t computeHistogramPredivider(const Size &size, + enum rkisp1_cif_isp_histogram_mode mode); + void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, ControlList &metadata); + double estimateLuminance(double gain) const override; + void processFrameDuration(IPAContext &context, + IPAFrameContext &frameContext, + utils::Duration frameDuration); - uint64_t frameCount_; - - uint32_t numCells_; - uint32_t numHistBins_; + Span<const uint8_t> expMeans_; + Span<const uint8_t> weights_; - utils::Duration filteredExposure_; + std::map<int32_t, std::vector<uint8_t>> meteringModes_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/algorithm.h b/src/ipa/rkisp1/algorithms/algorithm.h index 9454c9a1..715cfcd8 100644 --- a/src/ipa/rkisp1/algorithms/algorithm.h +++ b/src/ipa/rkisp1/algorithms/algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * algorithm.h - RkISP1 control algorithm interface + * RkISP1 control algorithm interface */ #pragma once diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 744f4a38..399fb51b 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -2,20 +2,24 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * awb.cpp - AWB control algorithm + * AWB control algorithm */ #include "awb.h" #include <algorithm> -#include <cmath> -#include <iomanip> +#include <ios> #include <libcamera/base/log.h> #include <libcamera/control_ids.h> + #include <libcamera/ipa/core_ipa_interface.h> +#include "libipa/awb_bayes.h" +#include "libipa/awb_grey.h" +#include "libipa/colours.h" + /** * \file awb.h */ @@ -31,27 +35,107 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Awb) +constexpr int32_t kMinColourTemperature = 2500; +constexpr int32_t kMaxColourTemperature = 10000; +constexpr int32_t kDefaultColourTemperature = 5000; + /* Minimum mean value below which AWB can't operate. */ constexpr double kMeanMinThreshold = 2.0; +class RkISP1AwbStats final : public AwbStats +{ +public: + RkISP1AwbStats(const RGB<double> &rgbMeans) + : rgbMeans_(rgbMeans) + { + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); + } + + double computeColourError(const RGB<double> &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB<double> rgbMeans() const override + { + return rgbMeans_; + } + +private: + RGB<double> rgbMeans_; + double rg_; + double bg_; +}; + Awb::Awb() : rgbMode_(false) { } /** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature, + kMaxColourTemperature, + kDefaultColourTemperature); + cmap[&controls::AwbEnable] = ControlInfo(false, true); + cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f, 1.0f); + + if (!tuningData.contains("algorithm")) + LOG(RkISP1Awb, Info) << "No AWB algorithm specified." + << " Default to grey world"; + + auto mode = tuningData["algorithm"].get<std::string>("grey"); + if (mode == "grey") { + awbAlgo_ = std::make_unique<AwbGrey>(); + } else if (mode == "bayes") { + awbAlgo_ = std::make_unique<AwbBayes>(); + } else { + LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode; + return -EINVAL; + } + LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode; + + int ret = awbAlgo_->init(tuningData); + if (ret) { + LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm"; + return ret; + } + + const auto &src = awbAlgo_->controls(); + cmap.insert(src.begin(), src.end()); + + return 0; +} + +/** * \copydoc libcamera::ipa::Algorithm::configure */ int Awb::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - context.activeState.awb.gains.manual.red = 1.0; - context.activeState.awb.gains.manual.blue = 1.0; - context.activeState.awb.gains.manual.green = 1.0; - context.activeState.awb.gains.automatic.red = 1.0; - context.activeState.awb.gains.automatic.blue = 1.0; - context.activeState.awb.gains.automatic.green = 1.0; + context.activeState.awb.manual.gains = RGB<double>{ 1.0 }; + auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + context.activeState.awb.automatic.gains = *gains; + else + context.activeState.awb.automatic.gains = RGB<double>{ 1.0 }; + context.activeState.awb.autoEnabled = true; + context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; + context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; /* * Define the measurement window for AWB as a centered rectangle @@ -85,64 +169,83 @@ void Awb::queueRequest(IPAContext &context, << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; } - const auto &colourGains = controls.get(controls::ColourGains); - if (colourGains && !awb.autoEnabled) { - awb.gains.manual.red = (*colourGains)[0]; - awb.gains.manual.blue = (*colourGains)[1]; - - LOG(RkISP1Awb, Debug) - << "Set colour gains to red: " << awb.gains.manual.red - << ", blue: " << awb.gains.manual.blue; - } + awbAlgo_->handleControls(controls); frameContext.awb.autoEnabled = awb.autoEnabled; - if (!awb.autoEnabled) { - frameContext.awb.gains.red = awb.gains.manual.red; - frameContext.awb.gains.green = 1.0; - frameContext.awb.gains.blue = awb.gains.manual.blue; + if (awb.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + awb.manual.gains.r() = (*colourGains)[0]; + awb.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + awb.manual.temperatureK = *colourTemperature; + const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); + if (gains) { + awb.manual.gains.r() = gains->r(); + awb.manual.gains.b() = gains->b(); + update = true; + } } + + if (update) + LOG(RkISP1Awb, Debug) + << "Set colour gains to " << awb.manual.gains; + + frameContext.awb.gains = awb.manual.gains; + frameContext.awb.temperatureK = awb.manual.temperatureK; } /** * \copydoc libcamera::ipa::Algorithm::prepare */ void Awb::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { /* * This is the latest time we can read the active state. This is the * most up-to-date automatic values we can read. */ if (frameContext.awb.autoEnabled) { - frameContext.awb.gains.red = context.activeState.awb.gains.automatic.red; - frameContext.awb.gains.green = context.activeState.awb.gains.automatic.green; - frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue; + const auto &awb = context.activeState.awb; + frameContext.awb.gains = awb.automatic.gains; + frameContext.awb.temperatureK = awb.automatic.temperatureK; } - params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green; - params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue; - params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red; - params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green; + auto gainConfig = params->block<BlockType::AwbGain>(); + gainConfig.setEnabled(true); - /* Update the gains. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; + gainConfig->gain_green_b = std::clamp<int>(256 * frameContext.awb.gains.g(), 0, 0x3ff); + gainConfig->gain_blue = std::clamp<int>(256 * frameContext.awb.gains.b(), 0, 0x3ff); + gainConfig->gain_red = std::clamp<int>(256 * frameContext.awb.gains.r(), 0, 0x3ff); + gainConfig->gain_green_r = std::clamp<int>(256 * frameContext.awb.gains.g(), 0, 0x3ff); /* If we have already set the AWB measurement parameters, return. */ if (frame > 0) return; - rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config; + auto awbConfig = params->block<BlockType::Awb>(); + awbConfig.setEnabled(true); /* Configure the measure window for AWB. */ - awb_config.awb_wnd = context.configuration.awb.measureWindow; + awbConfig->awb_wnd = context.configuration.awb.measureWindow; /* Number of frames to use to estimate the means (0 means 1 frame). */ - awb_config.frames = 0; + awbConfig->frames = 0; /* Select RGB or YCbCr means measurement. */ if (rgbMode_) { - awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB; + awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB; /* * For RGB-based measurements, pixels are selected with maximum @@ -150,19 +253,19 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, * awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other * values are not used, set them to 0. */ - awb_config.awb_ref_cr = 250; - awb_config.min_y = 250; - awb_config.awb_ref_cb = 250; + awbConfig->awb_ref_cr = 250; + awbConfig->min_y = 250; + awbConfig->awb_ref_cb = 250; - awb_config.max_y = 0; - awb_config.min_c = 0; - awb_config.max_csum = 0; + awbConfig->max_y = 0; + awbConfig->min_c = 0; + awbConfig->max_csum = 0; } else { - awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; + awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; /* Set the reference Cr and Cb (AWB target) to white. */ - awb_config.awb_ref_cb = 128; - awb_config.awb_ref_cr = 128; + awbConfig->awb_ref_cb = 128; + awbConfig->awb_ref_cr = 128; /* * Filter out pixels based on luminance and chrominance values. @@ -170,36 +273,11 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, * range, while the acceptable chroma values are specified with * a minimum of 16 and a maximum Cb+Cr sum of 250. */ - awb_config.min_y = 16; - awb_config.max_y = 250; - awb_config.min_c = 16; - awb_config.max_csum = 250; + awbConfig->min_y = 16; + awbConfig->max_y = 250; + awbConfig->min_c = 16; + awbConfig->max_csum = 250; } - - /* Enable the AWB gains. */ - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - - /* Update the AWB measurement parameters and enable the AWB module. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB; -} - -uint32_t Awb::estimateCCT(double red, double green, double blue) -{ - /* Convert the RGB values to CIE tristimulus values (XYZ) */ - double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); - double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); - double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); - - /* Calculate the normalized chromaticity values */ - double x = X / (X + Y + Z); - double y = Y / (X + Y + Z); - - /* Calculate CCT */ - double n = (x - 0.3320) / (0.1858 - y); - return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; } /** @@ -211,41 +289,111 @@ void Awb::process(IPAContext &context, const rkisp1_stat_buffer *stats, ControlList &metadata) { + IPAActiveState &activeState = context.activeState; + + metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); + metadata.set(controls::ColourGains, { + static_cast<float>(frameContext.awb.gains.r()), + static_cast<float>(frameContext.awb.gains.b()) + }); + metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); + + if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) { + LOG(RkISP1Awb, Error) << "AWB data is missing in statistics"; + return; + } + const rkisp1_cif_isp_stat *params = &stats->params; const rkisp1_cif_isp_awb_stat *awb = ¶ms->awb; - IPAActiveState &activeState = context.activeState; - double greenMean; - double redMean; - double blueMean; + + if (awb->awb_mean[0].cnt == 0) { + LOG(RkISP1Awb, Debug) << "AWB statistics are empty"; + return; + } + + RGB<double> rgbMeans = calculateRgbMeans(frameContext, awb); + + /* + * If the means are too small we don't have enough information to + * meaningfully calculate gains. Freeze the algorithm in that case. + */ + if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold && + rgbMeans.b() < kMeanMinThreshold) + return; + + RkISP1AwbStats awbStats{ rgbMeans }; + AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux); + + /* + * Clamp the gain values to the hardware, which expresses gains as Q2.8 + * unsigned integer values. Set the minimum just above zero to avoid + * divisions by zero when computing the raw means in subsequent + * iterations. + */ + awbResult.gains = awbResult.gains.max(1.0 / 256).min(1023.0 / 256); + + /* Filter the values to avoid oscillations. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed); + awbResult.gains = awbResult.gains * speed + + activeState.awb.automatic.gains * (1 - speed); + + activeState.awb.automatic.temperatureK = static_cast<unsigned int>(ct); + activeState.awb.automatic.gains = awbResult.gains; + + LOG(RkISP1Awb, Debug) + << std::showpoint + << "Means " << rgbMeans << ", gains " + << activeState.awb.automatic.gains << ", temp " + << activeState.awb.automatic.temperatureK << "K"; +} + +RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const +{ + Vector<double, 3> rgbMeans; if (rgbMode_) { - greenMean = awb->awb_mean[0].mean_y_or_g; - redMean = awb->awb_mean[0].mean_cr_or_r; - blueMean = awb->awb_mean[0].mean_cb_or_b; + rgbMeans = {{ + static_cast<double>(awb->awb_mean[0].mean_cr_or_r), + static_cast<double>(awb->awb_mean[0].mean_y_or_g), + static_cast<double>(awb->awb_mean[0].mean_cb_or_b) + }}; } else { /* Get the YCbCr mean values */ - double yMean = awb->awb_mean[0].mean_y_or_g; - double cbMean = awb->awb_mean[0].mean_cb_or_b; - double crMean = awb->awb_mean[0].mean_cr_or_r; + Vector<double, 3> yuvMeans({ + static_cast<double>(awb->awb_mean[0].mean_y_or_g), + static_cast<double>(awb->awb_mean[0].mean_cb_or_b), + static_cast<double>(awb->awb_mean[0].mean_cr_or_r) + }); /* - * Convert from YCbCr to RGB. - * The hardware uses the following formulas: - * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B + * Convert from YCbCr to RGB. The hardware uses the following + * formulas: + * + * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B * - * The inverse matrix is thus: + * This seems to be based on limited range BT.601 with Q1.6 + * precision. + * + * The inverse matrix is: + * * [[1,1636, -0,0623, 1,6008] * [1,1636, -0,4045, -0,7949] * [1,1636, 1,9912, -0,0250]] */ - yMean -= 16; - cbMean -= 128; - crMean -= 128; - redMean = 1.1636 * yMean - 0.0623 * cbMean + 1.6008 * crMean; - greenMean = 1.1636 * yMean - 0.4045 * cbMean - 0.7949 * crMean; - blueMean = 1.1636 * yMean + 1.9912 * cbMean - 0.0250 * crMean; + static const Matrix<double, 3, 3> yuv2rgbMatrix({ + 1.1636, -0.0623, 1.6008, + 1.1636, -0.4045, -0.7949, + 1.1636, 1.9912, -0.0250 + }); + static const Vector<double, 3> yuv2rgbOffset({ + 16, 128, 128 + }); + + rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset); /* * Due to hardware rounding errors in the YCbCr means, the @@ -253,73 +401,23 @@ void Awb::process(IPAContext &context, * negative gains, messing up calculation. Prevent this by * clamping the means to positive values. */ - redMean = std::max(redMean, 0.0); - greenMean = std::max(greenMean, 0.0); - blueMean = std::max(blueMean, 0.0); + rgbMeans = rgbMeans.max(0.0); } /* - * The ISP computes the AWB means after applying the colour gains, - * divide by the gains that were used to get the raw means from the - * sensor. - */ - redMean /= frameContext.awb.gains.red; - greenMean /= frameContext.awb.gains.green; - blueMean /= frameContext.awb.gains.blue; - - /* - * If the means are too small we don't have enough information to - * meaningfully calculate gains. Freeze the algorithm in that case. - */ - if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold && - blueMean < kMeanMinThreshold) { - frameContext.awb.temperatureK = activeState.awb.temperatureK; - return; - } - - activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean); - - /* - * Estimate the red and blue gains to apply in a grey world. The green - * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the - * divisor to a minimum value of 1.0. + * The ISP computes the AWB means after applying the CCM. Apply the + * inverse as we want to get the raw means before the colour gains. */ - double redGain = greenMean / std::max(redMean, 1.0); - double blueGain = greenMean / std::max(blueMean, 1.0); + rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans; /* - * Clamp the gain values to the hardware, which expresses gains as Q2.8 - * unsigned integer values. Set the minimum just above zero to avoid - * divisions by zero when computing the raw means in subsequent - * iterations. + * The ISP computes the AWB means after applying the colour gains, + * divide by the gains that were used to get the raw means from the + * sensor. Apply a minimum value to avoid divisions by near-zero. */ - redGain = std::clamp(redGain, 1.0 / 256, 1023.0 / 256); - blueGain = std::clamp(blueGain, 1.0 / 256, 1023.0 / 256); - - /* Filter the values to avoid oscillations. */ - double speed = 0.2; - redGain = speed * redGain + (1 - speed) * activeState.awb.gains.automatic.red; - blueGain = speed * blueGain + (1 - speed) * activeState.awb.gains.automatic.blue; - - activeState.awb.gains.automatic.red = redGain; - activeState.awb.gains.automatic.blue = blueGain; - activeState.awb.gains.automatic.green = 1.0; - - frameContext.awb.temperatureK = activeState.awb.temperatureK; - - metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); - metadata.set(controls::ColourGains, { - static_cast<float>(frameContext.awb.gains.red), - static_cast<float>(frameContext.awb.gains.blue) - }); - metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); + rgbMeans /= frameContext.awb.gains.max(0.01); - LOG(RkISP1Awb, Debug) << std::showpoint - << "Means [" << redMean << ", " << greenMean << ", " << blueMean - << "], gains [" << activeState.awb.gains.automatic.red << ", " - << activeState.awb.gains.automatic.green << ", " - << activeState.awb.gains.automatic.blue << "], temp " - << frameContext.awb.temperatureK << "K"; + return rgbMeans; } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 9d45a442..02651cc7 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -2,11 +2,16 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * awb.h - AWB control algorithm + * AWB control algorithm */ #pragma once +#include "libcamera/internal/vector.h" + +#include "libipa/awb.h" +#include "libipa/interpolator.h" + #include "algorithm.h" namespace libcamera { @@ -19,20 +24,24 @@ public: Awb(); ~Awb() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; private: - uint32_t estimateCCT(double red, double green, double blue); + RGB<double> calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_cif_isp_awb_stat *awb) const; + + std::unique_ptr<AwbAlgorithm> awbAlgo_; bool rgbMode_; }; diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp index 15324fb1..98cb7145 100644 --- a/src/ipa/rkisp1/algorithms/blc.cpp +++ b/src/ipa/rkisp1/algorithms/blc.cpp @@ -2,13 +2,17 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * blc.cpp - RkISP1 Black Level Correction control + * RkISP1 Black Level Correction control */ #include "blc.h" +#include <linux/videodev2.h> + #include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + #include "libcamera/internal/yaml_parser.h" /** @@ -36,22 +40,62 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Blc) BlackLevelCorrection::BlackLevelCorrection() - : tuningParameters_(false) { + /* + * This is a bit of a hack. In raw mode no black level correction + * happens. This flag is used to ensure the metadata gets populated with + * the black level which is needed to capture proper raw images for + * tuning. + */ + supportsRaw_ = true; } /** * \copydoc libcamera::ipa::Algorithm::init */ -int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, - const YamlObject &tuningData) +int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData) { - blackLevelRed_ = tuningData["R"].get<int16_t>(256); - blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256); - blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256); - blackLevelBlue_ = tuningData["B"].get<int16_t>(256); - - tuningParameters_ = true; + std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>(); + std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>(); + std::optional<int16_t> levelGreenB = tuningData["Gb"].get<int16_t>(); + std::optional<int16_t> levelBlue = tuningData["B"].get<int16_t>(); + bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue; + + auto blackLevel = context.camHelper->blackLevel(); + if (!blackLevel) { + /* + * Not all camera sensor helpers have been updated with black + * levels. Print a warning and fall back to the levels from the + * tuning data to preserve backward compatibility. This should + * be removed once all helpers provide the data. + */ + LOG(RkISP1Blc, Warning) + << "No black levels provided by camera sensor helper" + << ", please fix"; + + blackLevelRed_ = levelRed.value_or(4096); + blackLevelGreenR_ = levelGreenR.value_or(4096); + blackLevelGreenB_ = levelGreenB.value_or(4096); + blackLevelBlue_ = levelBlue.value_or(4096); + } else if (tuningHasLevels) { + /* + * If black levels are provided in the tuning file, use them to + * avoid breaking existing camera tuning. This is deprecated and + * will be removed. + */ + LOG(RkISP1Blc, Warning) + << "Deprecated: black levels overwritten by tuning file"; + + blackLevelRed_ = *levelRed; + blackLevelGreenR_ = *levelGreenR; + blackLevelGreenB_ = *levelGreenB; + blackLevelBlue_ = *levelBlue; + } else { + blackLevelRed_ = *blackLevel; + blackLevelGreenR_ = *blackLevel; + blackLevelGreenB_ = *blackLevel; + blackLevelBlue_ = *blackLevel; + } LOG(RkISP1Blc, Debug) << "Black levels: red " << blackLevelRed_ @@ -62,29 +106,80 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, return 0; } +int BlackLevelCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + /* + * BLC on ISP versions that include the companding block requires usage + * of the extensible parameters format. + */ + supported_ = context.configuration.paramFormat == V4L2_META_FMT_RK_ISP1_EXT_PARAMS || + !context.hw->compand; + + if (!supported_) + LOG(RkISP1Blc, Warning) + << "BLC in companding block requires extensible parameters"; + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::prepare */ -void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, +void BlackLevelCorrection::prepare(IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { + if (context.configuration.raw) + return; + if (frame > 0) return; - if (!tuningParameters_) + if (!supported_) return; - params->others.bls_config.enable_auto = 0; - params->others.bls_config.fixed_val.r = blackLevelRed_; - params->others.bls_config.fixed_val.gr = blackLevelGreenR_; - params->others.bls_config.fixed_val.gb = blackLevelGreenB_; - params->others.bls_config.fixed_val.b = blackLevelBlue_; + if (context.hw->compand) { + auto config = params->block<BlockType::CompandBls>(); + config.setEnabled(true); + + /* + * Scale up to the 20-bit black levels used by the companding + * block. + */ + config->r = blackLevelRed_ << 4; + config->gr = blackLevelGreenR_ << 4; + config->gb = blackLevelGreenB_ << 4; + config->b = blackLevelBlue_ << 4; + } else { + auto config = params->block<BlockType::Bls>(); + config.setEnabled(true); + + config->enable_auto = 0; + + /* Scale down to the 12-bit black levels used by the BLS block. */ + config->fixed_val.r = blackLevelRed_ >> 4; + config->fixed_val.gr = blackLevelGreenR_ >> 4; + config->fixed_val.gb = blackLevelGreenB_ >> 4; + config->fixed_val.b = blackLevelBlue_ >> 4; + } +} - params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS; - params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_BLS; +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::SensorBlackLevels, + { static_cast<int32_t>(blackLevelRed_), + static_cast<int32_t>(blackLevelGreenR_), + static_cast<int32_t>(blackLevelGreenB_), + static_cast<int32_t>(blackLevelBlue_) }); } REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection") diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h index 0b1a2d43..f797ae44 100644 --- a/src/ipa/rkisp1/algorithms/blc.h +++ b/src/ipa/rkisp1/algorithms/blc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * blc.h - RkISP1 Black Level Correction control + * RkISP1 Black Level Correction control */ #pragma once @@ -20,12 +20,19 @@ public: ~BlackLevelCorrection() = default; int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; private: - bool tuningParameters_; + bool supported_; + int16_t blackLevelRed_; int16_t blackLevelGreenR_; int16_t blackLevelGreenB_; diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp new file mode 100644 index 00000000..de2b6fe7 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include <map> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +#include <libcamera/ipa/core_ipa_interface.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "libipa/fixedpoint.h" +#include "libipa/interpolator.h" + +/** + * \file ccm.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class Ccm + * \brief A color correction matrix algorithm + */ + +LOG_DEFINE_CATEGORY(RkISP1Ccm) + +constexpr Matrix<float, 3, 3> kIdentity3x3 = Matrix<float, 3, 3>::identity(); + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + cmap[&controls::ColourCorrectionMatrix] = ControlInfo( + ControlValue(-8.0f), + ControlValue(7.993f), + ControlValue(kIdentity3x3.data())); + + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(RkISP1Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, kIdentity3x3 } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(RkISP1Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); + } + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + auto &as = context.activeState; + as.ccm.manual = kIdentity3x3; + as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.temperatureK); + return 0; +} + +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; + + auto &ccm = context.activeState.ccm; + + const auto &colourTemperature = controls.get(controls::ColourTemperature); + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); + if (ccmMatrix) { + ccm.manual = Matrix<float, 3, 3>(*ccmMatrix); + LOG(RkISP1Ccm, Debug) + << "Setting manual CCM from CCM control to " << ccm.manual; + } else if (colourTemperature) { + ccm.manual = ccm_.getInterpolated(*colourTemperature); + LOG(RkISP1Ccm, Debug) + << "Setting manual CCM from CT control to " << ccm.manual; + } + + frameContext.ccm.ccm = ccm.manual; +} + +void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, + const Matrix<float, 3, 3> &matrix, + const Matrix<int16_t, 3, 1> &offsets) +{ + /* + * 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to + * +7.9921875 (0x3ff) + */ + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) + config.coeff[i][j] = + floatingToFixedPoint<4, 7, uint16_t, double>(matrix[i][j]); + } + + for (unsigned int i = 0; i < 3; i++) + config.ct_offset[i] = offsets[i][0] & 0xfff; + + LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix; + LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, RkISP1Params *params) +{ + if (!frameContext.awb.autoEnabled) { + auto config = params->block<BlockType::Ctk>(); + config.setEnabled(true); + setParameters(*config, frameContext.ccm.ccm, Matrix<int16_t, 3, 1>()); + return; + } + + uint32_t ct = frameContext.awb.temperatureK; + if (frame > 0 && ct == ct_) { + frameContext.ccm.ccm = context.activeState.ccm.automatic; + return; + } + + ct_ = ct; + Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct); + Matrix<int16_t, 3, 1> offsets = offsets_.getInterpolated(ct); + + context.activeState.ccm.automatic = ccm; + frameContext.ccm.ccm = ccm; + + auto config = params->block<BlockType::Ctk>(); + config.setEnabled(true); + setParameters(*config, ccm, offsets); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data()); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h new file mode 100644 index 00000000..c301e6e5 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/ccm.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Color Correction Matrix control algorithm + */ + +#pragma once + +#include <linux/rkisp1-config.h> + +#include "libcamera/internal/matrix.h" + +#include "libipa/interpolator.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() {} + ~Ccm() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + void parseYaml(const YamlObject &tuningData); + void setParameters(struct rkisp1_cif_isp_ctk_config &config, + const Matrix<float, 3, 3> &matrix, + const Matrix<int16_t, 3, 1> &offsets); + + unsigned int ct_; + Interpolator<Matrix<float, 3, 3>> ccm_; + Interpolator<Matrix<int16_t, 3, 1>> offsets_; +}; + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index eaa56c37..d1fff699 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * cproc.cpp - RkISP1 Color Processing control + * RkISP1 Color Processing control */ #include "cproc.h" @@ -33,20 +33,71 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1CProc) +namespace { + +constexpr float kDefaultBrightness = 0.0f; +constexpr float kDefaultContrast = 1.0f; +constexpr float kDefaultSaturation = 1.0f; + +int convertBrightness(const float v) +{ + return std::clamp<int>(std::lround(v * 128), -128, 127); +} + +int convertContrastOrSaturation(const float v) +{ + return std::clamp<int>(std::lround(v * 128), 0, 255); +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int ColorProcessing::init(IPAContext &context, + [[maybe_unused]] const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + + cmap[&controls::Brightness] = ControlInfo(-1.0f, 0.993f, kDefaultBrightness); + cmap[&controls::Contrast] = ControlInfo(0.0f, 1.993f, kDefaultContrast); + cmap[&controls::Saturation] = ControlInfo(0.0f, 1.993f, kDefaultSaturation); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int ColorProcessing::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + auto &cproc = context.activeState.cproc; + + cproc.brightness = convertBrightness(kDefaultBrightness); + cproc.contrast = convertContrastOrSaturation(kDefaultContrast); + cproc.saturation = convertContrastOrSaturation(kDefaultSaturation); + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::queueRequest */ void ColorProcessing::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, + const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { auto &cproc = context.activeState.cproc; bool update = false; + if (frame == 0) + update = true; + const auto &brightness = controls.get(controls::Brightness); if (brightness) { - int value = std::clamp<int>(std::lround(*brightness * 128), -128, 127); + int value = convertBrightness(*brightness); if (cproc.brightness != value) { cproc.brightness = value; update = true; @@ -57,7 +108,7 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto &contrast = controls.get(controls::Contrast); if (contrast) { - int value = std::clamp<int>(std::lround(*contrast * 128), 0, 255); + int value = convertContrastOrSaturation(*contrast); if (cproc.contrast != value) { cproc.contrast = value; update = true; @@ -68,7 +119,7 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto saturation = controls.get(controls::Saturation); if (saturation) { - int value = std::clamp<int>(std::lround(*saturation * 128), 0, 255); + int value = convertContrastOrSaturation(*saturation); if (cproc.saturation != value) { cproc.saturation = value; update = true; @@ -89,19 +140,17 @@ void ColorProcessing::queueRequest(IPAContext &context, void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { /* Check if the algorithm configuration has been updated. */ if (!frameContext.cproc.update) return; - params->others.cproc_config.brightness = frameContext.cproc.brightness; - params->others.cproc_config.contrast = frameContext.cproc.contrast; - params->others.cproc_config.sat = frameContext.cproc.saturation; - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC; + auto config = params->block<BlockType::Cproc>(); + config.setEnabled(true); + config->brightness = frameContext.cproc.brightness; + config->contrast = frameContext.cproc.contrast; + config->sat = frameContext.cproc.saturation; } REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing") diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h index ba6e901a..fd38fd17 100644 --- a/src/ipa/rkisp1/algorithms/cproc.h +++ b/src/ipa/rkisp1/algorithms/cproc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * cproc.h - RkISP1 Color Processing control + * RkISP1 Color Processing control */ #pragma once @@ -21,12 +21,15 @@ public: ColorProcessing() = default; ~ColorProcessing() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp index 80a1b734..78946281 100644 --- a/src/ipa/rkisp1/algorithms/dpcc.cpp +++ b/src/ipa/rkisp1/algorithms/dpcc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * dpcc.cpp - RkISP1 Defect Pixel Cluster Correction control + * RkISP1 Defect Pixel Cluster Correction control */ #include "dpcc.h" @@ -232,16 +232,14 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context, void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { if (frame > 0) return; - params->others.dpcc_config = config_; - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC; + auto config = params->block<BlockType::Dpcc>(); + config.setEnabled(true); + *config = config_; } REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection") diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h index b1fac7d1..b77766c3 100644 --- a/src/ipa/rkisp1/algorithms/dpcc.h +++ b/src/ipa/rkisp1/algorithms/dpcc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * dpcc.h - RkISP1 Defect Pixel Cluster Correction control + * RkISP1 Defect Pixel Cluster Correction control */ #pragma once @@ -22,7 +22,7 @@ public: int init(IPAContext &context, const YamlObject &tuningData) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: rkisp1_cif_isp_dpcc_config config_; diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp index 5bd7e59f..cb6095da 100644 --- a/src/ipa/rkisp1/algorithms/dpf.cpp +++ b/src/ipa/rkisp1/algorithms/dpf.cpp @@ -2,12 +2,14 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * dpf.cpp - RkISP1 Denoise Pre-Filter control + * RkISP1 Denoise Pre-Filter control */ #include "dpf.h" -#include <cmath> +#include <algorithm> +#include <string> +#include <vector> #include <libcamera/base/log.h> @@ -215,15 +217,21 @@ void Dpf::queueRequest(IPAContext &context, * \copydoc libcamera::ipa::Algorithm::prepare */ void Dpf::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { - if (frame == 0) { - params->others.dpf_config = config_; - params->others.dpf_strength_config = strengthConfig_; + if (!frameContext.dpf.update && frame > 0) + return; + + auto config = params->block<BlockType::Dpf>(); + config.setEnabled(frameContext.dpf.denoise); + + if (frameContext.dpf.denoise) { + *config = config_; const auto &awb = context.configuration.awb; const auto &lsc = context.configuration.lsc; - auto &mode = params->others.dpf_config.gain.mode; + + auto &mode = config->gain.mode; /* * The DPF needs to take into account the total amount of @@ -241,15 +249,12 @@ void Dpf::prepare(IPAContext &context, const uint32_t frame, mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS; else mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED; - - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF | - RKISP1_CIF_ISP_MODULE_DPF_STRENGTH; } - if (frameContext.dpf.update) { - params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF; - if (frameContext.dpf.denoise) - params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF; + if (frame == 0) { + auto strengthConfig = params->block<BlockType::DpfStrength>(); + strengthConfig.setEnabled(true); + *strengthConfig = strengthConfig_; } } diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h index 58f29f74..2dd8cd36 100644 --- a/src/ipa/rkisp1/algorithms/dpf.h +++ b/src/ipa/rkisp1/algorithms/dpf.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * dpf.h - RkISP1 Denoise Pre-Filter control + * RkISP1 Denoise Pre-Filter control */ #pragma once @@ -27,7 +27,7 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: struct rkisp1_cif_isp_dpf_config config_; diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp index 4b89c05a..7598ef8a 100644 --- a/src/ipa/rkisp1/algorithms/filter.cpp +++ b/src/ipa/rkisp1/algorithms/filter.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * filter.cpp - RkISP1 Filter control + * RkISP1 Filter control */ #include "filter.h" @@ -104,7 +104,7 @@ void Filter::queueRequest(IPAContext &context, */ void Filter::prepare([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, rkisp1_params_cfg *params) + IPAFrameContext &frameContext, RkISP1Params *params) { /* Check if the algorithm configuration has been updated. */ if (!frameContext.filter.update) @@ -160,23 +160,25 @@ void Filter::prepare([[maybe_unused]] IPAContext &context, uint8_t denoise = frameContext.filter.denoise; uint8_t sharpness = frameContext.filter.sharpness; - auto &flt_config = params->others.flt_config; - - flt_config.fac_sh0 = filt_fac_sh0[sharpness]; - flt_config.fac_sh1 = filt_fac_sh1[sharpness]; - flt_config.fac_mid = filt_fac_mid[sharpness]; - flt_config.fac_bl0 = filt_fac_bl0[sharpness]; - flt_config.fac_bl1 = filt_fac_bl1[sharpness]; - - flt_config.lum_weight = kFiltLumWeightDefault; - flt_config.mode = kFiltModeDefault; - flt_config.thresh_sh0 = filt_thresh_sh0[denoise]; - flt_config.thresh_sh1 = filt_thresh_sh1[denoise]; - flt_config.thresh_bl0 = filt_thresh_bl0[denoise]; - flt_config.thresh_bl1 = filt_thresh_bl1[denoise]; - flt_config.grn_stage1 = stage1_select[denoise]; - flt_config.chr_v_mode = filt_chr_v_mode[denoise]; - flt_config.chr_h_mode = filt_chr_h_mode[denoise]; + + auto config = params->block<BlockType::Flt>(); + config.setEnabled(true); + + config->fac_sh0 = filt_fac_sh0[sharpness]; + config->fac_sh1 = filt_fac_sh1[sharpness]; + config->fac_mid = filt_fac_mid[sharpness]; + config->fac_bl0 = filt_fac_bl0[sharpness]; + config->fac_bl1 = filt_fac_bl1[sharpness]; + + config->lum_weight = kFiltLumWeightDefault; + config->mode = kFiltModeDefault; + config->thresh_sh0 = filt_thresh_sh0[denoise]; + config->thresh_sh1 = filt_thresh_sh1[denoise]; + config->thresh_bl0 = filt_thresh_bl0[denoise]; + config->thresh_bl1 = filt_thresh_bl1[denoise]; + config->grn_stage1 = stage1_select[denoise]; + config->chr_v_mode = filt_chr_v_mode[denoise]; + config->chr_h_mode = filt_chr_h_mode[denoise]; /* * Combined high denoising and high sharpening requires some @@ -186,27 +188,23 @@ void Filter::prepare([[maybe_unused]] IPAContext &context, */ if (denoise == 9) { if (sharpness > 3) - flt_config.grn_stage1 = 2; + config->grn_stage1 = 2; } else if (denoise == 10) { if (sharpness > 5) - flt_config.grn_stage1 = 2; + config->grn_stage1 = 2; else if (sharpness > 3) - flt_config.grn_stage1 = 1; + config->grn_stage1 = 1; } if (denoise > 7) { if (sharpness > 7) { - flt_config.fac_bl0 /= 2; - flt_config.fac_bl1 /= 4; + config->fac_bl0 /= 2; + config->fac_bl1 /= 4; } else if (sharpness > 4) { - flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4; - flt_config.fac_bl1 /= 2; + config->fac_bl0 = config->fac_bl0 * 3 / 4; + config->fac_bl1 /= 2; } } - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT; - params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT; } REGISTER_IPA_ALGORITHM(Filter, "Filter") diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h index 3fd882ea..8f858e57 100644 --- a/src/ipa/rkisp1/algorithms/filter.h +++ b/src/ipa/rkisp1/algorithms/filter.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * filter.h - RkISP1 Filter control + * RkISP1 Filter control */ #pragma once @@ -26,7 +26,7 @@ public: const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp new file mode 100644 index 00000000..a9493678 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/goc.cpp @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Gamma out control + */ +#include "goc.h" + +#include <cmath> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "linux/rkisp1-config.h" + +/** + * \file goc.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class GammaOutCorrection + * \brief RkISP1 Gamma out correction + * + * This algorithm implements the gamma out curve for the RkISP1. It defaults to + * a gamma value of 2.2. + * + * As gamma is internally represented as a piecewise linear function with only + * 17 knots, the difference between gamma=2.2 and sRGB gamma is minimal. + * Therefore sRGB gamma was not implemented as special case. + * + * Useful links: + * - https://www.cambridgeincolour.com/tutorials/gamma-correction.htm + * - https://en.wikipedia.org/wiki/SRGB + */ + +LOG_DEFINE_CATEGORY(RkISP1Gamma) + +const float kDefaultGamma = 2.2f; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData) +{ + if (context.hw->numGammaOutSamples != + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10) { + LOG(RkISP1Gamma, Error) + << "Gamma is not implemented for RkISP1 V12"; + return -EINVAL; + } + + defaultGamma_ = tuningData["gamma"].get<double>(kDefaultGamma); + context.ctrlMap[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int GammaOutCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + context.activeState.goc.gamma = defaultGamma_; + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void GammaOutCorrection::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + if (frame == 0) + frameContext.goc.update = true; + + const auto &gamma = controls.get(controls::Gamma); + if (gamma) { + context.activeState.goc.gamma = *gamma; + frameContext.goc.update = true; + LOG(RkISP1Gamma, Debug) << "Set gamma to " << *gamma; + } + + frameContext.goc.gamma = context.activeState.goc.gamma; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaOutCorrection::prepare(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) +{ + ASSERT(context.hw->numGammaOutSamples == + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10); + + if (!frameContext.goc.update) + return; + + /* + * The logarithmic segments as specified in the reference. + * Plus an additional 0 to make the loop easier + */ + static constexpr std::array<unsigned int, RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10> segments = { + 64, 64, 64, 64, 128, 128, 128, 128, 256, + 256, 256, 512, 512, 512, 512, 512, 0 + }; + + auto config = params->block<BlockType::Goc>(); + config.setEnabled(true); + + __u16 *gamma_y = config->gamma_y; + + unsigned x = 0; + for (const auto [i, size] : utils::enumerate(segments)) { + gamma_y[i] = std::pow(x / 4096.0, 1.0 / frameContext.goc.gamma) * 1023.0; + x += size; + } + + config->mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void GammaOutCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::Gamma, frameContext.goc.gamma); +} + +REGISTER_IPA_ALGORITHM(GammaOutCorrection, "GammaOutCorrection") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h new file mode 100644 index 00000000..bb2ddfc9 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/goc.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 Gamma out control + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class GammaOutCorrection : public Algorithm +{ +public: + GammaOutCorrection() = default; + ~GammaOutCorrection() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + float defaultGamma_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp index b9f87912..9604c0ac 100644 --- a/src/ipa/rkisp1/algorithms/gsl.cpp +++ b/src/ipa/rkisp1/algorithms/gsl.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * gsl.cpp - RkISP1 Gamma Sensor Linearization control + * RkISP1 Gamma Sensor Linearization control */ #include "gsl.h" @@ -119,24 +119,20 @@ int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) + RkISP1Params *params) { if (frame > 0) return; - params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0]; - params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1]; + auto config = params->block<BlockType::Sdg>(); + config.setEnabled(true); - std::copy(curveYr_.begin(), curveYr_.end(), - params->others.sdg_config.curve_r.gamma_y); - std::copy(curveYg_.begin(), curveYg_.end(), - params->others.sdg_config.curve_g.gamma_y); - std::copy(curveYb_.begin(), curveYb_.end(), - params->others.sdg_config.curve_b.gamma_y); + config->xa_pnts.gamma_dx0 = gammaDx_[0]; + config->xa_pnts.gamma_dx1 = gammaDx_[1]; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG; - params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG; + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r.gamma_y); + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g.gamma_y); + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b.gamma_y); } REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h index 0f1116a7..91cf6efa 100644 --- a/src/ipa/rkisp1/algorithms/gsl.h +++ b/src/ipa/rkisp1/algorithms/gsl.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * gsl.h - RkISP1 Gamma Sensor Linearization control + * RkISP1 Gamma Sensor Linearization control */ #pragma once @@ -22,7 +22,7 @@ public: int init(IPAContext &context, const YamlObject &tuningData) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; private: uint32_t gammaDx_[2]; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index a7ccedb1..e7301bfe 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * lsc.cpp - RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction control */ #include "lsc.h" @@ -16,6 +16,7 @@ #include "libcamera/internal/yaml_parser.h" +#include "libipa/lsc_polynomial.h" #include "linux/rkisp1-config.h" /** @@ -24,6 +25,36 @@ namespace libcamera { +namespace ipa { + +constexpr int kColourTemperatureChangeThreshhold = 10; + +template<typename T> +void interpolateVector(const std::vector<T> &a, const std::vector<T> &b, + std::vector<T> &dest, double lambda) +{ + assert(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) { + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; + } +} + +template<> +void Interpolator<rkisp1::algorithms::LensShadingCorrection::Components>:: + interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a, + const rkisp1::algorithms::LensShadingCorrection::Components &b, + rkisp1::algorithms::LensShadingCorrection::Components &dest, + double lambda) +{ + interpolateVector(a.r, b.r, dest.r, lambda); + interpolateVector(a.gr, b.gr, dest.gr, lambda); + interpolateVector(a.gb, b.gb, dest.gb, lambda); + interpolateVector(a.b, b.b, dest.b, lambda); +} + +} /* namespace ipa */ + namespace ipa::rkisp1::algorithms { /** @@ -40,6 +71,200 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Lsc) +class LscPolynomialLoader +{ +public: + LscPolynomialLoader(const Size &sensorSize, + const Rectangle &cropRectangle, + const std::vector<double> &xSizes, + const std::vector<double> &ySizes) + : sensorSize_(sensorSize), + cropRectangle_(cropRectangle), + xSizes_(xSizes), + ySizes_(ySizes) + { + } + + int parseLscData(const YamlObject &yamlSets, + std::map<unsigned int, LensShadingCorrection::Components> &lscData) + { + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + std::optional<LscPolynomial> pr, pgr, pgb, pb; + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + pr = yamlSet["r"].get<LscPolynomial>(); + pgr = yamlSet["gr"].get<LscPolynomial>(); + pgb = yamlSet["gb"].get<LscPolynomial>(); + pb = yamlSet["b"].get<LscPolynomial>(); + + if (!(pr || pgr || pgb || pb)) { + LOG(RkISP1Lsc, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + set.ct = ct; + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + set.r = samplePolynomial(*pr); + set.gr = samplePolynomial(*pgr); + set.gb = samplePolynomial(*pgb); + set.b = samplePolynomial(*pb); + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + /* + * The lsc grid has custom spacing defined on half the range (see + * parseSizes() for details). For easier handling this function converts + * the spaces vector to positions and mirrors them. E.g.: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ + std::vector<double> sizesListToPositions(const std::vector<double> &sizes) + { + const int half = sizes.size(); + std::vector<double> res(half * 2 + 1); + double x = 0.0; + + res[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + res[half - i] = 0.5 - x; + res[half + i] = 0.5 + x; + } + + return res; + } + + std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly) + { + constexpr int k = RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + double m = poly.getM(); + double x0 = cropRectangle_.x / m; + double y0 = cropRectangle_.y / m; + double w = cropRectangle_.width / m; + double h = cropRectangle_.height / m; + std::vector<uint16_t> res; + + assert(xSizes_.size() * 2 + 1 == k); + assert(ySizes_.size() * 2 + 1 == k); + + res.reserve(k * k); + + std::vector<double> xPos(sizesListToPositions(xSizes_)); + std::vector<double> yPos(sizesListToPositions(ySizes_)); + + for (int y = 0; y < k; y++) { + for (int x = 0; x < k; x++) { + double xp = x0 + xPos[x] * w; + double yp = y0 + yPos[y] * h; + /* + * The hardware uses 2.10 fixed point format and + * limits the legal values to [1..3.999]. Scale + * and clamp the sampled value accordingly. + */ + int v = static_cast<int>( + poly.sampleAtNormalizedPixelPos(xp, yp) * + 1024); + v = std::min(std::max(v, 1024), 4095); + res.push_back(v); + } + } + return res; + } + + Size sensorSize_; + Rectangle cropRectangle_; + const std::vector<double> &xSizes_; + const std::vector<double> &ySizes_; +}; + +class LscTableLoader +{ +public: + int parseLscData(const YamlObject &yamlSets, + std::map<unsigned int, LensShadingCorrection::Components> &lscData) + { + const auto &sets = yamlSets.asList(); + + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + LensShadingCorrection::Components &set = lscData[ct]; + + set.ct = ct; + set.r = parseTable(yamlSet, "r"); + set.gr = parseTable(yamlSet, "gr"); + set.gb = parseTable(yamlSet, "gb"); + set.b = parseTable(yamlSet, "b"); + + if (set.r.empty() || set.gr.empty() || + set.gb.empty() || set.b.empty()) { + LOG(RkISP1Lsc, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + +private: + std::vector<uint16_t> parseTable(const YamlObject &tuningData, + const char *prop) + { + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector<uint16_t> table = + tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (table.size() != kLscNumSamples) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; + } +}; + static std::vector<double> parseSizes(const YamlObject &tuningData, const char *prop) { @@ -70,28 +295,10 @@ static std::vector<double> parseSizes(const YamlObject &tuningData, return sizes; } -static std::vector<uint16_t> parseTable(const YamlObject &tuningData, - const char *prop) -{ - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; - - std::vector<uint16_t> table = - tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{}); - if (table.size() != kLscNumSamples) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << kLscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - LensShadingCorrection::LensShadingCorrection() - : lastCt_({ 0, 0 }) + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { + sets_.setQuantization(kColourTemperatureChangeThreshhold); } /** @@ -114,38 +321,30 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["ct"].get<uint32_t>(0); - - if (sets_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - Components &set = sets_[ct]; - - set.ct = ct; - set.r = parseTable(yamlSet, "r"); - set.gr = parseTable(yamlSet, "gr"); - set.gb = parseTable(yamlSet, "gb"); - set.b = parseTable(yamlSet, "b"); - - if (set.r.empty() || set.gr.empty() || - set.gb.empty() || set.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } + std::map<unsigned int, Components> lscData; + int res = 0; + std::string type = tuningData["type"].get<std::string>("table"); + if (type == "table") { + LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; + auto loader = LscTableLoader(); + res = loader.parseLscData(yamlSets, lscData); + } else if (type == "polynomial") { + LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; + auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize, + context.sensorInfo.analogCrop, + xSize_, + ySize_); + res = loader.parseLscData(yamlSets, lscData); + } else { + LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" + << type << "'"; + res = -EINVAL; } - if (sets_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } + if (res) + return res; + + sets_.setData(std::move(lscData)); return 0; } @@ -185,18 +384,12 @@ int LensShadingCorrection::configure(IPAContext &context, return 0; } -void LensShadingCorrection::setParameters(rkisp1_params_cfg *params) +void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) { - struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; - memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl)); memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl)); memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl)); memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl)); - - params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC; } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, @@ -208,131 +401,34 @@ void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); } -/* - * Interpolate LSC parameters based on color temperature value. - */ -void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config, - const Components &set0, - const Components &set1, - const uint32_t ct) -{ - double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct); - double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct); - - for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) { - for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) { - unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j; - - config.r_data_tbl[i][j] = - set0.r[sample] * coeff0 + - set1.r[sample] * coeff1; - - config.gr_data_tbl[i][j] = - set0.gr[sample] * coeff0 + - set1.gr[sample] * coeff1; - - config.gb_data_tbl[i][j] = - set0.gb[sample] * coeff0 + - set1.gb[sample] * coeff1; - - config.b_data_tbl[i][j] = - set0.b[sample] * coeff0 + - set1.b[sample] * coeff1; - } - } -} - /** * \copydoc libcamera::ipa::Algorithm::prepare */ -void LensShadingCorrection::prepare(IPAContext &context, - const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, - rkisp1_params_cfg *params) +void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) { - struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; - - /* - * If there is only one set, the configuration has already been done - * for first frame. - */ - if (sets_.size() == 1 && frame > 0) + uint32_t ct = frameContext.awb.temperatureK; + if (std::abs(static_cast<int>(ct) - static_cast<int>(lastAppliedCt_)) < + kColourTemperatureChangeThreshhold) return; - - /* - * If there is only one set, pick it. We can ignore lastCt_, as it will - * never be relevant. - */ - if (sets_.size() == 1) { - setParameters(params); - copyTable(config, sets_.cbegin()->second); - return; - } - - uint32_t ct = context.activeState.awb.temperatureK; - ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first); - - /* - * If the original is the same, then it means the same adjustment would - * be made. If the adjusted is the same, then it means that it's the - * same as what was actually applied. Thus in these cases we can skip - * reprogramming the LSC. - * - * original == adjusted can only happen if an interpolation - * happened, or if original has an exact entry in sets_. This means - * that if original != adjusted, then original was adjusted to - * the nearest available entry in sets_, resulting in adjusted. - * Clearly, any ct value that is in between original and adjusted - * will be adjusted to the same adjusted value, so we can skip - * reprogramming the LSC table. - * - * We also skip updating the original value, as the last one had a - * larger bound and thus a larger range of ct values that will be - * adjusted to the same adjusted. - */ - if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) || - (lastCt_.adjusted <= ct && ct <= lastCt_.original)) + unsigned int quantizedCt; + const Components &set = sets_.getInterpolated(ct, &quantizedCt); + if (lastAppliedQuantizedCt_ == quantizedCt) return; - setParameters(params); + auto config = params->block<BlockType::Lsc>(); + config.setEnabled(true); + setParameters(*config); + copyTable(*config, set); - /* - * The color temperature matches exactly one of the available LSC tables. - */ - if (sets_.count(ct)) { - copyTable(config, sets_[ct]); - lastCt_ = { ct, ct }; - return; - } - - /* No shortcuts left; we need to round or interpolate */ - auto iter = sets_.upper_bound(ct); - const Components &set1 = iter->second; - const Components &set0 = (--iter)->second; - uint32_t ct0 = set0.ct; - uint32_t ct1 = set1.ct; - uint32_t diff0 = ct - ct0; - uint32_t diff1 = ct1 - ct; - static constexpr double kThreshold = 0.1; - float threshold = kThreshold * (ct1 - ct0); - - if (diff0 < threshold || diff1 < threshold) { - const Components &set = diff0 < diff1 ? set0 : set1; - LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct; - copyTable(config, set); - lastCt_ = { ct, set.ct }; - return; - } + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; - /* - * ct is not within 10% of the difference between the neighbouring - * color temperatures, so we need to interpolate. - */ LOG(RkISP1Lsc, Debug) - << "ct is " << ct << ", interpolating between " - << ct0 << " and " << ct1; - interpolateTable(config, set0, set1, ct); - lastCt_ = { ct, ct }; + << "ct is " << ct << ", quantized to " + << quantizedCt; } REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index e2a93a56..5a0824e3 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -2,13 +2,15 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * lsc.h - RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction control */ #pragma once #include <map> +#include "libipa/interpolator.h" + #include "algorithm.h" namespace libcamera { @@ -25,9 +27,8 @@ public: int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, - rkisp1_params_cfg *params) override; + RkISP1Params *params) override; -private: struct Components { uint32_t ct; std::vector<uint16_t> r; @@ -36,23 +37,23 @@ private: std::vector<uint16_t> b; }; - void setParameters(rkisp1_params_cfg *params); +private: + void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); void interpolateTable(rkisp1_cif_isp_lsc_config &config, const Components &set0, const Components &set1, const uint32_t ct); - std::map<uint32_t, Components> sets_; + ipa::Interpolator<Components> sets_; std::vector<double> xSize_; std::vector<double> ySize_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; - struct { - uint32_t original; - uint32_t adjusted; - } lastCt_; + + unsigned int lastAppliedCt_; + unsigned int lastAppliedQuantizedCt_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp new file mode 100644 index 00000000..a467767e --- /dev/null +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * lux.cpp - RkISP1 Lux control + */ + +#include "lux.h" + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +#include "libipa/histogram.h" +#include "libipa/lux.h" + +/** + * \file lux.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class Lux + * \brief RkISP1 Lux control + * + * The Lux algorithm is responsible for estimating the lux level of the image. + * It doesn't take or generate any controls, but it provides a lux level for + * other algorithms (such as AGC) to use. + */ + +/** + * \brief Construct an rkisp1 Lux algo module + */ +Lux::Lux() +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Lux::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + return lux_.parseTuningData(tuningData); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Lux::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + utils::Duration exposureTime = context.configuration.sensor.lineDuration + * frameContext.sensor.exposure; + double gain = frameContext.sensor.gain; + + /* \todo Deduplicate the histogram calculation from AGC */ + const rkisp1_cif_isp_stat *params = &stats->params; + Histogram yHist({ params->hist.hist_bins, context.hw->numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + + double lux = lux_.estimateLux(exposureTime, gain, 1.0, yHist); + frameContext.lux.lux = lux; + metadata.set(controls::Lux, lux); +} + +REGISTER_IPA_ALGORITHM(Lux, "Lux") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lux.h b/src/ipa/rkisp1/algorithms/lux.h new file mode 100644 index 00000000..8a90de55 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/lux.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * lux.h - RkISP1 Lux control + */ + +#pragma once + +#include <sys/types.h> + +#include "libipa/lux.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class Lux : public Algorithm +{ +public: + Lux(); + + int init(IPAContext &context, const YamlObject &tuningData) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + ipa::Lux lux_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build index 93a48329..c66b0b70 100644 --- a/src/ipa/rkisp1/algorithms/meson.build +++ b/src/ipa/rkisp1/algorithms/meson.build @@ -4,10 +4,13 @@ rkisp1_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'cproc.cpp', 'dpcc.cpp', 'dpf.cpp', 'filter.cpp', + 'goc.cpp', 'gsl.cpp', 'lsc.cpp', + 'lux.cpp', ]) diff --git a/src/ipa/rkisp1/data/imx219.yaml b/src/ipa/rkisp1/data/imx219.yaml index cbcc43b8..0d99cb52 100644 --- a/src/ipa/rkisp1/data/imx219.yaml +++ b/src/ipa/rkisp1/data/imx219.yaml @@ -6,10 +6,6 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 256 - Gr: 256 - Gb: 256 - B: 256 - LensShadingCorrection: x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] diff --git a/src/ipa/rkisp1/data/imx258.yaml b/src/ipa/rkisp1/data/imx258.yaml index 43dddf20..202af36a 100644 --- a/src/ipa/rkisp1/data/imx258.yaml +++ b/src/ipa/rkisp1/data/imx258.yaml @@ -5,6 +5,7 @@ version: 1 algorithms: - Agc: - Awb: + - BlackLevelCorrection: - LensShadingCorrection: x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ] diff --git a/src/ipa/rkisp1/data/meson.build b/src/ipa/rkisp1/data/meson.build index f5e9fa75..1e3522b2 100644 --- a/src/ipa/rkisp1/data/meson.build +++ b/src/ipa/rkisp1/data/meson.build @@ -2,10 +2,15 @@ conf_files = files([ 'imx219.yaml', + 'imx258.yaml', + 'ov2685.yaml', 'ov4689.yaml', 'ov5640.yaml', + 'ov5695.yaml', + 'ov8858.yaml', 'uncalibrated.yaml', ]) install_data(conf_files, - install_dir : ipa_data_dir / 'rkisp1') + install_dir : ipa_data_dir / 'rkisp1', + install_tag : 'runtime') diff --git a/src/ipa/rkisp1/data/ov4689.yaml b/src/ipa/rkisp1/data/ov4689.yaml index 2068684c..60901296 100644 --- a/src/ipa/rkisp1/data/ov4689.yaml +++ b/src/ipa/rkisp1/data/ov4689.yaml @@ -6,8 +6,4 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 66 - Gr: 66 - Gb: 66 - B: 66 ... diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml index 897b83cb..4b21d412 100644 --- a/src/ipa/rkisp1/data/ov5640.yaml +++ b/src/ipa/rkisp1/data/ov5640.yaml @@ -6,10 +6,6 @@ algorithms: - Agc: - Awb: - BlackLevelCorrection: - R: 256 - Gr: 256 - Gb: 256 - B: 256 - ColorProcessing: - GammaSensorLinearization: x-intervals: [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ] diff --git a/src/ipa/rkisp1/data/uncalibrated.yaml b/src/ipa/rkisp1/data/uncalibrated.yaml index a7bbd8d8..60901296 100644 --- a/src/ipa/rkisp1/data/uncalibrated.yaml +++ b/src/ipa/rkisp1/data/uncalibrated.yaml @@ -5,4 +5,5 @@ version: 1 algorithms: - Agc: - Awb: + - BlackLevelCorrection: ... diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 9bbf3684..65096105 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * ipa_context.cpp - RkISP1 IPA Context + * RkISP1 IPA Context */ #include "ipa_context.h" @@ -15,6 +15,25 @@ namespace libcamera::ipa::rkisp1 { /** + * \struct IPAHwSettings + * \brief RkISP1 version-specific hardware parameters + */ + +/** + * \var IPAHwSettings::numAeCells + * \brief Number of cells in the AE exposure means grid + * + * \var IPAHwSettings::numHistogramBins + * \brief Number of bins in the histogram + * + * \var IPAHwSettings::numHistogramWeights + * \brief Number of weights in the histogram grid + * + * \var IPAHwSettings::numGammaOutSamples + * \brief Number of samples in the gamma out table + */ + +/** * \struct IPASessionConfiguration * \brief Session configuration for the IPA module * @@ -33,14 +52,6 @@ namespace libcamera::ipa::rkisp1 { */ /** - * \var IPASessionConfiguration::hw - * \brief RkISP1-specific hardware information - * - * \var IPASessionConfiguration::hw.revision - * \brief Hardware revision of the ISP - */ - -/** * \var IPASessionConfiguration::awb * \brief AWB parameters configuration of the IPA * @@ -67,11 +78,11 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor * \brief Sensor-specific configuration of the IPA * - * \var IPASessionConfiguration::sensor.minShutterSpeed - * \brief Minimum shutter speed supported with the sensor + * \var IPASessionConfiguration::sensor.minExposureTime + * \brief Minimum exposure time supported with the sensor * - * \var IPASessionConfiguration::sensor.maxShutterSpeed - * \brief Maximum shutter speed supported with the sensor + * \var IPASessionConfiguration::sensor.maxExposureTime + * \brief Maximum exposure time supported with the sensor * * \var IPASessionConfiguration::sensor.minAnalogueGain * \brief Minimum analogue gain supported with the sensor @@ -95,6 +106,11 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPASessionConfiguration::paramFormat + * \brief The fourcc of the parameters buffers format + */ + +/** * \struct IPAActiveState * \brief Active state for algorithms * @@ -126,54 +142,86 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * The exposure and gain are the latest values computed by the AGC algorithm. + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. * - * \var IPAActiveState::agc.exposure - * \brief Exposure time expressed as a number of lines + * \struct IPAActiveState::agc.manual + * \brief Manual exposure time and analog gain (set through requests) * - * \var IPAActiveState::agc.gain - * \brief Analogue gain multiplier - */ - -/** - * \var IPAActiveState::awb - * \brief State for the Automatic White Balance algorithm + * \var IPAActiveState::agc.manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control * - * \struct IPAActiveState::awb.gains - * \brief White balance gains + * \var IPAActiveState::agc.manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control * - * \struct IPAActiveState::awb.gains.manual - * \brief Manual white balance gains (set through requests) + * \struct IPAActiveState::agc.automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) * - * \var IPAActiveState::awb.gains.manual.red - * \brief Manual white balance gain for R channel + * \var IPAActiveState::agc.automatic.exposure + * \brief Automatic exposure time expressed as a number of lines * - * \var IPAActiveState::awb.gains.manual.green - * \brief Manual white balance gain for G channel + * \var IPAActiveState::agc.automatic.gain + * \brief Automatic analogue gain multiplier * - * \var IPAActiveState::awb.gains.manual.blue - * \brief Manual white balance gain for B channel + * \var IPAActiveState::agc.autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control * - * \struct IPAActiveState::awb.gains.automatic - * \brief Automatic white balance gains (computed by the algorithm) + * \var IPAActiveState::agc.autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control * - * \var IPAActiveState::awb.gains.automatic.red - * \brief Automatic white balance gain for R channel + * \var IPAActiveState::agc.constraintMode + * \brief Constraint mode as set by the AeConstraintMode control * - * \var IPAActiveState::awb.gains.automatic.green - * \brief Automatic white balance gain for G channel + * \var IPAActiveState::agc.exposureMode + * \brief Exposure mode as set by the AeExposureMode control * - * \var IPAActiveState::awb.gains.automatic.blue - * \brief Automatic white balance gain for B channel + * \var IPAActiveState::agc.meteringMode + * \brief Metering mode as set by the AeMeteringMode control * - * \var IPAActiveState::awb.temperatureK - * \brief Estimated color temperature + * \var IPAActiveState::agc.minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var IPAActiveState::agc.maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \var IPAActiveState::awb + * \brief State for the Automatic White Balance algorithm + * + * \struct IPAActiveState::awb::AwbState + * \brief Struct for the AWB regulation state + * + * \var IPAActiveState::awb::AwbState.gains + * \brief White balance gains + * + * \var IPAActiveState::awb::AwbState.temperatureK + * \brief Color temperature + * + * \var IPAActiveState::awb.manual + * \brief Manual regulation state (set through requests) + * + * \var IPAActiveState::awb.automatic + * \brief Automatic regulation state (computed by the algorithm) * * \var IPAActiveState::awb.autoEnabled * \brief Whether the Auto White Balance algorithm is enabled */ /** + * \var IPAActiveState::ccm + * \brief State for the Colour Correction Matrix algorithm + * + * \var IPAActiveState::ccm.manual + * \brief Manual CCM (set through requests) + * + * \var IPAActiveState::awb.automatic + * \brief Automatic CCM (computed by the algorithm) + */ + +/** * \var IPAActiveState::cproc * \brief State for the Color Processing algorithm * @@ -207,6 +255,14 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPAActiveState::goc + * \brief State for the goc algorithm + * + * \var IPAActiveState::goc.gamma + * \brief Gamma value applied as 1.0/gamma + */ + +/** * \struct IPAFrameContext * \brief Per-frame context for algorithms * @@ -243,15 +299,57 @@ namespace libcamera::ipa::rkisp1 { * \brief Automatic Gain Control parameters for this frame * * The exposure and gain are provided by the AGC algorithm, and are to be - * applied to the sensor in order to take effect for this frame. + * applied to the sensor in order to take effect for this frame. Additionally + * the vertical blanking period is determined to maintain a consistent frame + * rate matched to the FrameDurationLimits as set by the user. * * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines + * \brief Exposure time expressed as a number of lines computed by the algorithm * * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier + * \brief Analogue gain multiplier computed by the algorithm * * The gain should be adapted to the sensor specific gain code before applying. + * + * \var IPAFrameContext::agc.vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var IPAFrameContext::agc.autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var IPAFrameContext::agc.autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var IPAFrameContext::agc.constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var IPAFrameContext::agc.exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var IPAFrameContext::agc.meteringMode + * \brief Metering mode as set by the AeMeteringMode control + * + * \var IPAFrameContext::agc.minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var IPAFrameContext::agc.maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var IPAFrameContext::agc.frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var IPAFrameContext::agc.updateMetering + * \brief Indicate if new ISP AGC metering parameters need to be applied + * + * \var IPAFrameContext::agc.autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame. + * + * \var IPAFrameContext::agc.autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame. */ /** @@ -261,23 +359,25 @@ namespace libcamera::ipa::rkisp1 { * \struct IPAFrameContext::awb.gains * \brief White balance gains * - * \var IPAFrameContext::awb.gains.red - * \brief White balance gain for R channel - * - * \var IPAFrameContext::awb.gains.green - * \brief White balance gain for G channel - * - * \var IPAFrameContext::awb.gains.blue - * \brief White balance gain for B channel - * * \var IPAFrameContext::awb.temperatureK - * \brief Estimated color temperature + * \brief Color temperature used for processing this frame + * + * This does not match the color temperature estimated for this frame as the + * measurements were taken on a previous frame. * * \var IPAFrameContext::awb.autoEnabled * \brief Whether the Auto White Balance algorithm is enabled */ /** + * \var IPAFrameContext::ccm + * \brief Colour Correction Matrix parameters for this frame + * + * \struct IPAFrameContext::ccm.ccm + * \brief Colour Correction Matrix + */ + +/** * \var IPAFrameContext::cproc * \brief Color Processing parameters for this frame * @@ -323,6 +423,18 @@ namespace libcamera::ipa::rkisp1 { */ /** + * \var IPAFrameContext::goc + * \brief Gamma out correction parameters for this frame + * + * \var IPAFrameContext::goc.gamma + * \brief Gamma value applied as 1.0/gamma + * + * \var IPAFrameContext::goc.update + * \brief Indicates if the goc parameters have been updated compared to the + * previous frame + */ + +/** * \var IPAFrameContext::sensor * \brief Sensor configuration that used been used for this frame * @@ -337,6 +449,12 @@ namespace libcamera::ipa::rkisp1 { * \struct IPAContext * \brief Global IPA context data shared between all algorithms * + * \var IPAContext::hw + * \brief RkISP1 version-specific hardware parameters + * + * \var IPAContext::sensorInfo + * \brief The IPA session sensorInfo, immutable during the session + * * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index b9b20653..f0d50421 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -2,24 +2,43 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * ipa_context.h - RkISP1 IPA Context + * RkISP1 IPA Context * */ #pragma once +#include <memory> + #include <linux/rkisp1-config.h> #include <libcamera/base/utils.h> +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> #include <libcamera/geometry.h> +#include <libcamera/ipa/core_ipa_interface.h> + +#include "libcamera/internal/debug_controls.h" +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/vector.h" + +#include <libipa/camera_sensor_helper.h> #include <libipa/fc_queue.h> namespace libcamera { namespace ipa::rkisp1 { +struct IPAHwSettings { + unsigned int numAeCells; + unsigned int numHistogramBins; + unsigned int numHistogramWeights; + unsigned int numGammaOutSamples; + bool compand; +}; + struct IPASessionConfiguration { struct { struct rkisp1_cif_isp_window measureWindow; @@ -35,8 +54,8 @@ struct IPASessionConfiguration { } lsc; struct { - utils::Duration minShutterSpeed; - utils::Duration maxShutterSpeed; + utils::Duration minExposureTime; + utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; @@ -45,11 +64,8 @@ struct IPASessionConfiguration { Size size; } sensor; - struct { - rkisp1_cif_isp_version revision; - } hw; - bool raw; + uint32_t paramFormat; }; struct IPAActiveState { @@ -63,28 +79,33 @@ struct IPAActiveState { double gain; } automatic; - bool autoEnabled; + bool autoExposureEnabled; + bool autoGainEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + controls::AeMeteringModeEnum meteringMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; } agc; struct { - struct { - struct { - double red; - double green; - double blue; - } manual; - struct { - double red; - double green; - double blue; - } automatic; - } gains; + struct AwbState { + RGB<double> gains; + unsigned int temperatureK; + }; + + AwbState manual; + AwbState automatic; - unsigned int temperatureK; bool autoEnabled; } awb; struct { + Matrix<float, 3, 3> manual; + Matrix<float, 3, 3> automatic; + } ccm; + + struct { int8_t brightness; uint8_t contrast; uint8_t saturation; @@ -98,24 +119,34 @@ struct IPAActiveState { uint8_t denoise; uint8_t sharpness; } filter; + + struct { + double gamma; + } goc; }; struct IPAFrameContext : public FrameContext { struct { uint32_t exposure; double gain; - bool autoEnabled; + uint32_t vblank; + bool autoExposureEnabled; + bool autoGainEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + controls::AeMeteringModeEnum meteringMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + utils::Duration frameDuration; + bool updateMetering; + bool autoExposureModeChange; + bool autoGainModeChange; } agc; struct { - struct { - double red; - double green; - double blue; - } gains; - - unsigned int temperatureK; + RGB<double> gains; bool autoEnabled; + unsigned int temperatureK; } awb; struct { @@ -137,16 +168,43 @@ struct IPAFrameContext : public FrameContext { } filter; struct { + double gamma; + bool update; + } goc; + + struct { uint32_t exposure; double gain; } sensor; + + struct { + Matrix<float, 3, 3> ccm; + } ccm; + + struct { + double lux; + } lux; }; struct IPAContext { + IPAContext(unsigned int frameContextSize) + : hw(nullptr), frameContexts(frameContextSize) + { + } + + const IPAHwSettings *hw; + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue<IPAFrameContext> frameContexts; + + ControlInfoMap::Map ctrlMap; + + DebugMetadata debugMetadata; + + /* Interface to the Camera Helper */ + std::unique_ptr<CameraSensorHelper> camHelper; }; } /* namespace ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build index e813da53..26a9fa40 100644 --- a/src/ipa/rkisp1/meson.build +++ b/src/ipa/rkisp1/meson.build @@ -7,17 +7,16 @@ ipa_name = 'ipa_rkisp1' rkisp1_ipa_sources = files([ 'ipa_context.cpp', + 'params.cpp', 'rkisp1.cpp', ]) rkisp1_ipa_sources += rkisp1_ipa_algorithms -mod = shared_module(ipa_name, - [rkisp1_ipa_sources, libcamera_generated_ipa_headers], +mod = shared_module(ipa_name, rkisp1_ipa_sources, name_prefix : '', - include_directories : [ipa_includes, libipa_includes], - dependencies : libcamera_private, - link_with : libipa, + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], install : true, install_dir : ipa_install_dir) diff --git a/src/ipa/rkisp1/module.h b/src/ipa/rkisp1/module.h index 89f83208..69e9bc82 100644 --- a/src/ipa/rkisp1/module.h +++ b/src/ipa/rkisp1/module.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas On Board * - * module.h - RkISP1 IPA Module + * RkISP1 IPA Module */ #pragma once @@ -14,13 +14,14 @@ #include <libipa/module.h> #include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::rkisp1 { using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo, - rkisp1_params_cfg, rkisp1_stat_buffer>; + RkISP1Params, rkisp1_stat_buffer>; } /* namespace ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/params.cpp b/src/ipa/rkisp1/params.cpp new file mode 100644 index 00000000..4c0b051c --- /dev/null +++ b/src/ipa/rkisp1/params.cpp @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 ISP Parameters + */ + +#include "params.h" + +#include <map> +#include <stddef.h> +#include <string.h> + +#include <linux/rkisp1-config.h> +#include <linux/videodev2.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +namespace libcamera { + +LOG_DEFINE_CATEGORY(RkISP1Params) + +namespace ipa::rkisp1 { + +namespace { + +struct BlockTypeInfo { + enum rkisp1_ext_params_block_type type; + size_t size; + size_t offset; + uint32_t enableBit; +}; + +#define RKISP1_BLOCK_TYPE_ENTRY(block, id, type, category, bit) \ + { BlockType::block, { \ + RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \ + sizeof(struct rkisp1_cif_isp_##type##_config), \ + offsetof(struct rkisp1_params_cfg, category.type##_config), \ + RKISP1_CIF_ISP_MODULE_##bit, \ + } } + +#define RKISP1_BLOCK_TYPE_ENTRY_MEAS(block, id, type) \ + RKISP1_BLOCK_TYPE_ENTRY(block, id##_MEAS, type, meas, id) + +#define RKISP1_BLOCK_TYPE_ENTRY_OTHERS(block, id, type) \ + RKISP1_BLOCK_TYPE_ENTRY(block, id, type, others, id) + +#define RKISP1_BLOCK_TYPE_ENTRY_EXT(block, id, type) \ + { BlockType::block, { \ + RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \ + sizeof(struct rkisp1_cif_isp_##type##_config), \ + 0, 0, \ + } } + +const std::map<BlockType, BlockTypeInfo> kBlockTypeInfo = { + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bls, BLS, bls), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpcc, DPCC, dpcc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Sdg, SDG, sdg), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(AwbGain, AWB_GAIN, awb_gain), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Flt, FLT, flt), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bdm, BDM, bdm), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ctk, CTK, ctk), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Goc, GOC, goc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpf, DPF, dpf), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(DpfStrength, DPF_STRENGTH, dpf_strength), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Cproc, CPROC, cproc), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ie, IE, ie), + RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Lsc, LSC, lsc), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Awb, AWB, awb_meas), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Hst, HST, hst), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Aec, AEC, aec), + RKISP1_BLOCK_TYPE_ENTRY_MEAS(Afc, AFC, afc), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandBls, COMPAND_BLS, compand_bls), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandExpand, COMPAND_EXPAND, compand_curve), + RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandCompress, COMPAND_COMPRESS, compand_curve), +}; + +} /* namespace */ + +RkISP1ParamsBlockBase::RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type, + const Span<uint8_t> &data) + : params_(params), type_(type) +{ + if (params_->format() == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) { + header_ = data.subspan(0, sizeof(rkisp1_ext_params_block_header)); + data_ = data.subspan(sizeof(rkisp1_ext_params_block_header)); + } else { + data_ = data; + } +} + +void RkISP1ParamsBlockBase::setEnabled(bool enabled) +{ + /* + * For the legacy fixed format, blocks are enabled in the top-level + * header. Delegate to the RkISP1Params class. + */ + if (params_->format() == V4L2_META_FMT_RK_ISP1_PARAMS) + return params_->setBlockEnabled(type_, enabled); + + /* + * For the extensible format, set the enable and disable flags in the + * block header directly. + */ + struct rkisp1_ext_params_block_header *header = + reinterpret_cast<struct rkisp1_ext_params_block_header *>(header_.data()); + header->flags &= ~(RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE | + RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE); + header->flags |= enabled ? RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE + : RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE; +} + +RkISP1Params::RkISP1Params(uint32_t format, Span<uint8_t> data) + : format_(format), data_(data), used_(0) +{ + if (format_ == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) { + struct rkisp1_ext_params_cfg *cfg = + reinterpret_cast<struct rkisp1_ext_params_cfg *>(data.data()); + + cfg->version = RKISP1_EXT_PARAM_BUFFER_V1; + cfg->data_size = 0; + + used_ += offsetof(struct rkisp1_ext_params_cfg, data); + } else { + memset(data.data(), 0, data.size()); + used_ = sizeof(struct rkisp1_params_cfg); + } +} + +void RkISP1Params::setBlockEnabled(BlockType type, bool enabled) +{ + const BlockTypeInfo &info = kBlockTypeInfo.at(type); + + struct rkisp1_params_cfg *cfg = + reinterpret_cast<struct rkisp1_params_cfg *>(data_.data()); + if (enabled) + cfg->module_ens |= info.enableBit; + else + cfg->module_ens &= ~info.enableBit; +} + +Span<uint8_t> RkISP1Params::block(BlockType type) +{ + auto infoIt = kBlockTypeInfo.find(type); + if (infoIt == kBlockTypeInfo.end()) { + LOG(RkISP1Params, Error) + << "Invalid parameters block type " + << utils::to_underlying(type); + return {}; + } + + const BlockTypeInfo &info = infoIt->second; + + /* + * For the legacy format, return a block referencing the fixed location + * of the data. + */ + if (format_ == V4L2_META_FMT_RK_ISP1_PARAMS) { + /* + * Blocks available only in extended parameters have an offset + * of 0. Return nullptr in that case. + */ + if (info.offset == 0) { + LOG(RkISP1Params, Error) + << "Block type " << utils::to_underlying(type) + << " unavailable in fixed parameters format"; + return {}; + } + + struct rkisp1_params_cfg *cfg = + reinterpret_cast<struct rkisp1_params_cfg *>(data_.data()); + + cfg->module_cfg_update |= info.enableBit; + cfg->module_en_update |= info.enableBit; + + return data_.subspan(info.offset, info.size); + } + + /* + * For the extensible format, allocate memory for the block, including + * the header. Look up the block in the cache first. If an algorithm + * requests the same block type twice, it should get the same block. + */ + auto cacheIt = blocks_.find(type); + if (cacheIt != blocks_.end()) + return cacheIt->second; + + /* Make sure we don't run out of space. */ + size_t size = sizeof(struct rkisp1_ext_params_block_header) + + ((info.size + 7) & ~7); + if (size > data_.size() - used_) { + LOG(RkISP1Params, Error) + << "Out of memory to allocate block type " + << utils::to_underlying(type); + return {}; + } + + /* Allocate a new block, clear its memory, and initialize its header. */ + Span<uint8_t> block = data_.subspan(used_, size); + used_ += size; + + struct rkisp1_ext_params_cfg *cfg = + reinterpret_cast<struct rkisp1_ext_params_cfg *>(data_.data()); + cfg->data_size += size; + + memset(block.data(), 0, block.size()); + + struct rkisp1_ext_params_block_header *header = + reinterpret_cast<struct rkisp1_ext_params_block_header *>(block.data()); + header->type = info.type; + header->size = block.size(); + + /* Update the cache. */ + blocks_[type] = block; + + return block; +} + +} /* namespace ipa::rkisp1 */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/params.h b/src/ipa/rkisp1/params.h new file mode 100644 index 00000000..40450e34 --- /dev/null +++ b/src/ipa/rkisp1/params.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * + * RkISP1 ISP Parameters + */ + +#pragma once + +#include <map> +#include <stdint.h> + +#include <linux/rkisp1-config.h> + +#include <libcamera/base/class.h> +#include <libcamera/base/span.h> + +namespace libcamera { + +namespace ipa::rkisp1 { + +enum class BlockType { + Bls, + Dpcc, + Sdg, + AwbGain, + Flt, + Bdm, + Ctk, + Goc, + Dpf, + DpfStrength, + Cproc, + Ie, + Lsc, + Awb, + Hst, + Aec, + Afc, + CompandBls, + CompandExpand, + CompandCompress, +}; + +namespace details { + +template<BlockType B> +struct block_type { +}; + +#define RKISP1_DEFINE_BLOCK_TYPE(blockType, blockStruct) \ +template<> \ +struct block_type<BlockType::blockType> { \ + using type = struct rkisp1_cif_isp_##blockStruct##_config; \ +}; + +RKISP1_DEFINE_BLOCK_TYPE(Bls, bls) +RKISP1_DEFINE_BLOCK_TYPE(Dpcc, dpcc) +RKISP1_DEFINE_BLOCK_TYPE(Sdg, sdg) +RKISP1_DEFINE_BLOCK_TYPE(AwbGain, awb_gain) +RKISP1_DEFINE_BLOCK_TYPE(Flt, flt) +RKISP1_DEFINE_BLOCK_TYPE(Bdm, bdm) +RKISP1_DEFINE_BLOCK_TYPE(Ctk, ctk) +RKISP1_DEFINE_BLOCK_TYPE(Goc, goc) +RKISP1_DEFINE_BLOCK_TYPE(Dpf, dpf) +RKISP1_DEFINE_BLOCK_TYPE(DpfStrength, dpf_strength) +RKISP1_DEFINE_BLOCK_TYPE(Cproc, cproc) +RKISP1_DEFINE_BLOCK_TYPE(Ie, ie) +RKISP1_DEFINE_BLOCK_TYPE(Lsc, lsc) +RKISP1_DEFINE_BLOCK_TYPE(Awb, awb_meas) +RKISP1_DEFINE_BLOCK_TYPE(Hst, hst) +RKISP1_DEFINE_BLOCK_TYPE(Aec, aec) +RKISP1_DEFINE_BLOCK_TYPE(Afc, afc) +RKISP1_DEFINE_BLOCK_TYPE(CompandBls, compand_bls) +RKISP1_DEFINE_BLOCK_TYPE(CompandExpand, compand_curve) +RKISP1_DEFINE_BLOCK_TYPE(CompandCompress, compand_curve) + +} /* namespace details */ + +class RkISP1Params; + +class RkISP1ParamsBlockBase +{ +public: + RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type, + const Span<uint8_t> &data); + + Span<uint8_t> data() const { return data_; } + + void setEnabled(bool enabled); + +private: + LIBCAMERA_DISABLE_COPY(RkISP1ParamsBlockBase) + + RkISP1Params *params_; + BlockType type_; + Span<uint8_t> header_; + Span<uint8_t> data_; +}; + +template<BlockType B> +class RkISP1ParamsBlock : public RkISP1ParamsBlockBase +{ +public: + using Type = typename details::block_type<B>::type; + + RkISP1ParamsBlock(RkISP1Params *params, const Span<uint8_t> &data) + : RkISP1ParamsBlockBase(params, B, data) + { + } + + const Type *operator->() const + { + return reinterpret_cast<const Type *>(data().data()); + } + + Type *operator->() + { + return reinterpret_cast<Type *>(data().data()); + } + + const Type &operator*() const & + { + return *reinterpret_cast<const Type *>(data().data()); + } + + Type &operator*() & + { + return *reinterpret_cast<Type *>(data().data()); + } +}; + +class RkISP1Params +{ +public: + RkISP1Params(uint32_t format, Span<uint8_t> data); + + template<BlockType B> + RkISP1ParamsBlock<B> block() + { + return RkISP1ParamsBlock<B>(this, block(B)); + } + + uint32_t format() const { return format_; } + size_t size() const { return used_; } + +private: + friend class RkISP1ParamsBlockBase; + + Span<uint8_t> block(BlockType type); + void setBlockEnabled(BlockType type, bool enabled); + + uint32_t format_; + + Span<uint8_t> data_; + size_t used_; + + std::map<BlockType, Span<uint8_t>> blocks_; +}; + +} /* namespace ipa::rkisp1 */ + +} /* namespace libcamera*/ diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 6544c925..1ed7d7d9 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -2,12 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * rkisp1.cpp - RkISP1 Image Processing Algorithms + * RkISP1 Image Processing Algorithms */ #include <algorithm> -#include <math.h> -#include <queue> +#include <array> +#include <chrono> #include <stdint.h> #include <string.h> @@ -18,20 +18,22 @@ #include <libcamera/base/log.h> #include <libcamera/control_ids.h> +#include <libcamera/controls.h> #include <libcamera/framebuffer.h> +#include <libcamera/request.h> + #include <libcamera/ipa/ipa_interface.h> #include <libcamera/ipa/ipa_module_info.h> #include <libcamera/ipa/rkisp1_ipa_interface.h> -#include <libcamera/request.h> #include "libcamera/internal/formats.h" #include "libcamera/internal/mapped_framebuffer.h" #include "libcamera/internal/yaml_parser.h" #include "algorithms/algorithm.h" -#include "libipa/camera_sensor_helper.h" #include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -63,9 +65,9 @@ public: void unmapBuffers(const std::vector<unsigned int> &ids) override; void queueRequest(const uint32_t frame, const ControlList &controls) override; - void fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) override; - void processStatsBuffer(const uint32_t frame, const uint32_t bufferId, - const ControlList &sensorControls) override; + void computeParams(const uint32_t frame, const uint32_t bufferId) override; + void processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls) override; protected: std::string logPrefix() const override; @@ -81,29 +83,39 @@ private: ControlInfoMap sensorControls_; - /* revision-specific data */ - rkisp1_cif_isp_version hwRevision_; - unsigned int hwHistBinNMax_; - unsigned int hwGammaOutMaxSamples_; - unsigned int hwHistogramWeightGridsSize_; - - /* Interface to the Camera Helper */ - std::unique_ptr<CameraSensorHelper> camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; namespace { +const IPAHwSettings ipaHwSettingsV10{ + RKISP1_CIF_ISP_AE_MEAN_MAX_V10, + RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10, + RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10, + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10, + false, +}; + +const IPAHwSettings ipaHwSettingsIMX8MP{ + RKISP1_CIF_ISP_AE_MEAN_MAX_V10, + RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10, + RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10, + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10, + true, +}; + +const IPAHwSettings ipaHwSettingsV12{ + RKISP1_CIF_ISP_AE_MEAN_MAX_V12, + RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12, + RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12, + RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12, + false, +}; + /* List of controls handled by the RkISP1 IPA */ const ControlInfoMap::Map rkisp1Controls{ - { &controls::AeEnable, ControlInfo(false, true) }, - { &controls::AwbEnable, ControlInfo(false, true) }, - { &controls::ColourGains, ControlInfo(0.0f, 3.996f, 1.0f) }, - { &controls::Brightness, ControlInfo(-1.0f, 0.993f, 0.0f) }, - { &controls::Contrast, ControlInfo(0.0f, 1.993f, 1.0f) }, - { &controls::Saturation, ControlInfo(0.0f, 1.993f, 1.0f) }, + { &controls::DebugMetadataEnable, ControlInfo(false, true, false) }, { &controls::Sharpness, ControlInfo(0.0f, 10.0f, 1.0f) }, { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }, }; @@ -111,7 +123,7 @@ const ControlInfoMap::Map rkisp1Controls{ } /* namespace */ IPARkISP1::IPARkISP1() - : context_({ {}, {}, { kMaxFrameContexts } }) + : context_(kMaxFrameContexts) { } @@ -128,14 +140,13 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, /* \todo Add support for other revisions */ switch (hwRevision) { case RKISP1_V10: - hwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10; - hwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10; - hwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10; + context_.hw = &ipaHwSettingsV10; + break; + case RKISP1_V_IMX8MP: + context_.hw = &ipaHwSettingsIMX8MP; break; case RKISP1_V12: - hwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12; - hwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12; - hwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12; + context_.hw = &ipaHwSettingsV12; break; default: LOG(IPARkISP1, Error) @@ -146,19 +157,18 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; - /* Cache the value to set it in configure. */ - hwRevision_ = static_cast<rkisp1_cif_isp_version>(hwRevision); + context_.sensorInfo = sensorInfo; - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPARkISP1, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; return -ENODEV; } - context_.configuration.sensor.lineDuration = sensorInfo.minLineLength - * 1.0s / sensorInfo.pixelRate; + context_.configuration.sensor.lineDuration = + sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; /* Load the tuning data file. */ File file(settings.configurationFile); @@ -199,8 +209,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, int IPARkISP1::start() { - setControls(0); - + /* \todo Properly handle startup controls. */ return 0; } @@ -232,8 +241,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.activeState = {}; context_.frameContexts.clear(); - /* Set the hardware revision for the algorithms. */ - context_.configuration.hw.revision = hwRevision_; + context_.configuration.paramFormat = ipaConfig.paramFormat; const IPACameraSensorInfo &info = ipaConfig.sensorInfo; const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; @@ -246,17 +254,19 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, /* * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for shutter speed and analogue gain. - * As it depends on the sensor, update it with the controls. + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. * - * \todo take VBLANK into account for maximum shutter speed + * \todo take VBLANK into account for maximum exposure time */ - context_.configuration.sensor.minShutterSpeed = + context_.configuration.sensor.minExposureTime = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.maxShutterSpeed = + context_.configuration.sensor.maxExposureTime = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.sensor.maxAnalogueGain = camHelper_->gain(maxGain); + context_.configuration.sensor.minAnalogueGain = + context_.camHelper->gain(minGain); + context_.configuration.sensor.maxAnalogueGain = + context_.camHelper->gain(maxGain); context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(), [](auto &cfg) -> bool { @@ -314,6 +324,7 @@ void IPARkISP1::unmapBuffers(const std::vector<unsigned int> &ids) void IPARkISP1::queueRequest(const uint32_t frame, const ControlList &controls) { IPAFrameContext &frameContext = context_.frameContexts.alloc(frame); + context_.debugMetadata.enableByControl(controls); for (auto const &a : algorithms()) { Algorithm *algo = static_cast<Algorithm *>(a.get()); @@ -323,25 +334,21 @@ void IPARkISP1::queueRequest(const uint32_t frame, const ControlList &controls) } } -void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId) +void IPARkISP1::computeParams(const uint32_t frame, const uint32_t bufferId) { IPAFrameContext &frameContext = context_.frameContexts.get(frame); - rkisp1_params_cfg *params = - reinterpret_cast<rkisp1_params_cfg *>( - mappedBuffers_.at(bufferId).planes()[0].data()); - - /* Prepare parameters buffer. */ - memset(params, 0, sizeof(*params)); + RkISP1Params params(context_.configuration.paramFormat, + mappedBuffers_.at(bufferId).planes()[0]); for (auto const &algo : algorithms()) - algo->prepare(context_, frame, frameContext, params); + algo->prepare(context_, frame, frameContext, ¶ms); - paramsBufferReady.emit(frame); + paramsComputed.emit(frame, params.size()); } -void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId, - const ControlList &sensorControls) +void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls) { IPAFrameContext &frameContext = context_.frameContexts.get(frame); @@ -357,7 +364,7 @@ void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); frameContext.sensor.gain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()); ControlList metadata(controls::controls); @@ -370,6 +377,7 @@ void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId setControls(frame); + context_.debugMetadata.moveEntries(metadata); metadataReady.emit(frame, metadata); } @@ -394,9 +402,9 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, /* Compute the analogue gain limits. */ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>()); - float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>()); - float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>()); + float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>()); + float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>()); + float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>()); ctrlMap.emplace(std::piecewise_construct, std::forward_as_tuple(&controls::AnalogueGain), std::forward_as_tuple(minGain, maxGain, defGain)); @@ -424,10 +432,11 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); } - ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], - frameDurations[1], - frameDurations[2]); + /* \todo Move this (and other agc-related controls) to agc */ + context_.ctrlMap[&controls::FrameDurationLimits] = + ControlInfo(frameDurations[0], frameDurations[1], frameDurations[2]); + ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } @@ -440,11 +449,17 @@ void IPARkISP1::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); uint32_t exposure = frameContext.agc.exposure; - uint32_t gain = camHelper_->gainCode(frameContext.agc.gain); + uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); + uint32_t vblank = frameContext.agc.vblank; + + LOG(IPARkISP1, Debug) + << "Set controls for frame " << frame << ": exposure " << exposure + << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(sensorControls_); ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain)); + ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank)); setSensorControls.emit(frame, ctrls); } @@ -459,7 +474,7 @@ extern "C" { const struct IPAModuleInfo ipaModuleInfo = { IPA_MODULE_API_VERSION, 1, - "PipelineHandlerRkISP1", + "rkisp1", "rkisp1", }; diff --git a/src/ipa/rpi/cam_helper/cam_helper.cpp b/src/ipa/rpi/cam_helper/cam_helper.cpp index ddd5e9a4..a78db9c1 100644 --- a/src/ipa/rpi/cam_helper/cam_helper.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * cam_helper.cpp - helper information for different sensors + * helper information for different sensors */ #include <linux/videodev2.h> @@ -156,17 +156,9 @@ void CamHelper::setCameraMode(const CameraMode &mode) } } -void CamHelper::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const +void CamHelper::setHwConfig(const Controller::HardwareConfig &hwConfig) { - /* - * These values are correct for many sensors. Other sensors will - * need to over-ride this function. - */ - exposureDelay = 2; - gainDelay = 1; - vblankDelay = 2; - hblankDelay = 2; + hwConfig_ = hwConfig; } bool CamHelper::sensorEmbeddedDataPresent() const @@ -241,7 +233,7 @@ void CamHelper::parseEmbeddedData(Span<const uint8_t> buffer, return; } - deviceStatus.shutterSpeed = parsedDeviceStatus.shutterSpeed; + deviceStatus.exposureTime = parsedDeviceStatus.exposureTime; deviceStatus.analogueGain = parsedDeviceStatus.analogueGain; deviceStatus.frameLength = parsedDeviceStatus.frameLength; deviceStatus.lineLength = parsedDeviceStatus.lineLength; diff --git a/src/ipa/rpi/cam_helper/cam_helper.h b/src/ipa/rpi/cam_helper/cam_helper.h index 58a4b202..4a826690 100644 --- a/src/ipa/rpi/cam_helper/cam_helper.h +++ b/src/ipa/rpi/cam_helper/cam_helper.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * cam_helper.h - helper class providing camera information + * helper class providing camera information */ #pragma once @@ -36,11 +36,6 @@ namespace RPiController { * exposure time, and to convert between the sensor's gain codes and actual * gains. * - * A function to return the number of frames of delay between updating exposure, - * analogue gain and vblanking, and for the changes to take effect. For many - * sensors these take the values 2, 1 and 2 respectively, but sensors that are - * different will need to over-ride the default function provided. - * * A function to query if the sensor outputs embedded data that can be parsed. * * A function to return the sensitivity of a given camera mode. @@ -76,6 +71,7 @@ public: CamHelper(std::unique_ptr<MdParser> parser, unsigned int frameIntegrationDiff); virtual ~CamHelper(); void setCameraMode(const CameraMode &mode); + void setHwConfig(const Controller::HardwareConfig &hwConfig); virtual void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata); virtual void process(StatisticsPtr &stats, Metadata &metadata); @@ -91,8 +87,6 @@ public: libcamera::utils::Duration lineLengthPckToDuration(uint32_t lineLengthPck) const; virtual uint32_t gainCode(double gain) const = 0; virtual double gain(uint32_t gainCode) const = 0; - virtual void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const; virtual bool sensorEmbeddedDataPresent() const; virtual double getModeSensitivity(const CameraMode &mode) const; virtual unsigned int hideFramesStartup() const; @@ -108,6 +102,7 @@ protected: std::unique_ptr<MdParser> parser_; CameraMode mode_; + Controller::HardwareConfig hwConfig_; private: /* diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp index c3337ed0..ba01153e 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx219.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * cam_helper_imx219.cpp - camera helper for imx219 sensor + * camera helper for imx219 sensor */ #include <assert.h> @@ -99,7 +99,7 @@ void CamHelperImx219::populateMetadata(const MdParser::RegisterMap ®isters, deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 + registers.at(lineLengthLoReg)); - deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), + deviceStatus.exposureTime = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), deviceStatus.lineLength); deviceStatus.analogueGain = gain(registers.at(gainReg)); deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp new file mode 100644 index 00000000..efc03193 --- /dev/null +++ b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2024, Raspberry Pi Ltd + * + * cam_helper_Imx283.cpp - camera information for Imx283 sensor + */ + +#include <assert.h> + +#include "cam_helper.h" + +using namespace RPiController; + +class CamHelperImx283 : public CamHelper +{ +public: + CamHelperImx283(); + uint32_t gainCode(double gain) const override; + double gain(uint32_t gainCode) const override; + unsigned int hideFramesModeSwitch() const override; + +private: + /* + * Smallest difference between the frame length and integration time, + * in units of lines. + */ + static constexpr int frameIntegrationDiff = 4; +}; + +/* + * Imx283 doesn't output metadata, so we have to use the delayed controls which + * works by counting frames. + */ + +CamHelperImx283::CamHelperImx283() + : CamHelper({}, frameIntegrationDiff) +{ +} + +uint32_t CamHelperImx283::gainCode(double gain) const +{ + return static_cast<uint32_t>(2048.0 - 2048.0 / gain); +} + +double CamHelperImx283::gain(uint32_t gainCode) const +{ + return static_cast<double>(2048.0 / (2048 - gainCode)); +} + +unsigned int CamHelperImx283::hideFramesModeSwitch() const +{ + /* After a mode switch, we seem to get 1 bad frame. */ + return 1; +} + +static CamHelper *create() +{ + return new CamHelperImx283(); +} + +static RegisterCamHelper reg("imx283", &create); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp index d98b51cd..c1aa8528 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx290.cpp @@ -2,10 +2,10 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * cam_helper_imx290.cpp - camera helper for imx290 sensor + * camera helper for imx290 sensor */ -#include <math.h> +#include <cmath> #include "cam_helper.h" @@ -17,8 +17,6 @@ public: CamHelperImx290(); uint32_t gainCode(double gain) const override; double gain(uint32_t gainCode) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; unsigned int hideFramesStartup() const override; unsigned int hideFramesModeSwitch() const override; @@ -37,22 +35,13 @@ CamHelperImx290::CamHelperImx290() uint32_t CamHelperImx290::gainCode(double gain) const { - int code = 66.6667 * log10(gain); + int code = 66.6667 * std::log10(gain); return std::max(0, std::min(code, 0xf0)); } double CamHelperImx290::gain(uint32_t gainCode) const { - return pow(10, 0.015 * gainCode); -} - -void CamHelperImx290::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 2; - hblankDelay = 2; + return std::pow(10, 0.015 * gainCode); } unsigned int CamHelperImx290::hideFramesStartup() const @@ -73,3 +62,5 @@ static CamHelper *create() } static RegisterCamHelper reg("imx290", &create); +static RegisterCamHelper reg327("imx327", &create); +static RegisterCamHelper reg462("imx462", &create); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp index ecb845e7..ac7ee2ea 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx296.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * cam_helper_imx296.cpp - Camera helper for IMX296 sensor + * Camera helper for IMX296 sensor */ #include <algorithm> @@ -23,8 +23,6 @@ public: double gain(uint32_t gainCode) const override; uint32_t exposureLines(const Duration exposure, const Duration lineLength) const override; Duration exposure(uint32_t exposureLines, const Duration lineLength) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; private: static constexpr uint32_t minExposureLines = 1; @@ -66,15 +64,6 @@ Duration CamHelperImx296::exposure(uint32_t exposureLines, return std::max<uint32_t>(minExposureLines, exposureLines) * timePerLine + 14.26us; } -void CamHelperImx296::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 2; - hblankDelay = 2; -} - static CamHelper *create() { return new CamHelperImx296(); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx415.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx415.cpp new file mode 100644 index 00000000..c0a09eee --- /dev/null +++ b/src/ipa/rpi/cam_helper/cam_helper_imx415.cpp @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2025, Raspberry Pi Ltd + * + * camera helper for imx415 sensor + */ + +#include <cmath> + +#include "cam_helper.h" + +using namespace RPiController; + +class CamHelperImx415 : public CamHelper +{ +public: + CamHelperImx415(); + uint32_t gainCode(double gain) const override; + double gain(uint32_t gainCode) const override; + unsigned int hideFramesStartup() const override; + unsigned int hideFramesModeSwitch() const override; + +private: + /* + * Smallest difference between the frame length and integration time, + * in units of lines. + */ + static constexpr int frameIntegrationDiff = 8; +}; + +CamHelperImx415::CamHelperImx415() + : CamHelper({}, frameIntegrationDiff) +{ +} + +uint32_t CamHelperImx415::gainCode(double gain) const +{ + int code = 66.6667 * std::log10(gain); + return std::max(0, std::min(code, 0xf0)); +} + +double CamHelperImx415::gain(uint32_t gainCode) const +{ + return std::pow(10, 0.015 * gainCode); +} + +unsigned int CamHelperImx415::hideFramesStartup() const +{ + /* On startup, we seem to get 1 bad frame. */ + return 1; +} + +unsigned int CamHelperImx415::hideFramesModeSwitch() const +{ + /* After a mode switch, we seem to get 1 bad frame. */ + return 1; +} + +static CamHelper *create() +{ + return new CamHelperImx415(); +} + +static RegisterCamHelper reg("imx415", &create); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp index bc769ca7..a72ac67d 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx477.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * cam_helper_imx477.cpp - camera helper for imx477 sensor + * camera helper for imx477 sensor */ #include <algorithm> @@ -51,8 +51,6 @@ public: void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override; std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration, Duration maxFrameDuration) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; bool sensorEmbeddedDataPresent() const override; private: @@ -112,7 +110,7 @@ void CamHelperImx477::prepare(libcamera::Span<const uint8_t> buffer, Metadata &m DeviceStatus parsedDeviceStatus; metadata.get("device.status", parsedDeviceStatus); - parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed; + parsedDeviceStatus.exposureTime = deviceStatus.exposureTime; parsedDeviceStatus.frameLength = deviceStatus.frameLength; metadata.set("device.status", parsedDeviceStatus); @@ -159,15 +157,6 @@ std::pair<uint32_t, uint32_t> CamHelperImx477::getBlanking(Duration &exposure, return { frameLength - mode_.height, hblank }; } -void CamHelperImx477::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 3; - hblankDelay = 3; -} - bool CamHelperImx477::sensorEmbeddedDataPresent() const { return true; @@ -180,7 +169,7 @@ void CamHelperImx477::populateMetadata(const MdParser::RegisterMap ®isters, deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 + registers.at(lineLengthLoReg)); - deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), + deviceStatus.exposureTime = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), deviceStatus.lineLength); deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg)); deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp index c7262aa0..10cbea48 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx519.cpp @@ -3,7 +3,7 @@ * Based on cam_helper_imx477.cpp * Copyright (C) 2020, Raspberry Pi Ltd * - * cam_helper_imx519.cpp - camera helper for imx519 sensor + * camera helper for imx519 sensor * Copyright (C) 2021, Arducam Technology co., Ltd. */ @@ -51,8 +51,6 @@ public: void prepare(libcamera::Span<const uint8_t> buffer, Metadata &metadata) override; std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration, Duration maxFrameDuration) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; bool sensorEmbeddedDataPresent() const override; private: @@ -112,7 +110,7 @@ void CamHelperImx519::prepare(libcamera::Span<const uint8_t> buffer, Metadata &m DeviceStatus parsedDeviceStatus; metadata.get("device.status", parsedDeviceStatus); - parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed; + parsedDeviceStatus.exposureTime = deviceStatus.exposureTime; parsedDeviceStatus.frameLength = deviceStatus.frameLength; metadata.set("device.status", parsedDeviceStatus); @@ -159,15 +157,6 @@ std::pair<uint32_t, uint32_t> CamHelperImx519::getBlanking(Duration &exposure, return { frameLength - mode_.height, hblank }; } -void CamHelperImx519::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 3; - hblankDelay = 3; -} - bool CamHelperImx519::sensorEmbeddedDataPresent() const { return true; @@ -180,7 +169,7 @@ void CamHelperImx519::populateMetadata(const MdParser::RegisterMap ®isters, deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 + registers.at(lineLengthLoReg)); - deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), + deviceStatus.exposureTime = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), deviceStatus.lineLength); deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg)); deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg); diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp index dce39cd5..6150909c 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Raspberry Pi Ltd * - * cam_helper_imx708.cpp - camera helper for imx708 sensor + * camera helper for imx708 sensor */ #include <cmath> @@ -54,8 +54,6 @@ public: void process(StatisticsPtr &stats, Metadata &metadata) override; std::pair<uint32_t, uint32_t> getBlanking(Duration &exposure, Duration minFrameDuration, Duration maxFrameDuration) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; bool sensorEmbeddedDataPresent() const override; double getModeSensitivity(const CameraMode &mode) const override; unsigned int hideFramesModeSwitch() const override; @@ -66,7 +64,7 @@ private: * Smallest difference between the frame length and integration time, * in units of lines. */ - static constexpr int frameIntegrationDiff = 22; + static constexpr int frameIntegrationDiff = 48; /* Maximum frame length allowable for long exposure calculations. */ static constexpr int frameLengthMax = 0xffdc; /* Largest long exposure scale factor given as a left shift on the frame length. */ @@ -155,7 +153,7 @@ void CamHelperImx708::prepare(libcamera::Span<const uint8_t> buffer, Metadata &m DeviceStatus parsedDeviceStatus; metadata.get("device.status", parsedDeviceStatus); - parsedDeviceStatus.shutterSpeed = deviceStatus.shutterSpeed; + parsedDeviceStatus.exposureTime = deviceStatus.exposureTime; parsedDeviceStatus.frameLength = deviceStatus.frameLength; metadata.set("device.status", parsedDeviceStatus); @@ -208,15 +206,6 @@ std::pair<uint32_t, uint32_t> CamHelperImx708::getBlanking(Duration &exposure, return { frameLength - mode_.height, hblank }; } -void CamHelperImx708::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 3; - hblankDelay = 3; -} - bool CamHelperImx708::sensorEmbeddedDataPresent() const { return true; @@ -255,7 +244,7 @@ void CamHelperImx708::populateMetadata(const MdParser::RegisterMap ®isters, deviceStatus.lineLength = lineLengthPckToDuration(registers.at(lineLengthHiReg) * 256 + registers.at(lineLengthLoReg)); - deviceStatus.shutterSpeed = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), + deviceStatus.exposureTime = exposure(registers.at(expHiReg) * 256 + registers.at(expLoReg), deviceStatus.lineLength); deviceStatus.analogueGain = gain(registers.at(gainHiReg) * 256 + registers.at(gainLoReg)); deviceStatus.frameLength = registers.at(frameLengthHiReg) * 256 + registers.at(frameLengthLoReg); @@ -269,7 +258,7 @@ bool CamHelperImx708::parsePdafData(const uint8_t *ptr, size_t len, { size_t step = bpp >> 1; /* bytes per PDAF grid entry */ - if (bpp < 10 || bpp > 12 || len < 194 * step || ptr[0] != 0 || ptr[1] >= 0x40) { + if (bpp < 10 || bpp > 14 || len < 194 * step || ptr[0] != 0 || ptr[1] >= 0x40) { LOG(IPARPI, Error) << "PDAF data in unsupported format"; return false; } diff --git a/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp index 5a99083d..40d6b6d7 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_ov5647.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * cam_helper_ov5647.cpp - camera information for ov5647 sensor + * camera information for ov5647 sensor */ #include <assert.h> @@ -17,8 +17,6 @@ public: CamHelperOv5647(); uint32_t gainCode(double gain) const override; double gain(uint32_t gainCode) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; unsigned int hideFramesStartup() const override; unsigned int hideFramesModeSwitch() const override; unsigned int mistrustFramesStartup() const override; @@ -52,19 +50,6 @@ double CamHelperOv5647::gain(uint32_t gainCode) const return static_cast<double>(gainCode) / 16.0; } -void CamHelperOv5647::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - /* - * We run this sensor in a mode where the gain delay is bumped up to - * 2. It seems to be the only way to make the delays "predictable". - */ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 2; - hblankDelay = 2; -} - unsigned int CamHelperOv5647::hideFramesStartup() const { /* diff --git a/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp new file mode 100644 index 00000000..980495a8 --- /dev/null +++ b/src/ipa/rpi/cam_helper/cam_helper_ov64a40.cpp @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2021, Raspberry Pi Ltd + * Copyright (C) 2023, Ideas on Board Oy. + * + * camera information for ov64a40 sensor + */ + +#include <assert.h> + +#include "cam_helper.h" + +using namespace RPiController; + +class CamHelperOv64a40 : public CamHelper +{ +public: + CamHelperOv64a40(); + uint32_t gainCode(double gain) const override; + double gain(uint32_t gainCode) const override; + double getModeSensitivity(const CameraMode &mode) const override; + +private: + /* + * Smallest difference between the frame length and integration time, + * in units of lines. + */ + static constexpr int frameIntegrationDiff = 32; +}; + +CamHelperOv64a40::CamHelperOv64a40() + : CamHelper({}, frameIntegrationDiff) +{ +} + +uint32_t CamHelperOv64a40::gainCode(double gain) const +{ + return static_cast<uint32_t>(gain * 128.0); +} + +double CamHelperOv64a40::gain(uint32_t gainCode) const +{ + return static_cast<double>(gainCode) / 128.0; +} + +double CamHelperOv64a40::getModeSensitivity(const CameraMode &mode) const +{ + if (mode.binX >= 2 && mode.scaleX >= 4) { + return 4.0; + } else if (mode.binX >= 2 && mode.scaleX >= 2) { + return 2.0; + } else { + return 1.0; + } +} + +static CamHelper *create() +{ + return new CamHelperOv64a40(); +} + +static RegisterCamHelper reg("ov64a40", &create); diff --git a/src/ipa/rpi/cam_helper/cam_helper_ov7251.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov7251.cpp new file mode 100644 index 00000000..fc7b999f --- /dev/null +++ b/src/ipa/rpi/cam_helper/cam_helper_ov7251.cpp @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2021, Raspberry Pi Ltd + * + * camera information for ov7251 sensor + */ + +#include <assert.h> + +#include "cam_helper.h" + +using namespace RPiController; + +class CamHelperOv7251 : public CamHelper +{ +public: + CamHelperOv7251(); + uint32_t gainCode(double gain) const override; + double gain(uint32_t gainCode) const override; + +private: + /* + * Smallest difference between the frame length and integration time, + * in units of lines. + */ + static constexpr int frameIntegrationDiff = 4; +}; + +/* + * OV7251 doesn't output metadata, so we have to use the "unicam parser" which + * works by counting frames. + */ + +CamHelperOv7251::CamHelperOv7251() + : CamHelper({}, frameIntegrationDiff) +{ +} + +uint32_t CamHelperOv7251::gainCode(double gain) const +{ + return static_cast<uint32_t>(gain * 16.0); +} + +double CamHelperOv7251::gain(uint32_t gainCode) const +{ + return static_cast<double>(gainCode) / 16.0; +} + +static CamHelper *create() +{ + return new CamHelperOv7251(); +} + +static RegisterCamHelper reg("ov7251", &create); diff --git a/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp b/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp index 86c5bc4c..e93a4691 100644 --- a/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp +++ b/src/ipa/rpi/cam_helper/cam_helper_ov9281.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * cam_helper_ov9281.cpp - camera information for ov9281 sensor + * camera information for ov9281 sensor */ #include <assert.h> @@ -17,15 +17,13 @@ public: CamHelperOv9281(); uint32_t gainCode(double gain) const override; double gain(uint32_t gainCode) const override; - void getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const override; private: /* * Smallest difference between the frame length and integration time, * in units of lines. */ - static constexpr int frameIntegrationDiff = 4; + static constexpr int frameIntegrationDiff = 25; }; /* @@ -48,16 +46,6 @@ double CamHelperOv9281::gain(uint32_t gainCode) const return static_cast<double>(gainCode) / 16.0; } -void CamHelperOv9281::getDelays(int &exposureDelay, int &gainDelay, - int &vblankDelay, int &hblankDelay) const -{ - /* The driver appears to behave as follows: */ - exposureDelay = 2; - gainDelay = 2; - vblankDelay = 2; - hblankDelay = 2; -} - static CamHelper *create() { return new CamHelperOv9281(); diff --git a/src/ipa/rpi/cam_helper/md_parser.h b/src/ipa/rpi/cam_helper/md_parser.h index 77d557aa..227c376c 100644 --- a/src/ipa/rpi/cam_helper/md_parser.h +++ b/src/ipa/rpi/cam_helper/md_parser.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * md_parser.h - image sensor metadata parser interface + * image sensor metadata parser interface */ #pragma once diff --git a/src/ipa/rpi/cam_helper/md_parser_smia.cpp b/src/ipa/rpi/cam_helper/md_parser_smia.cpp index 210787ed..c7bdcf94 100644 --- a/src/ipa/rpi/cam_helper/md_parser_smia.cpp +++ b/src/ipa/rpi/cam_helper/md_parser_smia.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * md_parser_smia.cpp - SMIA specification based embedded data parser + * SMIA specification based embedded data parser */ #include <libcamera/base/log.h> @@ -86,10 +86,13 @@ MdParserSmia::ParseStatus MdParserSmia::findRegs(libcamera::Span<const uint8_t> while (1) { int tag = buffer[currentOffset++]; - if ((bitsPerPixel_ == 10 && - (currentOffset + 1 - currentLineStart) % 5 == 0) || - (bitsPerPixel_ == 12 && - (currentOffset + 1 - currentLineStart) % 3 == 0)) { + /* Non-dummy bytes come in even-sized blocks: skip can only ever follow tag */ + while ((bitsPerPixel_ == 10 && + (currentOffset + 1 - currentLineStart) % 5 == 0) || + (bitsPerPixel_ == 12 && + (currentOffset + 1 - currentLineStart) % 3 == 0) || + (bitsPerPixel_ == 14 && + (currentOffset - currentLineStart) % 7 >= 4)) { if (buffer[currentOffset++] != RegSkip) return BadDummy; } diff --git a/src/ipa/rpi/cam_helper/meson.build b/src/ipa/rpi/cam_helper/meson.build index bdf2db8e..abf02147 100644 --- a/src/ipa/rpi/cam_helper/meson.build +++ b/src/ipa/rpi/cam_helper/meson.build @@ -4,11 +4,15 @@ rpi_ipa_cam_helper_sources = files([ 'cam_helper.cpp', 'cam_helper_ov5647.cpp', 'cam_helper_imx219.cpp', + 'cam_helper_imx283.cpp', 'cam_helper_imx290.cpp', 'cam_helper_imx296.cpp', + 'cam_helper_imx415.cpp', 'cam_helper_imx477.cpp', 'cam_helper_imx519.cpp', 'cam_helper_imx708.cpp', + 'cam_helper_ov64a40.cpp', + 'cam_helper_ov7251.cpp', 'cam_helper_ov9281.cpp', 'md_parser_smia.cpp', ]) diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp index f28eb36b..e0a93daa 100644 --- a/src/ipa/rpi/common/ipa_base.cpp +++ b/src/ipa/rpi/common/ipa_base.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2023, Raspberry Pi Ltd * - * ipa_base.cpp - Raspberry Pi IPA base class + * Raspberry Pi IPA base class */ #include "ipa_base.h" @@ -24,6 +24,7 @@ #include "controller/ccm_status.h" #include "controller/contrast_algorithm.h" #include "controller/denoise_algorithm.h" +#include "controller/hdr_algorithm.h" #include "controller/lux_status.h" #include "controller/sharpen_algorithm.h" #include "controller/statistics.h" @@ -54,9 +55,19 @@ constexpr Duration controllerMinFrameDuration = 1.0s / 30.0; /* List of controls handled by the Raspberry Pi IPA */ const ControlInfoMap::Map ipaControls{ - { &controls::AeEnable, ControlInfo(false, true) }, - { &controls::ExposureTime, ControlInfo(0, 66666) }, - { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) }, + /* \todo Move this to the Camera class */ + { &controls::AeEnable, ControlInfo(false, true, true) }, + { &controls::ExposureTimeMode, + ControlInfo(static_cast<int32_t>(controls::ExposureTimeModeAuto), + static_cast<int32_t>(controls::ExposureTimeModeManual), + static_cast<int32_t>(controls::ExposureTimeModeAuto)) }, + { &controls::ExposureTime, + ControlInfo(1, 66666, static_cast<int32_t>(defaultExposureTime.get<std::micro>())) }, + { &controls::AnalogueGainMode, + ControlInfo(static_cast<int32_t>(controls::AnalogueGainModeAuto), + static_cast<int32_t>(controls::AnalogueGainModeManual), + static_cast<int32_t>(controls::AnalogueGainModeAuto)) }, + { &controls::AnalogueGain, ControlInfo(1.0f, 16.0f, 1.0f) }, { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) }, { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) }, { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) }, @@ -67,10 +78,14 @@ const ControlInfoMap::Map ipaControls{ { &controls::AeFlickerPeriod, ControlInfo(100, 1000000) }, { &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) }, { &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) }, + { &controls::HdrMode, ControlInfo(controls::HdrModeValues) }, { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) }, { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) }, - { &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) }, - { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) } + { &controls::FrameDurationLimits, + ControlInfo(INT64_C(33333), INT64_C(120000), + static_cast<int64_t>(defaultMinFrameDuration.get<std::micro>())) }, + { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }, + { &controls::rpi::StatsOutputEnable, ControlInfo(false, true, false) }, }; /* IPA controls handled conditionally, if the sensor is not mono */ @@ -78,6 +93,7 @@ const ControlInfoMap::Map ipaColourControls{ { &controls::AwbEnable, ControlInfo(false, true) }, { &controls::AwbMode, ControlInfo(controls::AwbModeValues) }, { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, + { &controls::ColourTemperature, ControlInfo(100, 100000) }, { &controls::Saturation, ControlInfo(0.0f, 32.0f, 1.0f) }, }; @@ -93,6 +109,13 @@ const ControlInfoMap::Map ipaAfControls{ { &controls::LensPosition, ControlInfo(0.0f, 32.0f, 1.0f) } }; +/* Platform specific controls */ +const std::map<const std::string, ControlInfoMap::Map> platformControls { + { "pisp", { + { &controls::rpi::ScalerCrops, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) } + } }, +}; + } /* namespace */ LOG_DEFINE_CATEGORY(IPARPI) @@ -100,8 +123,9 @@ LOG_DEFINE_CATEGORY(IPARPI) namespace ipa::RPi { IpaBase::IpaBase() - : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), frameCount_(0), - mistrustCount_(0), lastRunTimestamp_(0), firstStart_(true), flickerState_({ 0, 0s }) + : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), statsMetadataOutput_(false), + stitchSwapBuffers_(false), frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0), + firstStart_(true), flickerState_({ 0, 0s }) { } @@ -123,18 +147,8 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams ¶ms, Ini return -EINVAL; } - /* - * Pass out the sensor config to the pipeline handler in order - * to setup the staggered writer class. - */ - int gainDelay, exposureDelay, vblankDelay, hblankDelay, sensorMetadata; - helper_->getDelays(exposureDelay, gainDelay, vblankDelay, hblankDelay); - sensorMetadata = helper_->sensorEmbeddedDataPresent(); - - result->sensorConfig.gainDelay = gainDelay; - result->sensorConfig.exposureDelay = exposureDelay; - result->sensorConfig.vblankDelay = vblankDelay; - result->sensorConfig.hblankDelay = hblankDelay; + /* Pass out the sensor metadata to the pipeline handler */ + int sensorMetadata = helper_->sensorEmbeddedDataPresent(); result->sensorConfig.sensorMetadata = sensorMetadata; /* Load the tuning file for this sensor. */ @@ -149,12 +163,17 @@ int32_t IpaBase::init(const IPASettings &settings, const InitParams ¶ms, Ini lensPresent_ = params.lensPresent; controller_.initialise(); + helper_->setHwConfig(controller_.getHardwareConfig()); /* Return the controls handled by the IPA */ ControlInfoMap::Map ctrlMap = ipaControls; if (lensPresent_) ctrlMap.merge(ControlInfoMap::Map(ipaAfControls)); + auto platformCtrlsIt = platformControls.find(controller_.getTarget()); + if (platformCtrlsIt != platformControls.end()) + ctrlMap.merge(ControlInfoMap::Map(platformCtrlsIt->second)); + monoSensor_ = params.sensorInfo.cfaPattern == properties::draft::ColorFilterArrangementEnum::MONO; if (!monoSensor_) ctrlMap.merge(ControlInfoMap::Map(ipaColourControls)); @@ -209,7 +228,7 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa /* Supply initial values for gain and exposure. */ AgcStatus agcStatus; - agcStatus.shutterTime = defaultExposureTime; + agcStatus.exposureTime = defaultExposureTime; agcStatus.analogueGain = defaultAnalogueGain; applyAGC(&agcStatus, ctrls); @@ -243,15 +262,18 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa ControlInfoMap::Map ctrlMap = ipaControls; ctrlMap[&controls::FrameDurationLimits] = ControlInfo(static_cast<int64_t>(mode_.minFrameDuration.get<std::micro>()), - static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>())); + static_cast<int64_t>(mode_.maxFrameDuration.get<std::micro>()), + static_cast<int64_t>(defaultMinFrameDuration.get<std::micro>())); ctrlMap[&controls::AnalogueGain] = ControlInfo(static_cast<float>(mode_.minAnalogueGain), - static_cast<float>(mode_.maxAnalogueGain)); + static_cast<float>(mode_.maxAnalogueGain), + static_cast<float>(defaultAnalogueGain)); ctrlMap[&controls::ExposureTime] = - ControlInfo(static_cast<int32_t>(mode_.minShutter.get<std::micro>()), - static_cast<int32_t>(mode_.maxShutter.get<std::micro>())); + ControlInfo(static_cast<int32_t>(mode_.minExposureTime.get<std::micro>()), + static_cast<int32_t>(mode_.maxExposureTime.get<std::micro>()), + static_cast<int32_t>(defaultExposureTime.get<std::micro>())); /* Declare colour processing related controls for non-mono sensors. */ if (!monoSensor_) @@ -284,16 +306,18 @@ void IpaBase::start(const ControlList &controls, StartResult *result) /* SwitchMode may supply updated exposure/gain values to use. */ AgcStatus agcStatus; - agcStatus.shutterTime = 0.0s; + agcStatus.exposureTime = 0.0s; agcStatus.analogueGain = 0.0; metadata.get("agc.status", agcStatus); - if (agcStatus.shutterTime && agcStatus.analogueGain) { + if (agcStatus.exposureTime && agcStatus.analogueGain) { ControlList ctrls(sensorCtrls_); applyAGC(&agcStatus, ctrls); result->controls = std::move(ctrls); setCameraTimeoutValue(); } + /* Make a note of this as it tells us the HDR status of the first few frames. */ + hdrStatus_ = agcStatus.hdr; /* * Initialise frame counts, and decide how many frames must be hidden or @@ -397,11 +421,17 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) * sensor exposure/gain changes. So fetch it from the metadata list * indexed by the IPA cookie returned, and put it in the current frame * metadata. + * + * Note if the HDR mode has changed, as things like tonemaps may need updating. */ AgcStatus agcStatus; + bool hdrChange = false; RPiController::Metadata &delayedMetadata = rpiMetadata_[params.delayContext]; - if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) + if (!delayedMetadata.get<AgcStatus>("agc.status", agcStatus)) { rpiMetadata.set("agc.delayed_status", agcStatus); + hdrChange = agcStatus.hdr.mode != hdrStatus_.mode; + hdrStatus_ = agcStatus.hdr; + } /* * This may overwrite the DeviceStatus using values from the sensor @@ -412,7 +442,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) /* Allow a 10% margin on the comparison below. */ Duration delta = (frameTimestamp - lastRunTimestamp_) * 1.0ns; if (lastRunTimestamp_ && frameCount_ > dropFrameCount_ && - delta < controllerMinFrameDuration * 0.9) { + delta < controllerMinFrameDuration * 0.9 && !hdrChange) { /* * Ensure we merge the previous frame's metadata with the current * frame. This will not overwrite exposure/gain values for the @@ -449,7 +479,7 @@ void IpaBase::prepareIsp(const PrepareParams ¶ms) reportMetadata(ipaContext); /* Ready to push the input buffer into the ISP. */ - prepareIspComplete.emit(params.buffers, false); + prepareIspComplete.emit(params.buffers, stitchSwapBuffers_); } void IpaBase::processStats(const ProcessParams ¶ms) @@ -532,6 +562,33 @@ void IpaBase::setMode(const IPACameraSensorInfo &sensorInfo) mode_.maxLineLength = sensorInfo.maxLineLength * (1.0s / sensorInfo.pixelRate); /* + * Ensure that the maximum pixel processing rate does not exceed the ISP + * hardware capabilities. If it does, try adjusting the minimum line + * length to compensate if possible. + */ + Duration minPixelTime = controller_.getHardwareConfig().minPixelProcessingTime; + Duration pixelTime = mode_.minLineLength / mode_.width; + if (minPixelTime && pixelTime < minPixelTime) { + Duration adjustedLineLength = minPixelTime * mode_.width; + if (adjustedLineLength <= mode_.maxLineLength) { + LOG(IPARPI, Info) + << "Adjusting mode minimum line length from " << mode_.minLineLength + << " to " << adjustedLineLength << " because of ISP constraints."; + mode_.minLineLength = adjustedLineLength; + } else { + LOG(IPARPI, Error) + << "Sensor minimum line length of " << pixelTime * mode_.width + << " (" << 1us / pixelTime << " MPix/s)" + << " is below the minimum allowable ISP limit of " + << adjustedLineLength + << " (" << 1us / minPixelTime << " MPix/s) "; + LOG(IPARPI, Error) + << "THIS WILL CAUSE IMAGE CORRUPTION!!! " + << "Please update the camera sensor driver to allow more horizontal blanking control."; + } + } + + /* * Set the frame length limits for the mode to ensure exposure and * framerate calculations are clipped appropriately. */ @@ -549,16 +606,26 @@ void IpaBase::setMode(const IPACameraSensorInfo &sensorInfo) mode_.sensitivity = helper_->getModeSensitivity(mode_); const ControlInfo &gainCtrl = sensorCtrls_.at(V4L2_CID_ANALOGUE_GAIN); - const ControlInfo &shutterCtrl = sensorCtrls_.at(V4L2_CID_EXPOSURE); + const ControlInfo &exposureTimeCtrl = sensorCtrls_.at(V4L2_CID_EXPOSURE); mode_.minAnalogueGain = helper_->gain(gainCtrl.min().get<int32_t>()); mode_.maxAnalogueGain = helper_->gain(gainCtrl.max().get<int32_t>()); - /* Shutter speed is calculated based on the limits of the frame durations. */ - mode_.minShutter = helper_->exposure(shutterCtrl.min().get<int32_t>(), mode_.minLineLength); - mode_.maxShutter = Duration::max(); - helper_->getBlanking(mode_.maxShutter, - mode_.minFrameDuration, mode_.maxFrameDuration); + /* + * We need to give the helper the min/max frame durations so it can calculate + * the correct exposure limits below. + */ + helper_->setCameraMode(mode_); + + /* + * Exposure time is calculated based on the limits of the frame + * durations. + */ + mode_.minExposureTime = helper_->exposure(exposureTimeCtrl.min().get<int32_t>(), + mode_.minLineLength); + mode_.maxExposureTime = Duration::max(); + helper_->getBlanking(mode_.maxExposureTime, mode_.minFrameDuration, + mode_.maxFrameDuration); } void IpaBase::setCameraTimeoutValue() @@ -643,14 +710,6 @@ static const std::map<int32_t, std::string> AwbModeTable = { { controls::AwbCustom, "custom" }, }; -static const std::map<int32_t, RPiController::DenoiseMode> DenoiseModeTable = { - { controls::draft::NoiseReductionModeOff, RPiController::DenoiseMode::Off }, - { controls::draft::NoiseReductionModeFast, RPiController::DenoiseMode::ColourFast }, - { controls::draft::NoiseReductionModeHighQuality, RPiController::DenoiseMode::ColourHighQuality }, - { controls::draft::NoiseReductionModeMinimal, RPiController::DenoiseMode::ColourOff }, - { controls::draft::NoiseReductionModeZSL, RPiController::DenoiseMode::ColourHighQuality }, -}; - static const std::map<int32_t, RPiController::AfAlgorithm::AfMode> AfModeTable = { { controls::AfModeManual, RPiController::AfAlgorithm::AfModeManual }, { controls::AfModeAuto, RPiController::AfAlgorithm::AfModeAuto }, @@ -669,9 +728,21 @@ static const std::map<int32_t, RPiController::AfAlgorithm::AfPause> AfPauseTable { controls::AfPauseResume, RPiController::AfAlgorithm::AfPauseResume }, }; +static const std::map<int32_t, std::string> HdrModeTable = { + { controls::HdrModeOff, "Off" }, + { controls::HdrModeMultiExposureUnmerged, "MultiExposureUnmerged" }, + { controls::HdrModeMultiExposure, "MultiExposure" }, + { controls::HdrModeSingleExposure, "SingleExposure" }, + { controls::HdrModeNight, "Night" }, +}; + void IpaBase::applyControls(const ControlList &controls) { + using RPiController::AgcAlgorithm; using RPiController::AfAlgorithm; + using RPiController::ContrastAlgorithm; + using RPiController::DenoiseAlgorithm; + using RPiController::HdrAlgorithm; /* Clear the return metadata buffer. */ libcameraMetadata_.clear(); @@ -693,6 +764,42 @@ void IpaBase::applyControls(const ControlList &controls) af->setMode(mode->second); } + /* + * Because some AE controls are mode-specific, handle the AE-related + * mode changes first. + */ + const auto analogueGainMode = controls.get(controls::AnalogueGainMode); + const auto exposureTimeMode = controls.get(controls::ExposureTimeMode); + + if (analogueGainMode || exposureTimeMode) { + RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( + controller_.getAlgorithm("agc")); + if (agc) { + if (analogueGainMode) { + if (*analogueGainMode == controls::AnalogueGainModeManual) + agc->disableAutoGain(); + else + agc->enableAutoGain(); + + libcameraMetadata_.set(controls::AnalogueGainMode, + *analogueGainMode); + } + + if (exposureTimeMode) { + if (*exposureTimeMode == controls::ExposureTimeModeManual) + agc->disableAutoExposure(); + else + agc->enableAutoExposure(); + + libcameraMetadata_.set(controls::ExposureTimeMode, + *exposureTimeMode); + } + } else { + LOG(IPARPI, Warning) + << "Could not set AnalogueGainMode or ExposureTimeMode - no AGC algorithm"; + } + } + /* Iterate over controls */ for (auto const &ctrl : controls) { LOG(IPARPI, Debug) << "Request ctrl: " @@ -700,23 +807,8 @@ void IpaBase::applyControls(const ControlList &controls) << " = " << ctrl.second.toString(); switch (ctrl.first) { - case controls::AE_ENABLE: { - RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( - controller_.getAlgorithm("agc")); - if (!agc) { - LOG(IPARPI, Warning) - << "Could not set AE_ENABLE - no AGC algorithm"; - break; - } - - if (ctrl.second.get<bool>() == false) - agc->disableAuto(0); - else - agc->enableAuto(0); - - libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>()); - break; - } + case controls::EXPOSURE_TIME_MODE: + break; /* We already handled this one above */ case controls::EXPOSURE_TIME: { RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( @@ -727,13 +819,23 @@ void IpaBase::applyControls(const ControlList &controls) break; } + /* + * Ignore manual exposure time when the auto exposure + * algorithm is running. + */ + if (agc->autoExposureEnabled()) + break; + /* The control provides units of microseconds. */ - agc->setFixedShutter(0, ctrl.second.get<int32_t>() * 1.0us); + agc->setFixedExposureTime(0, ctrl.second.get<int32_t>() * 1.0us); libcameraMetadata_.set(controls::ExposureTime, ctrl.second.get<int32_t>()); break; } + case controls::ANALOGUE_GAIN_MODE: + break; /* We already handled this one above */ + case controls::ANALOGUE_GAIN: { RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( controller_.getAlgorithm("agc")); @@ -743,6 +845,13 @@ void IpaBase::applyControls(const ControlList &controls) break; } + /* + * Ignore manual analogue gain value when the auto gain + * algorithm is running. + */ + if (agc->autoGainEnabled()) + break; + agc->setFixedAnalogueGain(0, ctrl.second.get<float>()); libcameraMetadata_.set(controls::AnalogueGain, @@ -781,7 +890,7 @@ void IpaBase::applyControls(const ControlList &controls) int32_t idx = ctrl.second.get<int32_t>(); if (ConstraintModeTable.count(idx)) { - agc->setConstraintMode(0, ConstraintModeTable.at(idx)); + agc->setConstraintMode(ConstraintModeTable.at(idx)); libcameraMetadata_.set(controls::AeConstraintMode, idx); } else { LOG(IPARPI, Error) << "Constraint mode " << idx @@ -799,9 +908,16 @@ void IpaBase::applyControls(const ControlList &controls) break; } + /* + * Ignore AE_EXPOSURE_MODE if the shutter or the gain + * are in auto mode. + */ + if (agc->autoExposureEnabled() || agc->autoGainEnabled()) + break; + int32_t idx = ctrl.second.get<int32_t>(); if (ExposureModeTable.count(idx)) { - agc->setExposureMode(0, ExposureModeTable.at(idx)); + agc->setExposureMode(ExposureModeTable.at(idx)); libcameraMetadata_.set(controls::AeExposureMode, idx); } else { LOG(IPARPI, Error) << "Exposure mode " << idx @@ -830,6 +946,17 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::AE_ENABLE: { + /* + * The AeEnable control is now just a wrapper that will already have been + * converted to ExposureTimeMode and AnalogueGainMode equivalents, so there + * would be nothing to do here. Nonetheless, "handle" the control so as to + * avoid warnings from the "default:" clause of the switch statement. + */ + + break; + } + case controls::AE_FLICKER_MODE: { RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( controller_.getAlgorithm("agc")); @@ -844,12 +971,12 @@ void IpaBase::applyControls(const ControlList &controls) switch (mode) { case controls::FlickerOff: - agc->setFlickerPeriod(0, 0us); + agc->setFlickerPeriod(0us); break; case controls::FlickerManual: - agc->setFlickerPeriod(0, flickerState_.manualPeriod); + agc->setFlickerPeriod(flickerState_.manualPeriod); break; @@ -883,7 +1010,7 @@ void IpaBase::applyControls(const ControlList &controls) * first, and the period updated after, or vice versa. */ if (flickerState_.mode == controls::FlickerManual) - agc->setFlickerPeriod(0, flickerState_.manualPeriod); + agc->setFlickerPeriod(flickerState_.manualPeriod); break; } @@ -957,6 +1084,25 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::COLOUR_TEMPERATURE: { + /* Silently ignore this control for a mono sensor. */ + if (monoSensor_) + break; + + auto temperatureK = ctrl.second.get<int32_t>(); + RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>( + controller_.getAlgorithm("awb")); + if (!awb) { + LOG(IPARPI, Warning) + << "Could not set COLOUR_TEMPERATURE - no AWB algorithm"; + break; + } + + awb->setColourTemperature(temperatureK); + /* This metadata will get reported back automatically. */ + break; + } + case controls::BRIGHTNESS: { RPiController::ContrastAlgorithm *contrast = dynamic_cast<RPiController::ContrastAlgorithm *>( controller_.getAlgorithm("contrast")); @@ -1021,6 +1167,7 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::rpi::SCALER_CROPS: case controls::SCALER_CROP: { /* We do nothing with this, but should avoid the warning below. */ break; @@ -1032,36 +1179,11 @@ void IpaBase::applyControls(const ControlList &controls) break; } - case controls::NOISE_REDUCTION_MODE: { - RPiController::DenoiseAlgorithm *sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>( - controller_.getAlgorithm("SDN")); - /* Some platforms may have a combined "denoise" algorithm instead. */ - if (!sdn) - sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>( - controller_.getAlgorithm("denoise")); - if (!sdn) { - LOG(IPARPI, Warning) - << "Could not set NOISE_REDUCTION_MODE - no SDN algorithm"; - break; - } - - int32_t idx = ctrl.second.get<int32_t>(); - auto mode = DenoiseModeTable.find(idx); - if (mode != DenoiseModeTable.end()) { - sdn->setMode(mode->second); - - /* - * \todo If the colour denoise is not going to run due to an - * analysis image resolution or format mismatch, we should - * report the status correctly in the metadata. - */ - libcameraMetadata_.set(controls::draft::NoiseReductionMode, idx); - } else { - LOG(IPARPI, Error) << "Noise reduction mode " << idx - << " not recognised"; - } + case controls::draft::NOISE_REDUCTION_MODE: + /* Handled below in handleControls() */ + libcameraMetadata_.set(controls::draft::NoiseReductionMode, + ctrl.second.get<int32_t>()); break; - } case controls::AF_MODE: break; /* We already handled this one above */ @@ -1168,6 +1290,61 @@ void IpaBase::applyControls(const ControlList &controls) break; } + case controls::HDR_MODE: { + HdrAlgorithm *hdr = dynamic_cast<HdrAlgorithm *>(controller_.getAlgorithm("hdr")); + if (!hdr) { + LOG(IPARPI, Warning) << "No HDR algorithm available"; + break; + } + + auto mode = HdrModeTable.find(ctrl.second.get<int32_t>()); + if (mode == HdrModeTable.end()) { + LOG(IPARPI, Warning) << "Unrecognised HDR mode"; + break; + } + + AgcAlgorithm *agc = dynamic_cast<AgcAlgorithm *>(controller_.getAlgorithm("agc")); + if (!agc) { + LOG(IPARPI, Warning) << "HDR requires an AGC algorithm"; + break; + } + + if (hdr->setMode(mode->second) == 0) { + agc->setActiveChannels(hdr->getChannels()); + + /* We also disable adpative contrast enhancement if HDR is running. */ + ContrastAlgorithm *contrast = + dynamic_cast<ContrastAlgorithm *>(controller_.getAlgorithm("contrast")); + if (contrast) { + if (mode->second == "Off") + contrast->restoreCe(); + else + contrast->enableCe(false); + } + + DenoiseAlgorithm *denoise = + dynamic_cast<DenoiseAlgorithm *>(controller_.getAlgorithm("denoise")); + if (denoise) { + /* \todo - make the HDR mode say what denoise it wants? */ + if (mode->second == "Night") + denoise->setConfig("night"); + else if (mode->second == "SingleExposure") + denoise->setConfig("hdr"); + /* MultiExposure doesn't need extra extra denoise. */ + else + denoise->setConfig("normal"); + } + } else + LOG(IPARPI, Warning) + << "HDR mode " << mode->second << " not supported"; + + break; + } + + case controls::rpi::STATS_OUTPUT_ENABLE: + statsMetadataOutput_ = ctrl.second.get<bool>(); + break; + default: LOG(IPARPI, Warning) << "Ctrl " << controls::controls.at(ctrl.first)->name() @@ -1190,7 +1367,7 @@ void IpaBase::fillDeviceStatus(const ControlList &sensorControls, unsigned int i int32_t hblank = sensorControls.get(V4L2_CID_HBLANK).get<int32_t>(); deviceStatus.lineLength = helper_->hblankToLineLength(hblank); - deviceStatus.shutterSpeed = helper_->exposure(exposureLines, deviceStatus.lineLength); + deviceStatus.exposureTime = helper_->exposure(exposureLines, deviceStatus.lineLength); deviceStatus.analogueGain = helper_->gain(gainCode); deviceStatus.frameLength = mode_.height + vblank; @@ -1217,7 +1394,7 @@ void IpaBase::reportMetadata(unsigned int ipaContext) DeviceStatus *deviceStatus = rpiMetadata.getLocked<DeviceStatus>("device.status"); if (deviceStatus) { libcameraMetadata_.set(controls::ExposureTime, - deviceStatus->shutterSpeed.get<std::micro>()); + deviceStatus->exposureTime.get<std::micro>()); libcameraMetadata_.set(controls::AnalogueGain, deviceStatus->analogueGain); libcameraMetadata_.set(controls::FrameDuration, helper_->exposure(deviceStatus->frameLength, deviceStatus->lineLength).get<std::micro>()); @@ -1228,9 +1405,19 @@ void IpaBase::reportMetadata(unsigned int ipaContext) } AgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>("agc.prepare_status"); - if (agcPrepareStatus) { - libcameraMetadata_.set(controls::AeLocked, agcPrepareStatus->locked); + if (agcPrepareStatus) libcameraMetadata_.set(controls::DigitalGain, agcPrepareStatus->digitalGain); + + RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( + controller_.getAlgorithm("agc")); + if (agc) { + if (!agc->autoExposureEnabled() && !agc->autoGainEnabled()) + libcameraMetadata_.set(controls::AeState, controls::AeStateIdle); + else if (agcPrepareStatus) + libcameraMetadata_.set(controls::AeState, + agcPrepareStatus->locked + ? controls::AeStateConverged + : controls::AeStateSearching); } LuxStatus *luxStatus = rpiMetadata.getLocked<LuxStatus>("lux.status"); @@ -1315,6 +1502,35 @@ void IpaBase::reportMetadata(unsigned int ipaContext) libcameraMetadata_.set(controls::AfPauseState, p); } + /* + * THe HDR algorithm sets the HDR channel into the agc.status at the time that those + * AGC parameters were calculated several frames ago, so it comes back to us now in + * the delayed_status. If this frame is too soon after a mode switch for the + * delayed_status to be available, we use the HDR status that came out of the + * switchMode call. + */ + const AgcStatus *agcStatus = rpiMetadata.getLocked<AgcStatus>("agc.delayed_status"); + const HdrStatus &hdrStatus = agcStatus ? agcStatus->hdr : hdrStatus_; + if (!hdrStatus.mode.empty() && hdrStatus.mode != "Off") { + int32_t hdrMode = controls::HdrModeOff; + for (auto const &[mode, name] : HdrModeTable) { + if (hdrStatus.mode == name) { + hdrMode = mode; + break; + } + } + libcameraMetadata_.set(controls::HdrMode, hdrMode); + + if (hdrStatus.channel == "short") + libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelShort); + else if (hdrStatus.channel == "long") + libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelLong); + else if (hdrStatus.channel == "medium") + libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelMedium); + else + libcameraMetadata_.set(controls::HdrChannel, controls::HdrChannelNone); + } + metadataReady.emit(libcameraMetadata_); } @@ -1339,15 +1555,15 @@ void IpaBase::applyFrameDurations(Duration minFrameDuration, Duration maxFrameDu /* * Calculate the maximum exposure time possible for the AGC to use. - * getBlanking() will update maxShutter with the largest exposure + * getBlanking() will update maxExposureTime with the largest exposure * value possible. */ - Duration maxShutter = Duration::max(); - helper_->getBlanking(maxShutter, minFrameDuration_, maxFrameDuration_); + Duration maxExposureTime = Duration::max(); + helper_->getBlanking(maxExposureTime, minFrameDuration_, maxFrameDuration_); RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( controller_.getAlgorithm("agc")); - agc->setMaxShutter(maxShutter); + agc->setMaxExposureTime(maxExposureTime); } void IpaBase::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls) @@ -1364,14 +1580,14 @@ void IpaBase::applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls) gainCode = std::clamp<int32_t>(gainCode, minGainCode, maxGainCode); /* getBlanking might clip exposure time to the fps limits. */ - Duration exposure = agcStatus->shutterTime; + Duration exposure = agcStatus->exposureTime; auto [vblank, hblank] = helper_->getBlanking(exposure, minFrameDuration_, maxFrameDuration_); int32_t exposureLines = helper_->exposureLines(exposure, helper_->hblankToLineLength(hblank)); LOG(IPARPI, Debug) << "Applying AGC Exposure: " << exposure - << " (Shutter lines: " << exposureLines << ", AGC requested " - << agcStatus->shutterTime << ") Gain: " + << " (Exposure lines: " << exposureLines << ", AGC requested " + << agcStatus->exposureTime << ") Gain: " << agcStatus->analogueGain << " (Gain Code: " << gainCode << ")"; diff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h index eaa9f711..1a811beb 100644 --- a/src/ipa/rpi/common/ipa_base.h +++ b/src/ipa/rpi/common/ipa_base.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Raspberry Pi Ltd * - * ipa_base.h - Raspberry Pi IPA base class + * Raspberry Pi IPA base class */ #pragma once @@ -22,6 +22,7 @@ #include "controller/agc_status.h" #include "controller/camera_mode.h" #include "controller/controller.h" +#include "controller/hdr_status.h" #include "controller/metadata.h" namespace libcamera { @@ -48,6 +49,11 @@ public: void processStats(const ProcessParams ¶ms) override; protected: + bool monoSensor() const + { + return monoSensor_; + } + /* Raspberry Pi controller specific defines. */ std::unique_ptr<RPiController::CamHelper> helper_; RPiController::Controller controller_; @@ -61,6 +67,14 @@ protected: /* Track the frame length times over FrameLengthsQueueSize frames. */ std::deque<utils::Duration> frameLengths_; utils::Duration lastTimeout_; + ControlList libcameraMetadata_; + bool statsMetadataOutput_; + + /* Remember the HDR status after a mode switch. */ + HdrStatus hdrStatus_; + + /* Whether the stitch block (if available) needs to swap buffers. */ + bool stitchSwapBuffers_; private: /* Number of metadata objects available in the context list. */ @@ -89,7 +103,6 @@ private: bool lensPresent_; bool monoSensor_; - ControlList libcameraMetadata_; std::array<RPiController::Metadata, numMetadataContexts> rpiMetadata_; diff --git a/src/ipa/rpi/controller/af_status.h b/src/ipa/rpi/controller/af_status.h index 92c08812..c1487cc4 100644 --- a/src/ipa/rpi/controller/af_status.h +++ b/src/ipa/rpi/controller/af_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Raspberry Pi Ltd * - * af_status.h - AF control algorithm status + * AF control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/agc_algorithm.h b/src/ipa/rpi/controller/agc_algorithm.h index b8986560..fdaa10e6 100644 --- a/src/ipa/rpi/controller/agc_algorithm.h +++ b/src/ipa/rpi/controller/agc_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * agc_algorithm.h - AGC/AEC control algorithm interface + * AGC/AEC control algorithm interface */ #pragma once @@ -22,17 +22,20 @@ public: virtual unsigned int getConvergenceFrames() const = 0; virtual std::vector<double> const &getWeights() const = 0; virtual void setEv(unsigned int channel, double ev) = 0; - virtual void setFlickerPeriod(unsigned int channel, - libcamera::utils::Duration flickerPeriod) = 0; - virtual void setFixedShutter(unsigned int channel, - libcamera::utils::Duration fixedShutter) = 0; - virtual void setMaxShutter(libcamera::utils::Duration maxShutter) = 0; + virtual void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) = 0; + virtual void setFixedExposureTime(unsigned int channel, + libcamera::utils::Duration fixedExposureTime) = 0; + virtual void setMaxExposureTime(libcamera::utils::Duration maxExposureTime) = 0; virtual void setFixedAnalogueGain(unsigned int channel, double fixedAnalogueGain) = 0; virtual void setMeteringMode(std::string const &meteringModeName) = 0; - virtual void setExposureMode(unsigned int channel, std::string const &exposureModeName) = 0; - virtual void setConstraintMode(unsigned int channel, std::string const &contraintModeName) = 0; - virtual void enableAuto(unsigned int channel) = 0; - virtual void disableAuto(unsigned int channel) = 0; + virtual void setExposureMode(std::string const &exposureModeName) = 0; + virtual void setConstraintMode(std::string const &contraintModeName) = 0; + virtual void enableAutoExposure() = 0; + virtual void disableAutoExposure() = 0; + virtual bool autoExposureEnabled() const = 0; + virtual void enableAutoGain() = 0; + virtual void disableAutoGain() = 0; + virtual bool autoGainEnabled() const = 0; virtual void setActiveChannels(const std::vector<unsigned int> &activeChannels) = 0; }; diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h index 68f89958..9308b156 100644 --- a/src/ipa/rpi/controller/agc_status.h +++ b/src/ipa/rpi/controller/agc_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * agc_status.h - AGC/AEC control algorithm status + * AGC/AEC control algorithm status */ #pragma once @@ -28,7 +28,7 @@ struct AgcStatus { libcamera::utils::Duration totalExposureValue; /* value for all exposure and gain for this image */ libcamera::utils::Duration targetExposureValue; /* (unfiltered) target total exposure AGC is aiming for */ - libcamera::utils::Duration shutterTime; + libcamera::utils::Duration exposureTime; double analogueGain; std::string exposureMode; std::string constraintMode; @@ -36,7 +36,7 @@ struct AgcStatus { double ev; libcamera::utils::Duration flickerPeriod; int floatingRegionEnable; - libcamera::utils::Duration fixedShutter; + libcamera::utils::Duration fixedExposureTime; double fixedAnalogueGain; unsigned int channel; HdrStatus hdr; diff --git a/src/ipa/rpi/controller/algorithm.cpp b/src/ipa/rpi/controller/algorithm.cpp index a957fde5..beed47a1 100644 --- a/src/ipa/rpi/controller/algorithm.cpp +++ b/src/ipa/rpi/controller/algorithm.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * algorithm.cpp - ISP control algorithms + * ISP control algorithms */ #include "algorithm.h" diff --git a/src/ipa/rpi/controller/algorithm.h b/src/ipa/rpi/controller/algorithm.h index 4aa814eb..1971bfdc 100644 --- a/src/ipa/rpi/controller/algorithm.h +++ b/src/ipa/rpi/controller/algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * algorithm.h - ISP control algorithm interface + * ISP control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/alsc_status.h b/src/ipa/rpi/controller/alsc_status.h index 49a9f4a0..329e8a37 100644 --- a/src/ipa/rpi/controller/alsc_status.h +++ b/src/ipa/rpi/controller/alsc_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * alsc_status.h - ALSC (auto lens shading correction) control algorithm status + * ALSC (auto lens shading correction) control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/awb_algorithm.h b/src/ipa/rpi/controller/awb_algorithm.h index 8462c4db..d941ed4e 100644 --- a/src/ipa/rpi/controller/awb_algorithm.h +++ b/src/ipa/rpi/controller/awb_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * awb_algorithm.h - AWB control algorithm interface + * AWB control algorithm interface */ #pragma once @@ -16,8 +16,10 @@ public: AwbAlgorithm(Controller *controller) : Algorithm(controller) {} /* An AWB algorithm must provide the following: */ virtual unsigned int getConvergenceFrames() const = 0; + virtual void initialValues(double &gainR, double &gainB) = 0; virtual void setMode(std::string const &modeName) = 0; virtual void setManualGains(double manualR, double manualB) = 0; + virtual void setColourTemperature(double temperatureK) = 0; virtual void enableAuto() = 0; virtual void disableAuto() = 0; }; diff --git a/src/ipa/rpi/controller/awb_status.h b/src/ipa/rpi/controller/awb_status.h index dd5a79e3..125df1a0 100644 --- a/src/ipa/rpi/controller/awb_status.h +++ b/src/ipa/rpi/controller/awb_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * awb_status.h - AWB control algorithm status + * AWB control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/black_level_algorithm.h b/src/ipa/rpi/controller/black_level_algorithm.h new file mode 100644 index 00000000..ce044e59 --- /dev/null +++ b/src/ipa/rpi/controller/black_level_algorithm.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * black level control algorithm interface + */ +#pragma once + +#include "algorithm.h" + +namespace RPiController { + +class BlackLevelAlgorithm : public Algorithm +{ +public: + BlackLevelAlgorithm(Controller *controller) + : Algorithm(controller) {} + /* A black level algorithm must provide the following: */ + virtual void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG, + uint16_t &blackLevelB) = 0; +}; + +} /* namespace RPiController */ diff --git a/src/ipa/rpi/controller/black_level_status.h b/src/ipa/rpi/controller/black_level_status.h index fd5e4ccb..57a0705a 100644 --- a/src/ipa/rpi/controller/black_level_status.h +++ b/src/ipa/rpi/controller/black_level_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * black_level_status.h - black level control algorithm status + * black level control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h index 475d4c5c..adffce41 100644 --- a/src/ipa/rpi/controller/cac_status.h +++ b/src/ipa/rpi/controller/cac_status.h @@ -6,8 +6,6 @@ */ #pragma once -#include "pwl.h" - struct CacStatus { std::vector<double> lutRx; std::vector<double> lutRy; diff --git a/src/ipa/rpi/controller/camera_mode.h b/src/ipa/rpi/controller/camera_mode.h index 63b11778..61162b32 100644 --- a/src/ipa/rpi/controller/camera_mode.h +++ b/src/ipa/rpi/controller/camera_mode.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2020, Raspberry Pi Ltd * - * camera_mode.h - description of a particular operating mode of a sensor + * description of a particular operating mode of a sensor */ #pragma once @@ -50,9 +50,9 @@ struct CameraMode { double sensitivity; /* pixel clock rate */ uint64_t pixelRate; - /* Mode specific shutter speed limits */ - libcamera::utils::Duration minShutter; - libcamera::utils::Duration maxShutter; + /* Mode specific exposure time limits */ + libcamera::utils::Duration minExposureTime; + libcamera::utils::Duration maxExposureTime; /* Mode specific analogue gain limits */ double minAnalogueGain; double maxAnalogueGain; diff --git a/src/ipa/rpi/controller/ccm_algorithm.h b/src/ipa/rpi/controller/ccm_algorithm.h index e2c4d771..6678ba75 100644 --- a/src/ipa/rpi/controller/ccm_algorithm.h +++ b/src/ipa/rpi/controller/ccm_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * ccm_algorithm.h - CCM (colour correction matrix) control algorithm interface + * CCM (colour correction matrix) control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/ccm_status.h b/src/ipa/rpi/controller/ccm_status.h index 5e28ee7c..c81bcd42 100644 --- a/src/ipa/rpi/controller/ccm_status.h +++ b/src/ipa/rpi/controller/ccm_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * ccm_status.h - CCM (colour correction matrix) control algorithm status + * CCM (colour correction matrix) control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/contrast_algorithm.h b/src/ipa/rpi/controller/contrast_algorithm.h index 895b36b0..2e983350 100644 --- a/src/ipa/rpi/controller/contrast_algorithm.h +++ b/src/ipa/rpi/controller/contrast_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * contrast_algorithm.h - contrast (gamma) control algorithm interface + * contrast (gamma) control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h index fb9fe4ba..1f175872 100644 --- a/src/ipa/rpi/controller/contrast_status.h +++ b/src/ipa/rpi/controller/contrast_status.h @@ -2,11 +2,11 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * contrast_status.h - contrast (gamma) control algorithm status + * contrast (gamma) control algorithm status */ #pragma once -#include "pwl.h" +#include "libipa/pwl.h" /* * The "contrast" algorithm creates a gamma curve, optionally doing a little bit @@ -14,7 +14,7 @@ */ struct ContrastStatus { - RPiController::Pwl gammaCurve; + libcamera::ipa::Pwl gammaCurve; double brightness; double contrast; }; diff --git a/src/ipa/rpi/controller/controller.cpp b/src/ipa/rpi/controller/controller.cpp index e62becd8..651fff63 100644 --- a/src/ipa/rpi/controller/controller.cpp +++ b/src/ipa/rpi/controller/controller.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * controller.cpp - ISP controller + * ISP controller */ #include <assert.h> @@ -17,6 +17,7 @@ using namespace RPiController; using namespace libcamera; +using namespace std::literals::chrono_literals; LOG_DEFINE_CATEGORY(RPiController) @@ -37,6 +38,8 @@ static const std::map<std::string, Controller::HardwareConfig> HardwareConfigMap .numGammaPoints = 33, .pipelineWidth = 13, .statsInline = false, + .minPixelProcessingTime = 0s, + .dataBufferStrided = true, } }, { @@ -51,6 +54,25 @@ static const std::map<std::string, Controller::HardwareConfig> HardwareConfigMap .numGammaPoints = 64, .pipelineWidth = 16, .statsInline = true, + + /* + * The constraint below is on the rate of pixels going + * from CSI2 peripheral to ISP-FE (400Mpix/s, plus tiny + * overheads per scanline, for which 380Mpix/s is a + * conservative bound). + * + * There is a 64kbit data FIFO before the bottleneck, + * which means that in all reasonable cases the + * constraint applies at a timescale >= 1 scanline, so + * adding horizontal blanking can prevent loss. + * + * If the backlog were to grow beyond 64kbit during a + * single scanline, there could still be loss. This + * could happen using 4 lanes at 1.5Gbps at 10bpp with + * frames wider than ~16,000 pixels. + */ + .minPixelProcessingTime = 1.0us / 380, + .dataBufferStrided = false, } }, }; diff --git a/src/ipa/rpi/controller/controller.h b/src/ipa/rpi/controller/controller.h index 6e5f5952..fdb46557 100644 --- a/src/ipa/rpi/controller/controller.h +++ b/src/ipa/rpi/controller/controller.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * controller.h - ISP controller interface + * ISP controller interface */ #pragma once @@ -15,6 +15,7 @@ #include <vector> #include <string> +#include <libcamera/base/utils.h> #include "libcamera/internal/yaml_parser.h" #include "camera_mode.h" @@ -47,6 +48,8 @@ public: unsigned int numGammaPoints; unsigned int pipelineWidth; bool statsInline; + libcamera::utils::Duration minPixelProcessingTime; + bool dataBufferStrided; }; Controller(); diff --git a/src/ipa/rpi/controller/denoise_algorithm.h b/src/ipa/rpi/controller/denoise_algorithm.h index 444cbc25..b9a2a33c 100644 --- a/src/ipa/rpi/controller/denoise_algorithm.h +++ b/src/ipa/rpi/controller/denoise_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * denoise.h - Denoise control algorithm interface + * Denoise control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/denoise_status.h b/src/ipa/rpi/controller/denoise_status.h index 4d2bd291..eead6086 100644 --- a/src/ipa/rpi/controller/denoise_status.h +++ b/src/ipa/rpi/controller/denoise_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * denoise_status.h - Denoise control algorithm status + * Denoise control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/device_status.cpp b/src/ipa/rpi/controller/device_status.cpp index c907efdd..1695764d 100644 --- a/src/ipa/rpi/controller/device_status.cpp +++ b/src/ipa/rpi/controller/device_status.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * device_status.cpp - device (image sensor) status + * device (image sensor) status */ #include "device_status.h" @@ -10,7 +10,7 @@ using namespace libcamera; /* for the Duration operator<< overload */ std::ostream &operator<<(std::ostream &out, const DeviceStatus &d) { - out << "Exposure: " << d.shutterSpeed + out << "Exposure time: " << d.exposureTime << " Frame length: " << d.frameLength << " Line length: " << d.lineLength << " Gain: " << d.analogueGain; diff --git a/src/ipa/rpi/controller/device_status.h b/src/ipa/rpi/controller/device_status.h index c45db749..b1792035 100644 --- a/src/ipa/rpi/controller/device_status.h +++ b/src/ipa/rpi/controller/device_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * device_status.h - device (image sensor) status + * device (image sensor) status */ #pragma once @@ -12,21 +12,21 @@ #include <libcamera/base/utils.h> /* - * Definition of "device metadata" which stores things like shutter time and + * Definition of "device metadata" which stores things like exposure time and * analogue gain that downstream control algorithms will want to know. */ struct DeviceStatus { DeviceStatus() - : shutterSpeed(std::chrono::seconds(0)), frameLength(0), + : exposureTime(std::chrono::seconds(0)), frameLength(0), lineLength(std::chrono::seconds(0)), analogueGain(0.0) { } friend std::ostream &operator<<(std::ostream &out, const DeviceStatus &d); - /* time shutter is open */ - libcamera::utils::Duration shutterSpeed; + /* time the image is exposed */ + libcamera::utils::Duration exposureTime; /* frame length given in number of lines */ uint32_t frameLength; /* line length for the current frame */ diff --git a/src/ipa/rpi/controller/dpc_status.h b/src/ipa/rpi/controller/dpc_status.h index 46d0cf34..9f30d5d9 100644 --- a/src/ipa/rpi/controller/dpc_status.h +++ b/src/ipa/rpi/controller/dpc_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * dpc_status.h - DPC (defective pixel correction) control algorithm status + * DPC (defective pixel correction) control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/geq_status.h b/src/ipa/rpi/controller/geq_status.h index 2d749fc9..cb107a48 100644 --- a/src/ipa/rpi/controller/geq_status.h +++ b/src/ipa/rpi/controller/geq_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * geq_status.h - GEQ (green equalisation) control algorithm status + * GEQ (green equalisation) control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/hdr_algorithm.h b/src/ipa/rpi/controller/hdr_algorithm.h index f622e099..b889d8fd 100644 --- a/src/ipa/rpi/controller/hdr_algorithm.h +++ b/src/ipa/rpi/controller/hdr_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Raspberry Pi Ltd * - * hdr_algorithm.h - HDR control algorithm interface + * HDR control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/hdr_status.h b/src/ipa/rpi/controller/hdr_status.h index 24b1a935..a4955778 100644 --- a/src/ipa/rpi/controller/hdr_status.h +++ b/src/ipa/rpi/controller/hdr_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023 Raspberry Pi Ltd * - * hdr_status.h - HDR control algorithm status + * HDR control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/histogram.cpp b/src/ipa/rpi/controller/histogram.cpp index 0a27ba2c..13089839 100644 --- a/src/ipa/rpi/controller/histogram.cpp +++ b/src/ipa/rpi/controller/histogram.cpp @@ -2,9 +2,9 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * histogram.cpp - histogram calculations + * histogram calculations */ -#include <math.h> +#include <cmath> #include <stdio.h> #include "histogram.h" @@ -47,23 +47,29 @@ double Histogram::quantile(double q, int first, int last) const double Histogram::interBinMean(double binLo, double binHi) const { - assert(binHi > binLo); + assert(binHi >= binLo); double sumBinFreq = 0, cumulFreq = 0; - for (double binNext = floor(binLo) + 1.0; binNext <= ceil(binHi); + for (double binNext = std::floor(binLo) + 1.0; binNext <= std::ceil(binHi); binLo = binNext, binNext += 1.0) { - int bin = floor(binLo); + int bin = std::floor(binLo); double freq = (cumulative_[bin + 1] - cumulative_[bin]) * (std::min(binNext, binHi) - binLo); sumBinFreq += bin * freq; cumulFreq += freq; } + + if (cumulFreq == 0) { + /* interval had zero width or contained no weight? */ + return binHi; + } + /* add 0.5 to give an average for bin mid-points */ return sumBinFreq / cumulFreq + 0.5; } double Histogram::interQuantileMean(double qLo, double qHi) const { - assert(qHi > qLo); + assert(qHi >= qLo); double pLo = quantile(qLo); double pHi = quantile(qHi, (int)pLo); return interBinMean(pLo, pHi); diff --git a/src/ipa/rpi/controller/histogram.h b/src/ipa/rpi/controller/histogram.h index e2c5509b..ab4e5e31 100644 --- a/src/ipa/rpi/controller/histogram.h +++ b/src/ipa/rpi/controller/histogram.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * histogram.h - histogram calculation interface + * histogram calculation interface */ #pragma once diff --git a/src/ipa/rpi/controller/lux_status.h b/src/ipa/rpi/controller/lux_status.h index 5eb9faac..d8729f43 100644 --- a/src/ipa/rpi/controller/lux_status.h +++ b/src/ipa/rpi/controller/lux_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * lux_status.h - Lux control algorithm status + * Lux control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build index 32a4d31c..74b74888 100644 --- a/src/ipa/rpi/controller/meson.build +++ b/src/ipa/rpi/controller/meson.build @@ -5,7 +5,6 @@ rpi_ipa_controller_sources = files([ 'controller.cpp', 'device_status.cpp', 'histogram.cpp', - 'pwl.cpp', 'rpi/af.cpp', 'rpi/agc.cpp', 'rpi/agc_channel.cpp', @@ -32,4 +31,5 @@ rpi_ipa_controller_deps = [ ] rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources, + include_directories : libipa_includes, dependencies : rpi_ipa_controller_deps) diff --git a/src/ipa/rpi/controller/metadata.h b/src/ipa/rpi/controller/metadata.h index a232dcb1..77d3b074 100644 --- a/src/ipa/rpi/controller/metadata.h +++ b/src/ipa/rpi/controller/metadata.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * metadata.h - general metadata class + * general metadata class */ #pragma once @@ -12,6 +12,7 @@ #include <map> #include <mutex> #include <string> +#include <utility> #include <libcamera/base/thread_annotations.h> @@ -36,10 +37,10 @@ public: } template<typename T> - void set(std::string const &tag, T const &value) + void set(std::string const &tag, T &&value) { std::scoped_lock lock(mutex_); - data_[tag] = value; + data_[tag] = std::forward<T>(value); } template<typename T> @@ -90,6 +91,12 @@ public: data_.insert(other.data_.begin(), other.data_.end()); } + void erase(std::string const &tag) + { + std::scoped_lock lock(mutex_); + eraseLocked(tag); + } + template<typename T> T *getLocked(std::string const &tag) { @@ -104,10 +111,18 @@ public: } template<typename T> - void setLocked(std::string const &tag, T const &value) + void setLocked(std::string const &tag, T &&value) { /* Use this only if you're holding the lock yourself. */ - data_[tag] = value; + data_[tag] = std::forward<T>(value); + } + + void eraseLocked(std::string const &tag) + { + auto it = data_.find(tag); + if (it == data_.end()) + return; + data_.erase(it); } /* diff --git a/src/ipa/rpi/controller/noise_status.h b/src/ipa/rpi/controller/noise_status.h index da194f71..1919da32 100644 --- a/src/ipa/rpi/controller/noise_status.h +++ b/src/ipa/rpi/controller/noise_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * noise_status.h - Noise control algorithm status + * Noise control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/pdaf_data.h b/src/ipa/rpi/controller/pdaf_data.h index 470510f2..779b987d 100644 --- a/src/ipa/rpi/controller/pdaf_data.h +++ b/src/ipa/rpi/controller/pdaf_data.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Raspberry Pi Ltd * - * pdaf_data.h - PDAF Metadata + * PDAF Metadata */ #pragma once diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp deleted file mode 100644 index 70c2e24b..00000000 --- a/src/ipa/rpi/controller/pwl.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/* - * Copyright (C) 2019, Raspberry Pi Ltd - * - * pwl.cpp - piecewise linear functions - */ - -#include <cassert> -#include <cmath> -#include <stdexcept> - -#include "pwl.h" - -using namespace RPiController; - -int Pwl::read(const libcamera::YamlObject ¶ms) -{ - if (!params.size() || params.size() % 2) - return -EINVAL; - - const auto &list = params.asList(); - - for (auto it = list.begin(); it != list.end(); it++) { - auto x = it->get<double>(); - if (!x) - return -EINVAL; - if (it != list.begin() && *x <= points_.back().x) - return -EINVAL; - - auto y = (++it)->get<double>(); - if (!y) - return -EINVAL; - - points_.push_back(Point(*x, *y)); - } - - return 0; -} - -void Pwl::append(double x, double y, const double eps) -{ - if (points_.empty() || points_.back().x + eps < x) - points_.push_back(Point(x, y)); -} - -void Pwl::prepend(double x, double y, const double eps) -{ - if (points_.empty() || points_.front().x - eps > x) - points_.insert(points_.begin(), Point(x, y)); -} - -Pwl::Interval Pwl::domain() const -{ - return Interval(points_[0].x, points_[points_.size() - 1].x); -} - -Pwl::Interval Pwl::range() const -{ - double lo = points_[0].y, hi = lo; - for (auto &p : points_) - lo = std::min(lo, p.y), hi = std::max(hi, p.y); - return Interval(lo, hi); -} - -bool Pwl::empty() const -{ - return points_.empty(); -} - -double Pwl::eval(double x, int *spanPtr, bool updateSpan) const -{ - int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1); - if (spanPtr && updateSpan) - *spanPtr = span; - return points_[span].y + - (x - points_[span].x) * (points_[span + 1].y - points_[span].y) / - (points_[span + 1].x - points_[span].x); -} - -int Pwl::findSpan(double x, int span) const -{ - /* - * Pwls are generally small, so linear search may well be faster than - * binary, though could review this if large PWls start turning up. - */ - int lastSpan = points_.size() - 2; - /* - * some algorithms may call us with span pointing directly at the last - * control point - */ - span = std::max(0, std::min(lastSpan, span)); - while (span < lastSpan && x >= points_[span + 1].x) - span++; - while (span && x < points_[span].x) - span--; - return span; -} - -Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span, - const double eps) const -{ - assert(span >= -1); - bool prevOffEnd = false; - for (span = span + 1; span < (int)points_.size() - 1; span++) { - Point spanVec = points_[span + 1] - points_[span]; - double t = ((xy - points_[span]) % spanVec) / spanVec.len2(); - if (t < -eps) /* off the start of this span */ - { - if (span == 0) { - perp = points_[span]; - return PerpType::Start; - } else if (prevOffEnd) { - perp = points_[span]; - return PerpType::Vertex; - } - } else if (t > 1 + eps) /* off the end of this span */ - { - if (span == (int)points_.size() - 2) { - perp = points_[span + 1]; - return PerpType::End; - } - prevOffEnd = true; - } else /* a true perpendicular */ - { - perp = points_[span] + spanVec * t; - return PerpType::Perpendicular; - } - } - return PerpType::None; -} - -Pwl Pwl::inverse(bool *trueInverse, const double eps) const -{ - bool appended = false, prepended = false, neither = false; - Pwl inverse; - - for (Point const &p : points_) { - if (inverse.empty()) - inverse.append(p.y, p.x, eps); - else if (std::abs(inverse.points_.back().x - p.y) <= eps || - std::abs(inverse.points_.front().x - p.y) <= eps) - /* do nothing */; - else if (p.y > inverse.points_.back().x) { - inverse.append(p.y, p.x, eps); - appended = true; - } else if (p.y < inverse.points_.front().x) { - inverse.prepend(p.y, p.x, eps); - prepended = true; - } else - neither = true; - } - - /* - * This is not a proper inverse if we found ourselves putting points - * onto both ends of the inverse, or if there were points that couldn't - * go on either. - */ - if (trueInverse) - *trueInverse = !(neither || (appended && prepended)); - - return inverse; -} - -Pwl Pwl::compose(Pwl const &other, const double eps) const -{ - double thisX = points_[0].x, thisY = points_[0].y; - int thisSpan = 0, otherSpan = other.findSpan(thisY, 0); - Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } }); - while (thisSpan != (int)points_.size() - 1) { - double dx = points_[thisSpan + 1].x - points_[thisSpan].x, - dy = points_[thisSpan + 1].y - points_[thisSpan].y; - if (std::abs(dy) > eps && - otherSpan + 1 < (int)other.points_.size() && - points_[thisSpan + 1].y >= - other.points_[otherSpan + 1].x + eps) { - /* - * next control point in result will be where this - * function's y reaches the next span in other - */ - thisX = points_[thisSpan].x + - (other.points_[otherSpan + 1].x - - points_[thisSpan].y) * - dx / dy; - thisY = other.points_[++otherSpan].x; - } else if (std::abs(dy) > eps && otherSpan > 0 && - points_[thisSpan + 1].y <= - other.points_[otherSpan - 1].x - eps) { - /* - * next control point in result will be where this - * function's y reaches the previous span in other - */ - thisX = points_[thisSpan].x + - (other.points_[otherSpan + 1].x - - points_[thisSpan].y) * - dx / dy; - thisY = other.points_[--otherSpan].x; - } else { - /* we stay in the same span in other */ - thisSpan++; - thisX = points_[thisSpan].x, - thisY = points_[thisSpan].y; - } - result.append(thisX, other.eval(thisY, &otherSpan, false), - eps); - } - return result; -} - -void Pwl::map(std::function<void(double x, double y)> f) const -{ - for (auto &pt : points_) - f(pt.x, pt.y); -} - -void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1, - std::function<void(double x, double y0, double y1)> f) -{ - int span0 = 0, span1 = 0; - double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x); - f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); - while (span0 < (int)pwl0.points_.size() - 1 || - span1 < (int)pwl1.points_.size() - 1) { - if (span0 == (int)pwl0.points_.size() - 1) - x = pwl1.points_[++span1].x; - else if (span1 == (int)pwl1.points_.size() - 1) - x = pwl0.points_[++span0].x; - else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x) - x = pwl1.points_[++span1].x; - else - x = pwl0.points_[++span0].x; - f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false)); - } -} - -Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1, - std::function<double(double x, double y0, double y1)> f, - const double eps) -{ - Pwl result; - map2(pwl0, pwl1, [&](double x, double y0, double y1) { - result.append(x, f(x, y0, y1), eps); - }); - return result; -} - -void Pwl::matchDomain(Interval const &domain, bool clip, const double eps) -{ - int span = 0; - prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span), - eps); - span = points_.size() - 2; - append(domain.end, eval(clip ? points_.back().x : domain.end, &span), - eps); -} - -Pwl &Pwl::operator*=(double d) -{ - for (auto &pt : points_) - pt.y *= d; - return *this; -} - -void Pwl::debug(FILE *fp) const -{ - fprintf(fp, "Pwl {\n"); - for (auto &p : points_) - fprintf(fp, "\t(%g, %g)\n", p.x, p.y); - fprintf(fp, "}\n"); -} diff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h deleted file mode 100644 index aacf6039..00000000 --- a/src/ipa/rpi/controller/pwl.h +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/* - * Copyright (C) 2019, Raspberry Pi Ltd - * - * pwl.h - piecewise linear functions interface - */ -#pragma once - -#include <functional> -#include <math.h> -#include <vector> - -#include "libcamera/internal/yaml_parser.h" - -namespace RPiController { - -class Pwl -{ -public: - struct Interval { - Interval(double _start, double _end) - : start(_start), end(_end) - { - } - double start, end; - bool contains(double value) - { - return value >= start && value <= end; - } - double clip(double value) - { - return value < start ? start - : (value > end ? end : value); - } - double len() const { return end - start; } - }; - struct Point { - Point() : x(0), y(0) {} - Point(double _x, double _y) - : x(_x), y(_y) {} - double x, y; - Point operator-(Point const &p) const - { - return Point(x - p.x, y - p.y); - } - Point operator+(Point const &p) const - { - return Point(x + p.x, y + p.y); - } - double operator%(Point const &p) const - { - return x * p.x + y * p.y; - } - Point operator*(double f) const { return Point(x * f, y * f); } - Point operator/(double f) const { return Point(x / f, y / f); } - double len2() const { return x * x + y * y; } - double len() const { return sqrt(len2()); } - }; - Pwl() {} - Pwl(std::vector<Point> const &points) : points_(points) {} - int read(const libcamera::YamlObject ¶ms); - void append(double x, double y, const double eps = 1e-6); - void prepend(double x, double y, const double eps = 1e-6); - Interval domain() const; - Interval range() const; - bool empty() const; - /* - * Evaluate Pwl, optionally supplying an initial guess for the - * "span". The "span" may be optionally be updated. If you want to know - * the "span" value but don't have an initial guess you can set it to - * -1. - */ - double eval(double x, int *spanPtr = nullptr, - bool updateSpan = true) const; - /* - * Find perpendicular closest to xy, starting from span+1 so you can - * call it repeatedly to check for multiple closest points (set span to - * -1 on the first call). Also returns "pseudo" perpendiculars; see - * PerpType enum. - */ - enum class PerpType { - None, /* no perpendicular found */ - Start, /* start of Pwl is closest point */ - End, /* end of Pwl is closest point */ - Vertex, /* vertex of Pwl is closest point */ - Perpendicular /* true perpendicular found */ - }; - PerpType invert(Point const &xy, Point &perp, int &span, - const double eps = 1e-6) const; - /* - * Compute the inverse function. Indicate if it is a proper (true) - * inverse, or only a best effort (e.g. input was non-monotonic). - */ - Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const; - /* Compose two Pwls together, doing "this" first and "other" after. */ - Pwl compose(Pwl const &other, const double eps = 1e-6) const; - /* Apply function to (x,y) values at every control point. */ - void map(std::function<void(double x, double y)> f) const; - /* - * Apply function to (x, y0, y1) values wherever either Pwl has a - * control point. - */ - static void map2(Pwl const &pwl0, Pwl const &pwl1, - std::function<void(double x, double y0, double y1)> f); - /* - * Combine two Pwls, meaning we create a new Pwl where the y values are - * given by running f wherever either has a knot. - */ - static Pwl - combine(Pwl const &pwl0, Pwl const &pwl1, - std::function<double(double x, double y0, double y1)> f, - const double eps = 1e-6); - /* - * Make "this" match (at least) the given domain. Any extension my be - * clipped or linear. - */ - void matchDomain(Interval const &domain, bool clip = true, - const double eps = 1e-6); - Pwl &operator*=(double d); - void debug(FILE *fp = stdout) const; - -private: - int findSpan(double x, int span) const; - std::vector<Point> points_; -}; - -} /* namespace RPiController */ diff --git a/src/ipa/rpi/controller/region_stats.h b/src/ipa/rpi/controller/region_stats.h index a8860dc8..c60f7d9a 100644 --- a/src/ipa/rpi/controller/region_stats.h +++ b/src/ipa/rpi/controller/region_stats.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Raspberry Pi Ltd * - * region_stats.h - Raspberry Pi region based statistics container + * Raspberry Pi region based statistics container */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp index ed0c8a94..2157eb94 100644 --- a/src/ipa/rpi/controller/rpi/af.cpp +++ b/src/ipa/rpi/controller/rpi/af.cpp @@ -2,13 +2,13 @@ /* * Copyright (C) 2022-2023, Raspberry Pi Ltd * - * af.cpp - Autofocus control algorithm + * Autofocus control algorithm */ #include "af.h" +#include <cmath> #include <iomanip> -#include <math.h> #include <stdlib.h> #include <libcamera/base/log.h> @@ -139,7 +139,7 @@ int Af::CfgParams::read(const libcamera::YamlObject ¶ms) readNumber<uint32_t>(skipFrames, params, "skip_frames"); if (params.contains("map")) - map.read(params["map"]); + map = params["map"].get<ipa::Pwl>(ipa::Pwl{}); else LOG(RPiAf, Warning) << "No map defined"; @@ -721,7 +721,7 @@ bool Af::setLensPosition(double dioptres, int *hwpos) if (mode_ == AfModeManual) { LOG(RPiAf, Debug) << "setLensPosition: " << dioptres; - ftarget_ = cfg_.map.domain().clip(dioptres); + ftarget_ = cfg_.map.domain().clamp(dioptres); changed = !(initted_ && fsmooth_ == ftarget_); updateLensPosition(); } diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h index 6d2bae67..317a51f3 100644 --- a/src/ipa/rpi/controller/rpi/af.h +++ b/src/ipa/rpi/controller/rpi/af.h @@ -2,14 +2,15 @@ /* * Copyright (C) 2022-2023, Raspberry Pi Ltd * - * af.h - Autofocus control algorithm + * Autofocus control algorithm */ #pragma once #include "../af_algorithm.h" #include "../af_status.h" #include "../pdaf_data.h" -#include "../pwl.h" + +#include "libipa/pwl.h" /* * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF. @@ -100,7 +101,7 @@ private: uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */ uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */ uint32_t skipFrames; /* frames to skip at start or modeswitch */ - Pwl map; /* converts dioptres -> lens driver position */ + libcamera::ipa::Pwl map; /* converts dioptres -> lens driver position */ CfgParams(); int read(const libcamera::YamlObject ¶ms); diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp index 32eb3624..02bfdb4a 100644 --- a/src/ipa/rpi/controller/rpi/agc.cpp +++ b/src/ipa/rpi/controller/rpi/agc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * agc.cpp - AGC/AEC control algorithm + * AGC/AEC control algorithm */ #include "agc.h" @@ -74,22 +74,62 @@ int Agc::checkChannel(unsigned int channelIndex) const return 0; } -void Agc::disableAuto(unsigned int channelIndex) +void Agc::disableAutoExposure() { - if (checkChannel(channelIndex)) - return; + LOG(RPiAgc, Debug) << "disableAutoExposure"; - LOG(RPiAgc, Debug) << "disableAuto for channel " << channelIndex; - channelData_[channelIndex].channel.disableAuto(); + /* All channels are enabled/disabled together. */ + for (auto &data : channelData_) + data.channel.disableAutoExposure(); } -void Agc::enableAuto(unsigned int channelIndex) +void Agc::enableAutoExposure() { - if (checkChannel(channelIndex)) - return; + LOG(RPiAgc, Debug) << "enableAutoExposure"; + + /* All channels are enabled/disabled together. */ + for (auto &data : channelData_) + data.channel.enableAutoExposure(); +} + +bool Agc::autoExposureEnabled() const +{ + LOG(RPiAgc, Debug) << "autoExposureEnabled"; - LOG(RPiAgc, Debug) << "enableAuto for channel " << channelIndex; - channelData_[channelIndex].channel.enableAuto(); + /* + * We always have at least one channel, and since all channels are + * enabled and disabled together we can simply check the first one. + */ + return channelData_[0].channel.autoExposureEnabled(); +} + +void Agc::disableAutoGain() +{ + LOG(RPiAgc, Debug) << "disableAutoGain"; + + /* All channels are enabled/disabled together. */ + for (auto &data : channelData_) + data.channel.disableAutoGain(); +} + +void Agc::enableAutoGain() +{ + LOG(RPiAgc, Debug) << "enableAutoGain"; + + /* All channels are enabled/disabled together. */ + for (auto &data : channelData_) + data.channel.enableAutoGain(); +} + +bool Agc::autoGainEnabled() const +{ + LOG(RPiAgc, Debug) << "autoGainEnabled"; + + /* + * We always have at least one channel, and since all channels are + * enabled and disabled together we can simply check the first one. + */ + return channelData_[0].channel.autoGainEnabled(); } unsigned int Agc::getConvergenceFrames() const @@ -118,31 +158,30 @@ void Agc::setEv(unsigned int channelIndex, double ev) channelData_[channelIndex].channel.setEv(ev); } -void Agc::setFlickerPeriod(unsigned int channelIndex, Duration flickerPeriod) +void Agc::setFlickerPeriod(Duration flickerPeriod) { - if (checkChannel(channelIndex)) - return; + LOG(RPiAgc, Debug) << "setFlickerPeriod " << flickerPeriod; - LOG(RPiAgc, Debug) << "setFlickerPeriod " << flickerPeriod - << " for channel " << channelIndex; - channelData_[channelIndex].channel.setFlickerPeriod(flickerPeriod); + /* Flicker period will be the same across all channels. */ + for (auto &data : channelData_) + data.channel.setFlickerPeriod(flickerPeriod); } -void Agc::setMaxShutter(Duration maxShutter) +void Agc::setMaxExposureTime(Duration maxExposureTime) { /* Frame durations will be the same across all channels too. */ for (auto &data : channelData_) - data.channel.setMaxShutter(maxShutter); + data.channel.setMaxExposureTime(maxExposureTime); } -void Agc::setFixedShutter(unsigned int channelIndex, Duration fixedShutter) +void Agc::setFixedExposureTime(unsigned int channelIndex, Duration fixedExposureTime) { if (checkChannel(channelIndex)) return; - LOG(RPiAgc, Debug) << "setFixedShutter " << fixedShutter + LOG(RPiAgc, Debug) << "setFixedExposureTime " << fixedExposureTime << " for channel " << channelIndex; - channelData_[channelIndex].channel.setFixedShutter(fixedShutter); + channelData_[channelIndex].channel.setFixedExposureTime(fixedExposureTime); } void Agc::setFixedAnalogueGain(unsigned int channelIndex, double fixedAnalogueGain) @@ -162,22 +201,22 @@ void Agc::setMeteringMode(std::string const &meteringModeName) data.channel.setMeteringMode(meteringModeName); } -void Agc::setExposureMode(unsigned int channelIndex, std::string const &exposureModeName) +void Agc::setExposureMode(std::string const &exposureModeName) { - if (checkChannel(channelIndex)) - return; + LOG(RPiAgc, Debug) << "setExposureMode " << exposureModeName; - LOG(RPiAgc, Debug) << "setExposureMode " << exposureModeName - << " for channel " << channelIndex; - channelData_[channelIndex].channel.setExposureMode(exposureModeName); + /* Exposure mode will be the same across all channels. */ + for (auto &data : channelData_) + data.channel.setExposureMode(exposureModeName); } -void Agc::setConstraintMode(unsigned int channelIndex, std::string const &constraintModeName) +void Agc::setConstraintMode(std::string const &constraintModeName) { - if (checkChannel(channelIndex)) - return; + LOG(RPiAgc, Debug) << "setConstraintMode " << constraintModeName; - channelData_[channelIndex].channel.setConstraintMode(constraintModeName); + /* Constraint mode will be the same across all channels. */ + for (auto &data : channelData_) + data.channel.setConstraintMode(constraintModeName); } template<typename T> diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h index 90890439..c3a940bf 100644 --- a/src/ipa/rpi/controller/rpi/agc.h +++ b/src/ipa/rpi/controller/rpi/agc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * agc.h - AGC/AEC control algorithm + * AGC/AEC control algorithm */ #pragma once @@ -31,20 +31,21 @@ public: unsigned int getConvergenceFrames() const override; std::vector<double> const &getWeights() const override; void setEv(unsigned int channel, double ev) override; - void setFlickerPeriod(unsigned int channelIndex, - libcamera::utils::Duration flickerPeriod) override; - void setMaxShutter(libcamera::utils::Duration maxShutter) override; - void setFixedShutter(unsigned int channelIndex, - libcamera::utils::Duration fixedShutter) override; + void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) override; + void setMaxExposureTime(libcamera::utils::Duration maxExposureTime) override; + void setFixedExposureTime(unsigned int channelIndex, + libcamera::utils::Duration fixedExposureTime) override; void setFixedAnalogueGain(unsigned int channelIndex, double fixedAnalogueGain) override; void setMeteringMode(std::string const &meteringModeName) override; - void setExposureMode(unsigned int channelIndex, - std::string const &exposureModeName) override; - void setConstraintMode(unsigned int channelIndex, - std::string const &contraintModeName) override; - void enableAuto(unsigned int channelIndex) override; - void disableAuto(unsigned int channelIndex) override; + void setExposureMode(std::string const &exposureModeName) override; + void setConstraintMode(std::string const &contraintModeName) override; + void enableAutoExposure() override; + void disableAutoExposure() override; + bool autoExposureEnabled() const override; + void enableAutoGain() override; + void disableAutoGain() override; + bool autoGainEnabled() const override; void switchMode(CameraMode const &cameraMode, Metadata *metadata) override; void prepare(Metadata *imageMetadata) override; void process(StatisticsPtr &stats, Metadata *imageMetadata) override; diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp index 1e7eae06..b2999364 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Raspberry Pi Ltd * - * agc_channel.cpp - AGC/AEC control algorithm + * AGC/AEC control algorithm */ #include "agc_channel.h" @@ -12,6 +12,10 @@ #include <libcamera/base/log.h> +#include "libcamera/internal/vector.h" + +#include "libipa/colours.h" + #include "../awb_status.h" #include "../device_status.h" #include "../histogram.h" @@ -65,7 +69,7 @@ int AgcExposureMode::read(const libcamera::YamlObject ¶ms) auto value = params["shutter"].getList<double>(); if (!value) return -EINVAL; - std::transform(value->begin(), value->end(), std::back_inserter(shutter), + std::transform(value->begin(), value->end(), std::back_inserter(exposureTime), [](double v) { return v * 1us; }); value = params["gain"].getList<double>(); @@ -73,13 +77,13 @@ int AgcExposureMode::read(const libcamera::YamlObject ¶ms) return -EINVAL; gain = std::move(*value); - if (shutter.size() < 2 || gain.size() < 2) { + if (exposureTime.size() < 2 || gain.size() < 2) { LOG(RPiAgc, Error) << "AgcExposureMode: must have at least two entries in exposure profile"; return -EINVAL; } - if (shutter.size() != gain.size()) { + if (exposureTime.size() != gain.size()) { LOG(RPiAgc, Error) << "AgcExposureMode: expect same number of exposure and gain entries in exposure profile"; return -EINVAL; @@ -130,7 +134,8 @@ int AgcConstraint::read(const libcamera::YamlObject ¶ms) return -EINVAL; qHi = *value; - return yTarget.read(params["y_target"]); + yTarget = params["y_target"].get<ipa::Pwl>(ipa::Pwl{}); + return yTarget.empty() ? -EINVAL : 0; } static std::tuple<int, AgcConstraintMode> @@ -237,9 +242,9 @@ int AgcConfig::read(const libcamera::YamlObject ¶ms) return ret; } - ret = yTarget.read(params["y_target"]); - if (ret) - return ret; + yTarget = params["y_target"].get<ipa::Pwl>(ipa::Pwl{}); + if (yTarget.empty()) + return -EINVAL; speed = params["speed"].get<double>(0.2); startupFrames = params["startup_frames"].get<uint16_t>(10); @@ -253,11 +258,13 @@ int AgcConfig::read(const libcamera::YamlObject ¶ms) stableRegion = params["stable_region"].get<double>(0.02); + desaturate = params["desaturate"].get<int>(1); + return 0; } AgcChannel::ExposureValues::ExposureValues() - : shutter(0s), analogueGain(0), + : exposureTime(0s), analogueGain(0), totalExposure(0s), totalExposureNoDG(0s) { } @@ -266,9 +273,13 @@ AgcChannel::AgcChannel() : meteringMode_(nullptr), exposureMode_(nullptr), constraintMode_(nullptr), frameCount_(0), lockCount_(0), lastTargetExposure_(0s), ev_(1.0), flickerPeriod_(0s), - maxShutter_(0s), fixedShutter_(0s), fixedAnalogueGain_(0.0) + maxExposureTime_(0s), fixedExposureTime_(0s), fixedAnalogueGain_(0.0) { - memset(&awb_, 0, sizeof(awb_)); + /* Set AWB default values in case early frames have no updates in metadata. */ + awb_.gainR = 1.0; + awb_.gainG = 1.0; + awb_.gainB = 1.0; + /* * Setting status_.totalExposureValue_ to zero initially tells us * it's not been calculated yet (i.e. Process hasn't yet run). @@ -303,31 +314,49 @@ int AgcChannel::read(const libcamera::YamlObject ¶ms, exposureMode_ = &config_.exposureModes[exposureModeName_]; constraintModeName_ = config_.defaultConstraintMode; constraintMode_ = &config_.constraintModes[constraintModeName_]; - /* Set up the "last shutter/gain" values, in case AGC starts "disabled". */ - status_.shutterTime = config_.defaultExposureTime; + /* Set up the "last exposure time/gain" values, in case AGC starts "disabled". */ + status_.exposureTime = config_.defaultExposureTime; status_.analogueGain = config_.defaultAnalogueGain; return 0; } -void AgcChannel::disableAuto() +void AgcChannel::disableAutoExposure() +{ + fixedExposureTime_ = status_.exposureTime; +} + +void AgcChannel::enableAutoExposure() +{ + fixedExposureTime_ = 0s; +} + +bool AgcChannel::autoExposureEnabled() const +{ + return fixedExposureTime_ == 0s; +} + +void AgcChannel::disableAutoGain() { - fixedShutter_ = status_.shutterTime; fixedAnalogueGain_ = status_.analogueGain; } -void AgcChannel::enableAuto() +void AgcChannel::enableAutoGain() { - fixedShutter_ = 0s; fixedAnalogueGain_ = 0; } +bool AgcChannel::autoGainEnabled() const +{ + return fixedAnalogueGain_ == 0; +} + unsigned int AgcChannel::getConvergenceFrames() const { /* - * If shutter and gain have been explicitly set, there is no + * If exposure time and gain have been explicitly set, there is no * convergence to happen, so no need to drop any frames - return zero. */ - if (fixedShutter_ && fixedAnalogueGain_) + if (fixedExposureTime_ && fixedAnalogueGain_) return 0; else return config_.convergenceFrames; @@ -355,16 +384,16 @@ void AgcChannel::setFlickerPeriod(Duration flickerPeriod) flickerPeriod_ = flickerPeriod; } -void AgcChannel::setMaxShutter(Duration maxShutter) +void AgcChannel::setMaxExposureTime(Duration maxExposureTime) { - maxShutter_ = maxShutter; + maxExposureTime_ = maxExposureTime; } -void AgcChannel::setFixedShutter(Duration fixedShutter) +void AgcChannel::setFixedExposureTime(Duration fixedExposureTime) { - fixedShutter_ = fixedShutter; + fixedExposureTime_ = fixedExposureTime; /* Set this in case someone calls disableAuto() straight after. */ - status_.shutterTime = limitShutter(fixedShutter_); + status_.exposureTime = limitExposureTime(fixedExposureTime_); } void AgcChannel::setFixedAnalogueGain(double fixedAnalogueGain) @@ -404,23 +433,22 @@ void AgcChannel::switchMode(CameraMode const &cameraMode, double lastSensitivity = mode_.sensitivity; mode_ = cameraMode; - Duration fixedShutter = limitShutter(fixedShutter_); - if (fixedShutter && fixedAnalogueGain_) { + Duration fixedExposureTime = limitExposureTime(fixedExposureTime_); + if (fixedExposureTime && fixedAnalogueGain_) { /* We're going to reset the algorithm here with these fixed values. */ - fetchAwbStatus(metadata); double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 }); ASSERT(minColourGain != 0.0); /* This is the equivalent of computeTargetExposure and applyDigitalGain. */ - target_.totalExposureNoDG = fixedShutter_ * fixedAnalogueGain_; + target_.totalExposureNoDG = fixedExposureTime_ * fixedAnalogueGain_; target_.totalExposure = target_.totalExposureNoDG / minColourGain; /* Equivalent of filterExposure. This resets any "history". */ filtered_ = target_; /* Equivalent of divideUpExposure. */ - filtered_.shutter = fixedShutter; + filtered_.exposureTime = fixedExposureTime; filtered_.analogueGain = fixedAnalogueGain_; } else if (status_.totalExposureValue) { /* @@ -442,14 +470,15 @@ void AgcChannel::switchMode(CameraMode const &cameraMode, divideUpExposure(); } else { /* - * We come through here on startup, when at least one of the shutter - * or gain has not been fixed. We must still write those values out so - * that they will be applied immediately. We supply some arbitrary defaults - * for any that weren't set. + * We come through here on startup, when at least one of the + * exposure time or gain has not been fixed. We must still + * write those values out so that they will be applied + * immediately. We supply some arbitrary defaults for any that + * weren't set. */ /* Equivalent of divideUpExposure. */ - filtered_.shutter = fixedShutter ? fixedShutter : config_.defaultExposureTime; + filtered_.exposureTime = fixedExposureTime ? fixedExposureTime : config_.defaultExposureTime; filtered_.analogueGain = fixedAnalogueGain_ ? fixedAnalogueGain_ : config_.defaultAnalogueGain; } @@ -462,6 +491,9 @@ void AgcChannel::prepare(Metadata *imageMetadata) AgcStatus delayedStatus; AgcPrepareStatus prepareStatus; + /* Fetch the AWB status now because AWB also sets it in the prepare method. */ + fetchAwbStatus(imageMetadata); + if (!imageMetadata->get("agc.delayed_status", delayedStatus)) totalExposureValue = delayedStatus.totalExposureValue; @@ -472,7 +504,7 @@ void AgcChannel::prepare(Metadata *imageMetadata) /* Process has run, so we have meaningful values. */ DeviceStatus deviceStatus; if (imageMetadata->get("device.status", deviceStatus) == 0) { - Duration actualExposure = deviceStatus.shutterSpeed * + Duration actualExposure = deviceStatus.exposureTime * deviceStatus.analogueGain; if (actualExposure) { double digitalGain = totalExposureValue / actualExposure; @@ -505,8 +537,6 @@ void AgcChannel::process(StatisticsPtr &stats, DeviceStatus const &deviceStatus, * configuration, that kind of thing. */ housekeepConfig(); - /* Fetch the AWB status immediately, so that we can assume it's there. */ - fetchAwbStatus(imageMetadata); /* Get the current exposure values for the frame that's just arrived. */ fetchCurrentExposure(deviceStatus); /* Compute the total gain we require relative to the current exposure. */ @@ -528,7 +558,7 @@ void AgcChannel::process(StatisticsPtr &stats, DeviceStatus const &deviceStatus, */ bool desaturate = applyDigitalGain(gain, targetY, channelBound); /* - * The last thing is to divide up the exposure value into a shutter time + * The last thing is to divide up the exposure value into a exposure time * and analogue gain, according to the current exposure mode. */ divideUpExposure(); @@ -544,7 +574,7 @@ bool AgcChannel::updateLockStatus(DeviceStatus const &deviceStatus) const double resetMargin = 1.5; /* Add 200us to the exposure time error to allow for line quantisation. */ - Duration exposureError = lastDeviceStatus_.shutterSpeed * errorFactor + 200us; + Duration exposureError = lastDeviceStatus_.exposureTime * errorFactor + 200us; double gainError = lastDeviceStatus_.analogueGain * errorFactor; Duration targetError = lastTargetExposure_ * errorFactor; @@ -553,15 +583,15 @@ bool AgcChannel::updateLockStatus(DeviceStatus const &deviceStatus) * the values we keep requesting may be unachievable. For this reason * we only insist that we're close to values in the past few frames. */ - if (deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed - exposureError && - deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed + exposureError && + if (deviceStatus.exposureTime > lastDeviceStatus_.exposureTime - exposureError && + deviceStatus.exposureTime < lastDeviceStatus_.exposureTime + exposureError && deviceStatus.analogueGain > lastDeviceStatus_.analogueGain - gainError && deviceStatus.analogueGain < lastDeviceStatus_.analogueGain + gainError && status_.targetExposureValue > lastTargetExposure_ - targetError && status_.targetExposureValue < lastTargetExposure_ + targetError) lockCount_ = std::min(lockCount_ + 1, maxLockCount); - else if (deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed - resetMargin * exposureError || - deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed + resetMargin * exposureError || + else if (deviceStatus.exposureTime < lastDeviceStatus_.exposureTime - resetMargin * exposureError || + deviceStatus.exposureTime > lastDeviceStatus_.exposureTime + resetMargin * exposureError || deviceStatus.analogueGain < lastDeviceStatus_.analogueGain - resetMargin * gainError || deviceStatus.analogueGain > lastDeviceStatus_.analogueGain + resetMargin * gainError || status_.targetExposureValue < lastTargetExposure_ - resetMargin * targetError || @@ -579,11 +609,11 @@ void AgcChannel::housekeepConfig() { /* First fetch all the up-to-date settings, so no one else has to do it. */ status_.ev = ev_; - status_.fixedShutter = limitShutter(fixedShutter_); + status_.fixedExposureTime = limitExposureTime(fixedExposureTime_); status_.fixedAnalogueGain = fixedAnalogueGain_; status_.flickerPeriod = flickerPeriod_; - LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedShutter " - << status_.fixedShutter << " fixedAnalogueGain " + LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedExposureTime " + << status_.fixedExposureTime << " fixedAnalogueGain " << status_.fixedAnalogueGain; /* * Make sure the "mode" pointers point to the up-to-date things, if @@ -627,17 +657,14 @@ void AgcChannel::housekeepConfig() void AgcChannel::fetchCurrentExposure(DeviceStatus const &deviceStatus) { - current_.shutter = deviceStatus.shutterSpeed; + current_.exposureTime = deviceStatus.exposureTime; current_.analogueGain = deviceStatus.analogueGain; current_.totalExposure = 0s; /* this value is unused */ - current_.totalExposureNoDG = current_.shutter * current_.analogueGain; + current_.totalExposureNoDG = current_.exposureTime * current_.analogueGain; } void AgcChannel::fetchAwbStatus(Metadata *imageMetadata) { - awb_.gainR = 1.0; /* in case not found in metadata */ - awb_.gainG = 1.0; - awb_.gainB = 1.0; if (imageMetadata->get("awb.status", awb_) != 0) LOG(RPiAgc, Debug) << "No AWB status found"; } @@ -674,12 +701,13 @@ static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb, * Note that the weights are applied by the IPA to the statistics directly, * before they are given to us here. */ - double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0; + RGB<double> sum{ 0.0 }; + double pixelSum = 0; for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) { auto ®ion = stats->agcRegions.get(i); - rSum += std::min<double>(region.val.rSum * gain, (maxVal - 1) * region.counted); - gSum += std::min<double>(region.val.gSum * gain, (maxVal - 1) * region.counted); - bSum += std::min<double>(region.val.bSum * gain, (maxVal - 1) * region.counted); + sum.r() += std::min<double>(region.val.rSum * gain, (maxVal - 1) * region.counted); + sum.g() += std::min<double>(region.val.gSum * gain, (maxVal - 1) * region.counted); + sum.b() += std::min<double>(region.val.bSum * gain, (maxVal - 1) * region.counted); pixelSum += region.counted; } if (pixelSum == 0.0) { @@ -687,14 +715,11 @@ static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb, return 0; } - double ySum; /* Factor in the AWB correction if needed. */ - if (stats->agcStatsPos == Statistics::AgcStatsPos::PreWb) { - ySum = rSum * awb.gainR * .299 + - gSum * awb.gainG * .587 + - bSum * awb.gainB * .114; - } else - ySum = rSum * .299 + gSum * .587 + bSum * .114; + if (stats->agcStatsPos == Statistics::AgcStatsPos::PreWb) + sum *= RGB<double>{ { awb.gainR, awb.gainG, awb.gainB } }; + + double ySum = ipa::rec601LuminanceFromRGB(sum); return ySum / pixelSum / (1 << 16); } @@ -712,7 +737,7 @@ static constexpr double EvGainYTargetLimit = 0.9; static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux, double evGain, double &targetY) { - targetY = c.yTarget.eval(c.yTarget.domain().clip(lux)); + targetY = c.yTarget.eval(c.yTarget.domain().clamp(lux)); targetY = std::min(EvGainYTargetLimit, targetY * evGain); double iqm = h.interQuantileMean(c.qLo, c.qHi); return (targetY * h.bins()) / iqm; @@ -731,7 +756,7 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata, * The initial gain and target_Y come from some of the regions. After * that we consider the histogram constraints. */ - targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux)); + targetY = config_.yTarget.eval(config_.yTarget.domain().clamp(lux.lux)); targetY = std::min(EvGainYTargetLimit, targetY * evGain); /* @@ -771,17 +796,17 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata, void AgcChannel::computeTargetExposure(double gain) { - if (status_.fixedShutter && status_.fixedAnalogueGain) { + if (status_.fixedExposureTime && status_.fixedAnalogueGain) { /* - * When ag and shutter are both fixed, we need to drive the - * total exposure so that we end up with a digital gain of at least - * 1/minColourGain. Otherwise we'd desaturate channels causing - * white to go cyan or magenta. + * When analogue gain and exposure time are both fixed, we need + * to drive the total exposure so that we end up with a digital + * gain of at least 1/minColourGain. Otherwise we'd desaturate + * channels causing white to go cyan or magenta. */ double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 }); ASSERT(minColourGain != 0.0); target_.totalExposure = - status_.fixedShutter * status_.fixedAnalogueGain / minColourGain; + status_.fixedExposureTime * status_.fixedAnalogueGain / minColourGain; } else { /* * The statistics reflect the image without digital gain, so the final @@ -789,12 +814,12 @@ void AgcChannel::computeTargetExposure(double gain) */ target_.totalExposure = current_.totalExposureNoDG * gain; /* The final target exposure is also limited to what the exposure mode allows. */ - Duration maxShutter = status_.fixedShutter - ? status_.fixedShutter - : exposureMode_->shutter.back(); - maxShutter = limitShutter(maxShutter); + Duration maxExposureTime = status_.fixedExposureTime + ? status_.fixedExposureTime + : exposureMode_->exposureTime.back(); + maxExposureTime = limitExposureTime(maxExposureTime); Duration maxTotalExposure = - maxShutter * + maxExposureTime * (status_.fixedAnalogueGain != 0.0 ? status_.fixedAnalogueGain : exposureMode_->gain.back()); @@ -860,8 +885,10 @@ bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound * quickly (and we then approach the correct value more quickly from * below). */ - bool desaturate = !channelBound && - targetY > config_.fastReduceThreshold && gain < sqrt(targetY); + bool desaturate = false; + if (config_.desaturate) + desaturate = !channelBound && + targetY > config_.fastReduceThreshold && gain < sqrt(targetY); if (desaturate) dg /= config_.fastReduceThreshold; LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate; @@ -876,12 +903,16 @@ void AgcChannel::filterExposure() double stableRegion = config_.stableRegion; /* - * AGC adapts instantly if both shutter and gain are directly specified - * or we're in the startup phase. + * AGC adapts instantly if both exposure time and gain are directly + * specified or we're in the startup phase. Also disable the stable + * region, because we want to reflect any user exposure/gain updates, + * however small. */ - if ((status_.fixedShutter && status_.fixedAnalogueGain) || - frameCount_ <= config_.startupFrames) + if ((status_.fixedExposureTime && status_.fixedAnalogueGain) || + frameCount_ <= config_.startupFrames) { speed = 1.0; + stableRegion = 0.0; + } if (!filtered_.totalExposure) { filtered_.totalExposure = target_.totalExposure; } else if (filtered_.totalExposure * (1.0 - stableRegion) < target_.totalExposure && @@ -905,34 +936,34 @@ void AgcChannel::filterExposure() void AgcChannel::divideUpExposure() { /* - * Sending the fixed shutter/gain cases through the same code may seem - * unnecessary, but it will make more sense when extend this to cover - * variable aperture. + * Sending the fixed exposure time/gain cases through the same code may + * seem unnecessary, but it will make more sense when extend this to + * cover variable aperture. */ Duration exposureValue = filtered_.totalExposureNoDG; - Duration shutterTime; + Duration exposureTime; double analogueGain; - shutterTime = status_.fixedShutter ? status_.fixedShutter - : exposureMode_->shutter[0]; - shutterTime = limitShutter(shutterTime); + exposureTime = status_.fixedExposureTime ? status_.fixedExposureTime + : exposureMode_->exposureTime[0]; + exposureTime = limitExposureTime(exposureTime); analogueGain = status_.fixedAnalogueGain != 0.0 ? status_.fixedAnalogueGain : exposureMode_->gain[0]; analogueGain = limitGain(analogueGain); - if (shutterTime * analogueGain < exposureValue) { + if (exposureTime * analogueGain < exposureValue) { for (unsigned int stage = 1; stage < exposureMode_->gain.size(); stage++) { - if (!status_.fixedShutter) { - Duration stageShutter = - limitShutter(exposureMode_->shutter[stage]); - if (stageShutter * analogueGain >= exposureValue) { - shutterTime = exposureValue / analogueGain; + if (!status_.fixedExposureTime) { + Duration stageExposureTime = + limitExposureTime(exposureMode_->exposureTime[stage]); + if (stageExposureTime * analogueGain >= exposureValue) { + exposureTime = exposureValue / analogueGain; break; } - shutterTime = stageShutter; + exposureTime = stageExposureTime; } if (status_.fixedAnalogueGain == 0.0) { - if (exposureMode_->gain[stage] * shutterTime >= exposureValue) { - analogueGain = exposureValue / shutterTime; + if (exposureMode_->gain[stage] * exposureTime >= exposureValue) { + analogueGain = exposureValue / exposureTime; break; } analogueGain = exposureMode_->gain[stage]; @@ -940,18 +971,19 @@ void AgcChannel::divideUpExposure() } } } - LOG(RPiAgc, Debug) << "Divided up shutter and gain are " << shutterTime << " and " - << analogueGain; + LOG(RPiAgc, Debug) + << "Divided up exposure time and gain are " << exposureTime + << " and " << analogueGain; /* - * Finally adjust shutter time for flicker avoidance (require both - * shutter and gain not to be fixed). + * Finally adjust exposure time for flicker avoidance (require both + * exposure time and gain not to be fixed). */ - if (!status_.fixedShutter && !status_.fixedAnalogueGain && + if (!status_.fixedExposureTime && !status_.fixedAnalogueGain && status_.flickerPeriod) { - int flickerPeriods = shutterTime / status_.flickerPeriod; + int flickerPeriods = exposureTime / status_.flickerPeriod; if (flickerPeriods) { - Duration newShutterTime = flickerPeriods * status_.flickerPeriod; - analogueGain *= shutterTime / newShutterTime; + Duration newExposureTime = flickerPeriods * status_.flickerPeriod; + analogueGain *= exposureTime / newExposureTime; /* * We should still not allow the ag to go over the * largest value in the exposure mode. Note that this @@ -960,20 +992,20 @@ void AgcChannel::divideUpExposure() */ analogueGain = std::min(analogueGain, exposureMode_->gain.back()); analogueGain = limitGain(analogueGain); - shutterTime = newShutterTime; + exposureTime = newExposureTime; } - LOG(RPiAgc, Debug) << "After flicker avoidance, shutter " - << shutterTime << " gain " << analogueGain; + LOG(RPiAgc, Debug) << "After flicker avoidance, exposure time " + << exposureTime << " gain " << analogueGain; } - filtered_.shutter = shutterTime; + filtered_.exposureTime = exposureTime; filtered_.analogueGain = analogueGain; } void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate) { status_.totalExposureValue = filtered_.totalExposure; - status_.targetExposureValue = desaturate ? 0s : target_.totalExposureNoDG; - status_.shutterTime = filtered_.shutter; + status_.targetExposureValue = desaturate ? 0s : target_.totalExposure; + status_.exposureTime = filtered_.exposureTime; status_.analogueGain = filtered_.analogueGain; /* * Write to metadata as well, in case anyone wants to update the camera @@ -982,32 +1014,32 @@ void AgcChannel::writeAndFinish(Metadata *imageMetadata, bool desaturate) imageMetadata->set("agc.status", status_); LOG(RPiAgc, Debug) << "Output written, total exposure requested is " << filtered_.totalExposure; - LOG(RPiAgc, Debug) << "Camera exposure update: shutter time " << filtered_.shutter + LOG(RPiAgc, Debug) << "Camera exposure update: exposure time " << filtered_.exposureTime << " analogue gain " << filtered_.analogueGain; } -Duration AgcChannel::limitShutter(Duration shutter) +Duration AgcChannel::limitExposureTime(Duration exposureTime) { /* - * shutter == 0 is a special case for fixed shutter values, and must pass - * through unchanged + * exposureTime == 0 is a special case for fixed exposure time values, + * and must pass through unchanged. */ - if (!shutter) - return shutter; + if (!exposureTime) + return exposureTime; - shutter = std::clamp(shutter, mode_.minShutter, maxShutter_); - return shutter; + exposureTime = std::clamp(exposureTime, mode_.minExposureTime, maxExposureTime_); + return exposureTime; } double AgcChannel::limitGain(double gain) const { /* - * Only limit the lower bounds of the gain value to what the sensor limits. - * The upper bound on analogue gain will be made up with additional digital - * gain applied by the ISP. + * Only limit the lower bounds of the gain value to what the sensor + * limits. The upper bound on analogue gain will be made up with + * additional digital gain applied by the ISP. * - * gain == 0.0 is a special case for fixed shutter values, and must pass - * through unchanged + * gain == 0.0 is a special case for fixed exposure time values, and + * must pass through unchanged. */ if (!gain) return gain; diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h index c1808422..fa697e6f 100644 --- a/src/ipa/rpi/controller/rpi/agc_channel.h +++ b/src/ipa/rpi/controller/rpi/agc_channel.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Raspberry Pi Ltd * - * agc_channel.h - AGC/AEC control algorithm + * AGC/AEC control algorithm */ #pragma once @@ -12,10 +12,11 @@ #include <libcamera/base/utils.h> +#include <libipa/pwl.h> + #include "../agc_status.h" #include "../awb_status.h" #include "../controller.h" -#include "../pwl.h" /* This is our implementation of AGC. */ @@ -29,7 +30,7 @@ struct AgcMeteringMode { }; struct AgcExposureMode { - std::vector<libcamera::utils::Duration> shutter; + std::vector<libcamera::utils::Duration> exposureTime; std::vector<double> gain; int read(const libcamera::YamlObject ¶ms); }; @@ -40,7 +41,7 @@ struct AgcConstraint { Bound bound; double qLo; double qHi; - Pwl yTarget; + libcamera::ipa::Pwl yTarget; int read(const libcamera::YamlObject ¶ms); }; @@ -61,7 +62,7 @@ struct AgcConfig { std::map<std::string, AgcExposureMode> exposureModes; std::map<std::string, AgcConstraintMode> constraintModes; std::vector<AgcChannelConstraint> channelConstraints; - Pwl yTarget; + libcamera::ipa::Pwl yTarget; double speed; uint16_t startupFrames; unsigned int convergenceFrames; @@ -76,6 +77,7 @@ struct AgcConfig { libcamera::utils::Duration defaultExposureTime; double defaultAnalogueGain; double stableRegion; + bool desaturate; }; class AgcChannel @@ -88,14 +90,18 @@ public: std::vector<double> const &getWeights() const; void setEv(double ev); void setFlickerPeriod(libcamera::utils::Duration flickerPeriod); - void setMaxShutter(libcamera::utils::Duration maxShutter); - void setFixedShutter(libcamera::utils::Duration fixedShutter); + void setMaxExposureTime(libcamera::utils::Duration maxExposureTime); + void setFixedExposureTime(libcamera::utils::Duration fixedExposureTime); void setFixedAnalogueGain(double fixedAnalogueGain); void setMeteringMode(std::string const &meteringModeName); void setExposureMode(std::string const &exposureModeName); void setConstraintMode(std::string const &contraintModeName); - void enableAuto(); - void disableAuto(); + void enableAutoExposure(); + void disableAutoExposure(); + bool autoExposureEnabled() const; + void enableAutoGain(); + void disableAutoGain(); + bool autoGainEnabled() const; void switchMode(CameraMode const &cameraMode, Metadata *metadata); void prepare(Metadata *imageMetadata); void process(StatisticsPtr &stats, DeviceStatus const &deviceStatus, Metadata *imageMetadata, @@ -115,7 +121,7 @@ private: bool applyDigitalGain(double gain, double targetY, bool channelBound); void divideUpExposure(); void writeAndFinish(Metadata *imageMetadata, bool desaturate); - libcamera::utils::Duration limitShutter(libcamera::utils::Duration shutter); + libcamera::utils::Duration limitExposureTime(libcamera::utils::Duration exposureTime); double limitGain(double gain) const; AgcMeteringMode *meteringMode_; AgcExposureMode *exposureMode_; @@ -126,7 +132,7 @@ private: struct ExposureValues { ExposureValues(); - libcamera::utils::Duration shutter; + libcamera::utils::Duration exposureTime; double analogueGain; libcamera::utils::Duration totalExposure; libcamera::utils::Duration totalExposureNoDG; /* without digital gain */ @@ -144,8 +150,8 @@ private: std::string constraintModeName_; double ev_; libcamera::utils::Duration flickerPeriod_; - libcamera::utils::Duration maxShutter_; - libcamera::utils::Duration fixedShutter_; + libcamera::utils::Duration maxExposureTime_; + libcamera::utils::Duration fixedExposureTime_; double fixedAnalogueGain_; }; diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp index b7413611..21edb819 100644 --- a/src/ipa/rpi/controller/rpi/alsc.cpp +++ b/src/ipa/rpi/controller/rpi/alsc.cpp @@ -2,13 +2,14 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * alsc.cpp - ALSC (auto lens shading correction) control algorithm + * ALSC (auto lens shading correction) control algorithm */ #include <algorithm> +#include <cmath> #include <functional> -#include <math.h> #include <numeric> +#include <vector> #include <libcamera/base/log.h> #include <libcamera/base/span.h> @@ -251,12 +252,12 @@ static bool compareModes(CameraMode const &cm0, CameraMode const &cm1) */ if (cm0.transform != cm1.transform) return true; - int leftDiff = abs(cm0.cropX - cm1.cropX); - int topDiff = abs(cm0.cropY - cm1.cropY); - int rightDiff = fabs(cm0.cropX + cm0.scaleX * cm0.width - - cm1.cropX - cm1.scaleX * cm1.width); - int bottomDiff = fabs(cm0.cropY + cm0.scaleY * cm0.height - - cm1.cropY - cm1.scaleY * cm1.height); + int leftDiff = std::abs(cm0.cropX - cm1.cropX); + int topDiff = std::abs(cm0.cropY - cm1.cropY); + int rightDiff = std::abs(cm0.cropX + cm0.scaleX * cm0.width - + cm1.cropX - cm1.scaleX * cm1.width); + int bottomDiff = std::abs(cm0.cropY + cm0.scaleY * cm0.height - + cm1.cropY - cm1.scaleY * cm1.height); /* * These thresholds are a rather arbitrary amount chosen to trigger * when carrying on with the previously calculated tables might be @@ -496,8 +497,9 @@ void resampleCalTable(const Array2D<double> &calTableIn, * Precalculate and cache the x sampling locations and phases to save * recomputing them on every row. */ - int xLo[X], xHi[X]; - double xf[X]; + std::vector<int> xLo(X); + std::vector<int> xHi(X); + std::vector<double> xf(X); double scaleX = cameraMode.sensorWidth / (cameraMode.width * cameraMode.scaleX); double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth; @@ -548,7 +550,9 @@ static void calculateCrCb(const RgbyRegions &awbRegion, Array2D<double> &cr, for (unsigned int i = 0; i < cr.size(); i++) { auto s = awbRegion.get(i); - if (s.counted <= minCount || s.val.gSum / s.counted <= minG) { + /* Do not return unreliable, or zero, colour ratio statistics. */ + if (s.counted <= minCount || s.val.gSum / s.counted <= minG || + s.val.rSum / s.counted <= minG || s.val.bSum / s.counted <= minG) { cr[i] = cb[i] = InsufficientData; continue; } @@ -728,7 +732,7 @@ static double gaussSeidel2Sor(const SparseArray<double> &M, double omega, double maxDiff = 0; for (i = 0; i < XY; i++) { lambda[i] = oldLambda[i] + (lambda[i] - oldLambda[i]) * omega; - if (fabs(lambda[i] - oldLambda[i]) > fabs(maxDiff)) + if (std::abs(lambda[i] - oldLambda[i]) > std::abs(maxDiff)) maxDiff = lambda[i] - oldLambda[i]; } return maxDiff; @@ -760,7 +764,7 @@ static void runMatrixIterations(const Array2D<double> &C, constructM(C, W, M); double lastMaxDiff = std::numeric_limits<double>::max(); for (unsigned int i = 0; i < nIter; i++) { - double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound)); + double maxDiff = std::abs(gaussSeidel2Sor(M, omega, lambda, lambdaBound)); if (maxDiff < threshold) { LOG(RPiAlsc, Debug) << "Stop after " << i + 1 << " iterations"; diff --git a/src/ipa/rpi/controller/rpi/alsc.h b/src/ipa/rpi/controller/rpi/alsc.h index 0b6d9478..31087982 100644 --- a/src/ipa/rpi/controller/rpi/alsc.h +++ b/src/ipa/rpi/controller/rpi/alsc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * alsc.h - ALSC (auto lens shading correction) control algorithm + * ALSC (auto lens shading correction) control algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp index 5ae0c2fa..365b595f 100644 --- a/src/ipa/rpi/controller/rpi/awb.cpp +++ b/src/ipa/rpi/controller/rpi/awb.cpp @@ -2,10 +2,11 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * awb.cpp - AWB control algorithm + * AWB control algorithm */ #include <assert.h> +#include <cmath> #include <functional> #include <libcamera/base/log.h> @@ -20,6 +21,8 @@ using namespace libcamera; LOG_DEFINE_CATEGORY(RPiAwb) +constexpr double kDefaultCT = 4500.0; + #define NAME "rpi.awb" /* @@ -49,10 +52,11 @@ int AwbPrior::read(const libcamera::YamlObject ¶ms) return -EINVAL; lux = *value; - return prior.read(params["prior"]); + prior = params["prior"].get<ipa::Pwl>(ipa::Pwl{}); + return prior.empty() ? -EINVAL : 0; } -static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject ¶ms) +static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject ¶ms) { if (params.size() % 3) { LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry"; @@ -103,8 +107,8 @@ int AwbConfig::read(const libcamera::YamlObject ¶ms) if (ret) return ret; /* We will want the inverse functions of these too. */ - ctRInverse = ctR.inverse(); - ctBInverse = ctB.inverse(); + ctRInverse = ctR.inverse().first; + ctBInverse = ctB.inverse().first; } if (params.contains("priors")) { @@ -121,7 +125,7 @@ int AwbConfig::read(const libcamera::YamlObject ¶ms) } if (priors.empty()) { LOG(RPiAwb, Error) << "AwbConfig: no AWB priors configured"; - return ret; + return -EINVAL; } } if (params.contains("modes")) { @@ -161,11 +165,18 @@ int AwbConfig::read(const libcamera::YamlObject ¶ms) bayes = false; } } - fast = params[fast].get<int>(bayes); /* default to fast for Bayesian, otherwise slow */ whitepointR = params["whitepoint_r"].get<double>(0.0); whitepointB = params["whitepoint_b"].get<double>(0.0); if (bayes == false) sensitivityR = sensitivityB = 1.0; /* nor do sensitivities make any sense */ + /* + * The biasProportion parameter adds a small proportion of the counted + * pixles to a region biased to the biasCT colour temperature. + * + * A typical value for biasProportion would be between 0.05 to 0.1. + */ + biasProportion = params["bias_proportion"].get<double>(0.0); + biasCT = params["bias_ct"].get<double>(kDefaultCT); return 0; } @@ -207,19 +218,25 @@ void Awb::initialise() * them. */ if (!config_.ctR.empty() && !config_.ctB.empty()) { - syncResults_.temperatureK = config_.ctR.domain().clip(4000); + syncResults_.temperatureK = config_.ctR.domain().clamp(4000); syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK); syncResults_.gainG = 1.0; syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK); } else { /* random values just to stop the world blowing up */ - syncResults_.temperatureK = 4500; + syncResults_.temperatureK = kDefaultCT; syncResults_.gainR = syncResults_.gainG = syncResults_.gainB = 1.0; } prevSyncResults_ = syncResults_; asyncResults_ = syncResults_; } +void Awb::initialValues(double &gainR, double &gainB) +{ + gainR = syncResults_.gainR; + gainB = syncResults_.gainB; +} + void Awb::disableAuto() { /* Freeze the most recent values, and treat them as manual gains */ @@ -267,14 +284,32 @@ void Awb::setManualGains(double manualR, double manualB) syncResults_.gainB = prevSyncResults_.gainB = manualB_; if (config_.bayes) { /* Also estimate the best corresponding colour temperature from the curves. */ - double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_)); - double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_)); + double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clamp(1 / manualR_)); + double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clamp(1 / manualB_)); prevSyncResults_.temperatureK = (ctR + ctB) / 2; syncResults_.temperatureK = prevSyncResults_.temperatureK; } } } +void Awb::setColourTemperature(double temperatureK) +{ + if (!config_.bayes) { + LOG(RPiAwb, Warning) << "AWB uncalibrated - cannot set colour temperature"; + return; + } + + temperatureK = config_.ctR.domain().clamp(temperatureK); + manualR_ = 1 / config_.ctR.eval(temperatureK); + manualB_ = 1 / config_.ctB.eval(temperatureK); + + syncResults_.temperatureK = temperatureK; + syncResults_.gainR = manualR_; + syncResults_.gainG = 1.0; + syncResults_.gainB = manualB_; + prevSyncResults_ = syncResults_; +} + void Awb::switchMode([[maybe_unused]] CameraMode const &cameraMode, Metadata *metadata) { @@ -400,7 +435,8 @@ void Awb::asyncFunc() static void generateStats(std::vector<Awb::RGB> &zones, StatisticsPtr &stats, double minPixels, - double minG, Metadata &globalMetadata) + double minG, Metadata &globalMetadata, + double biasProportion, double biasCtR, double biasCtB) { std::scoped_lock<RPiController::Metadata> l(globalMetadata); @@ -413,6 +449,14 @@ static void generateStats(std::vector<Awb::RGB> &zones, continue; zone.R = region.val.rSum / region.counted; zone.B = region.val.bSum / region.counted; + /* + * Add some bias samples to allow the search to tend to a + * bias CT in failure cases. + */ + const unsigned int proportion = biasProportion * region.counted; + zone.R += proportion * biasCtR; + zone.B += proportion * biasCtB; + zone.G += proportion * 1.0; /* Factor in the ALSC applied colour shading correction if required. */ const AlscStatus *alscStatus = globalMetadata.getLocked<AlscStatus>("alsc.status"); if (stats->colourStatsPos == Statistics::ColourStatsPos::PreLsc && alscStatus) { @@ -432,8 +476,11 @@ void Awb::prepareStats() * LSC has already been applied to the stats in this pipeline, so stop * any LSC compensation. We also ignore config_.fast in this version. */ + const double biasCtR = config_.bayes ? config_.ctR.eval(config_.biasCT) : 0; + const double biasCtB = config_.bayes ? config_.ctB.eval(config_.biasCT) : 0; generateStats(zones_, statistics_, config_.minPixels, - config_.minG, getGlobalMetadata()); + config_.minG, getGlobalMetadata(), + config_.biasProportion, biasCtR, biasCtB); /* * apply sensitivities, so values appear to come from our "canonical" * sensor. @@ -462,7 +509,7 @@ double Awb::computeDelta2Sum(double gainR, double gainB) return delta2Sum; } -Pwl Awb::interpolatePrior() +ipa::Pwl Awb::interpolatePrior() { /* * Interpolate the prior log likelihood function for our current lux @@ -479,7 +526,7 @@ Pwl Awb::interpolatePrior() idx++; double lux0 = config_.priors[idx].lux, lux1 = config_.priors[idx + 1].lux; - return Pwl::combine(config_.priors[idx].prior, + return ipa::Pwl::combine(config_.priors[idx].prior, config_.priors[idx + 1].prior, [&](double /*x*/, double y0, double y1) { return y0 + (y1 - y0) * @@ -488,26 +535,26 @@ Pwl Awb::interpolatePrior() } } -static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b, - Pwl::Point const &c) +static double interpolateQuadatric(ipa::Pwl::Point const &a, ipa::Pwl::Point const &b, + ipa::Pwl::Point const &c) { /* * Given 3 points on a curve, find the extremum of the function in that * interval by fitting a quadratic. */ const double eps = 1e-3; - Pwl::Point ca = c - a, ba = b - a; - double denominator = 2 * (ba.y * ca.x - ca.y * ba.x); - if (abs(denominator) > eps) { - double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x; - double result = numerator / denominator + a.x; - return std::max(a.x, std::min(c.x, result)); + ipa::Pwl::Point ca = c - a, ba = b - a; + double denominator = 2 * (ba.y() * ca.x() - ca.y() * ba.x()); + if (std::abs(denominator) > eps) { + double numerator = ba.y() * ca.x() * ca.x() - ca.y() * ba.x() * ba.x(); + double result = numerator / denominator + a.x(); + return std::max(a.x(), std::min(c.x(), result)); } /* has degenerated to straight line segment */ - return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x); + return a.y() < c.y() - eps ? a.x() : (c.y() < a.y() - eps ? c.x() : b.x()); } -double Awb::coarseSearch(Pwl const &prior) +double Awb::coarseSearch(ipa::Pwl const &prior) { points_.clear(); /* assume doesn't deallocate memory */ size_t bestPoint = 0; @@ -519,22 +566,22 @@ double Awb::coarseSearch(Pwl const &prior) double b = config_.ctB.eval(t, &spanB); double gainR = 1 / r, gainB = 1 / b; double delta2Sum = computeDelta2Sum(gainR, gainB); - double priorLogLikelihood = prior.eval(prior.domain().clip(t)); + double priorLogLikelihood = prior.eval(prior.domain().clamp(t)); double finalLogLikelihood = delta2Sum - priorLogLikelihood; LOG(RPiAwb, Debug) << "t: " << t << " gain R " << gainR << " gain B " << gainB << " delta2_sum " << delta2Sum << " prior " << priorLogLikelihood << " final " << finalLogLikelihood; - points_.push_back(Pwl::Point(t, finalLogLikelihood)); - if (points_.back().y < points_[bestPoint].y) + points_.push_back(ipa::Pwl::Point({ t, finalLogLikelihood })); + if (points_.back().y() < points_[bestPoint].y()) bestPoint = points_.size() - 1; if (t == mode_->ctHi) break; /* for even steps along the r/b curve scale them by the current t */ t = std::min(t + t / 10 * config_.coarseStep, mode_->ctHi); } - t = points_[bestPoint].x; + t = points_[bestPoint].x(); LOG(RPiAwb, Debug) << "Coarse search found CT " << t; /* * We have the best point of the search, but refine it with a quadratic @@ -553,7 +600,7 @@ double Awb::coarseSearch(Pwl const &prior) return t; } -void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) +void Awb::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior) { int spanR = -1, spanB = -1; config_.ctR.eval(t, &spanR); @@ -564,14 +611,14 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) config_.ctR.eval(t - nsteps * step, &spanR); double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) - config_.ctB.eval(t - nsteps * step, &spanB); - Pwl::Point transverse(bDiff, -rDiff); - if (transverse.len2() < 1e-6) + ipa::Pwl::Point transverse({ bDiff, -rDiff }); + if (transverse.length2() < 1e-6) return; /* * unit vector orthogonal to the b vs. r function (pointing outwards * with r and b increasing) */ - transverse = transverse / transverse.len(); + transverse = transverse / transverse.length(); double bestLogLikelihood = 0, bestT = 0, bestR = 0, bestB = 0; double transverseRange = config_.transverseNeg + config_.transversePos; const int maxNumDeltas = 12; @@ -586,26 +633,26 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) for (int i = -nsteps; i <= nsteps; i++) { double tTest = t + i * step; double priorLogLikelihood = - prior.eval(prior.domain().clip(tTest)); + prior.eval(prior.domain().clamp(tTest)); double rCurve = config_.ctR.eval(tTest, &spanR); double bCurve = config_.ctB.eval(tTest, &spanB); /* x will be distance off the curve, y the log likelihood there */ - Pwl::Point points[maxNumDeltas]; + ipa::Pwl::Point points[maxNumDeltas]; int bestPoint = 0; /* Take some measurements transversely *off* the CT curve. */ for (int j = 0; j < numDeltas; j++) { - points[j].x = -config_.transverseNeg + - (transverseRange * j) / (numDeltas - 1); - Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) + - transverse * points[j].x; - double rTest = rbTest.x, bTest = rbTest.y; + points[j][0] = -config_.transverseNeg + + (transverseRange * j) / (numDeltas - 1); + ipa::Pwl::Point rbTest = ipa::Pwl::Point({ rCurve, bCurve }) + + transverse * points[j].x(); + double rTest = rbTest.x(), bTest = rbTest.y(); double gainR = 1 / rTest, gainB = 1 / bTest; double delta2Sum = computeDelta2Sum(gainR, gainB); - points[j].y = delta2Sum - priorLogLikelihood; + points[j][1] = delta2Sum - priorLogLikelihood; LOG(RPiAwb, Debug) << "At t " << tTest << " r " << rTest << " b " - << bTest << ": " << points[j].y; - if (points[j].y < points[bestPoint].y) + << bTest << ": " << points[j].y(); + if (points[j].y() < points[bestPoint].y()) bestPoint = j; } /* @@ -613,11 +660,11 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior) * now let's do a quadratic interpolation for the best result. */ bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2)); - Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) + - transverse * interpolateQuadatric(points[bestPoint - 1], - points[bestPoint], - points[bestPoint + 1]); - double rTest = rbTest.x, bTest = rbTest.y; + ipa::Pwl::Point rbTest = ipa::Pwl::Point({ rCurve, bCurve }) + + transverse * interpolateQuadatric(points[bestPoint - 1], + points[bestPoint], + points[bestPoint + 1]); + double rTest = rbTest.x(), bTest = rbTest.y(); double gainR = 1 / rTest, gainB = 1 / bTest; double delta2Sum = computeDelta2Sum(gainR, gainB); double finalLogLikelihood = delta2Sum - priorLogLikelihood; @@ -647,7 +694,7 @@ void Awb::awbBayes() * Get the current prior, and scale according to how many zones are * valid... not entirely sure about this. */ - Pwl prior = interpolatePrior(); + ipa::Pwl prior = interpolatePrior(); prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions()); prior.map([](double x, double y) { LOG(RPiAwb, Debug) << "(" << x << "," << y << ")"; @@ -709,7 +756,11 @@ void Awb::awbGrey() sumR += *ri, sumB += *bi; double gainR = sumR.G / (sumR.R + 1), gainB = sumB.G / (sumB.B + 1); - asyncResults_.temperatureK = 4500; /* don't know what it is */ + /* + * The grey world model can't estimate the colour temperature, use a + * default value. + */ + asyncResults_.temperatureK = kDefaultCT; asyncResults_.gainR = gainR; asyncResults_.gainG = 1.0; asyncResults_.gainB = gainB; diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h index e7d49cd8..2fb91254 100644 --- a/src/ipa/rpi/controller/rpi/awb.h +++ b/src/ipa/rpi/controller/rpi/awb.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * awb.h - AWB control algorithm + * AWB control algorithm */ #pragma once @@ -10,11 +10,14 @@ #include <condition_variable> #include <thread> +#include <libcamera/geometry.h> + #include "../awb_algorithm.h" -#include "../pwl.h" #include "../awb_status.h" #include "../statistics.h" +#include "libipa/pwl.h" + namespace RPiController { /* Control algorithm to perform AWB calculations. */ @@ -28,7 +31,7 @@ struct AwbMode { struct AwbPrior { int read(const libcamera::YamlObject ¶ms); double lux; /* lux level */ - Pwl prior; /* maps CT to prior log likelihood for this lux level */ + libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */ }; struct AwbConfig { @@ -40,11 +43,10 @@ struct AwbConfig { uint16_t startupFrames; unsigned int convergenceFrames; /* approx number of frames to converge */ double speed; /* IIR filter speed applied to algorithm results */ - bool fast; /* "fast" mode uses a 16x16 rather than 32x32 grid */ - Pwl ctR; /* function maps CT to r (= R/G) */ - Pwl ctB; /* function maps CT to b (= B/G) */ - Pwl ctRInverse; /* inverse of ctR */ - Pwl ctBInverse; /* inverse of ctB */ + libcamera::ipa::Pwl ctR; /* function maps CT to r (= R/G) */ + libcamera::ipa::Pwl ctB; /* function maps CT to b (= B/G) */ + libcamera::ipa::Pwl ctRInverse; /* inverse of ctR */ + libcamera::ipa::Pwl ctBInverse; /* inverse of ctB */ /* table of illuminant priors at different lux levels */ std::vector<AwbPrior> priors; /* AWB "modes" (determines the search range) */ @@ -84,6 +86,10 @@ struct AwbConfig { double whitepointR; double whitepointB; bool bayes; /* use Bayesian algorithm */ + /* proportion of counted samples to add for the search bias */ + double biasProportion; + /* CT target for the search bias */ + double biasCT; }; class Awb : public AwbAlgorithm @@ -95,8 +101,10 @@ public: void initialise() override; int read(const libcamera::YamlObject ¶ms) override; unsigned int getConvergenceFrames() const override; + void initialValues(double &gainR, double &gainB) override; void setMode(std::string const &name) override; void setManualGains(double manualR, double manualB) override; + void setColourTemperature(double temperatureK) override; void enableAuto() override; void disableAuto() override; void switchMode(CameraMode const &cameraMode, Metadata *metadata) override; @@ -160,11 +168,11 @@ private: void awbGrey(); void prepareStats(); double computeDelta2Sum(double gainR, double gainB); - Pwl interpolatePrior(); - double coarseSearch(Pwl const &prior); - void fineSearch(double &t, double &r, double &b, Pwl const &prior); + libcamera::ipa::Pwl interpolatePrior(); + double coarseSearch(libcamera::ipa::Pwl const &prior); + void fineSearch(double &t, double &r, double &b, libcamera::ipa::Pwl const &prior); std::vector<RGB> zones_; - std::vector<Pwl::Point> points_; + std::vector<libcamera::ipa::Pwl::Point> points_; /* manual r setting */ double manualR_; /* manual b setting */ diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp index 85baec3f..4c968f14 100644 --- a/src/ipa/rpi/controller/rpi/black_level.cpp +++ b/src/ipa/rpi/controller/rpi/black_level.cpp @@ -2,10 +2,9 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * black_level.cpp - black level control algorithm + * black level control algorithm */ -#include <math.h> #include <stdint.h> #include <libcamera/base/log.h> @@ -22,7 +21,7 @@ LOG_DEFINE_CATEGORY(RPiBlackLevel) #define NAME "rpi.black_level" BlackLevel::BlackLevel(Controller *controller) - : Algorithm(controller) + : BlackLevelAlgorithm(controller) { } @@ -45,6 +44,14 @@ int BlackLevel::read(const libcamera::YamlObject ¶ms) return 0; } +void BlackLevel::initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG, + uint16_t &blackLevelB) +{ + blackLevelR = blackLevelR_; + blackLevelG = blackLevelG_; + blackLevelB = blackLevelB_; +} + void BlackLevel::prepare(Metadata *imageMetadata) { /* diff --git a/src/ipa/rpi/controller/rpi/black_level.h b/src/ipa/rpi/controller/rpi/black_level.h index 2403f7f7..f50729db 100644 --- a/src/ipa/rpi/controller/rpi/black_level.h +++ b/src/ipa/rpi/controller/rpi/black_level.h @@ -2,23 +2,25 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * black_level.h - black level control algorithm + * black level control algorithm */ #pragma once -#include "../algorithm.h" +#include "../black_level_algorithm.h" #include "../black_level_status.h" /* This is our implementation of the "black level algorithm". */ namespace RPiController { -class BlackLevel : public Algorithm +class BlackLevel : public BlackLevelAlgorithm { public: BlackLevel(Controller *controller); char const *name() const override; int read(const libcamera::YamlObject ¶ms) override; + void initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG, + uint16_t &blackLevelB) override; void prepare(Metadata *imageMetadata) override; private: diff --git a/src/ipa/rpi/controller/rpi/cac.cpp b/src/ipa/rpi/controller/rpi/cac.cpp index 7c123da1..17779ad5 100644 --- a/src/ipa/rpi/controller/rpi/cac.cpp +++ b/src/ipa/rpi/controller/rpi/cac.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023 Raspberry Pi Ltd * - * cac.cpp - Chromatic Aberration Correction algorithm + * Chromatic Aberration Correction algorithm */ #include "cac.h" @@ -27,40 +27,23 @@ char const *Cac::name() const return NAME; } -int Cac::read(const libcamera::YamlObject ¶ms) -{ - arrayToSet(params["lut_rx"], config_.lutRx); - arrayToSet(params["lut_ry"], config_.lutRy); - arrayToSet(params["lut_bx"], config_.lutBx); - arrayToSet(params["lut_by"], config_.lutBy); - cacStatus_.lutRx = config_.lutRx; - cacStatus_.lutRy = config_.lutRy; - cacStatus_.lutBx = config_.lutBx; - cacStatus_.lutBy = config_.lutBy; - double strength = params["strength"].get<double>(1); - setStrength(config_.lutRx, cacStatus_.lutRx, strength); - setStrength(config_.lutBx, cacStatus_.lutBx, strength); - setStrength(config_.lutRy, cacStatus_.lutRy, strength); - setStrength(config_.lutBy, cacStatus_.lutBy, strength); - return 0; -} - -void Cac::initialise() -{ -} - -void Cac::arrayToSet(const libcamera::YamlObject ¶ms, std::vector<double> &inputArray) +static bool arrayToSet(const libcamera::YamlObject ¶ms, std::vector<double> &inputArray, const Size &size) { int num = 0; - const Size &size = getHardwareConfig().cacRegions; - inputArray.resize((size.width + 1) * (size.height + 1)); + int max_num = (size.width + 1) * (size.height + 1); + inputArray.resize(max_num); + for (const auto &p : params.asList()) { + if (num == max_num) + return false; inputArray[num++] = p.get<double>(0); } + + return num == max_num; } -void Cac::setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray, - double strengthFactor) +static void setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray, + double strengthFactor) { int num = 0; for (const auto &p : inputArray) { @@ -68,9 +51,52 @@ void Cac::setStrength(std::vector<double> &inputArray, std::vector<double> &outp } } +int Cac::read(const libcamera::YamlObject ¶ms) +{ + config_.enabled = params.contains("lut_rx") && params.contains("lut_ry") && + params.contains("lut_bx") && params.contains("lut_by"); + if (!config_.enabled) + return 0; + + const Size &size = getHardwareConfig().cacRegions; + + if (!arrayToSet(params["lut_rx"], config_.lutRx, size)) { + LOG(RPiCac, Error) << "Bad CAC lut_rx table"; + return -EINVAL; + } + + if (!arrayToSet(params["lut_ry"], config_.lutRy, size)) { + LOG(RPiCac, Error) << "Bad CAC lut_ry table"; + return -EINVAL; + } + + if (!arrayToSet(params["lut_bx"], config_.lutBx, size)) { + LOG(RPiCac, Error) << "Bad CAC lut_bx table"; + return -EINVAL; + } + + if (!arrayToSet(params["lut_by"], config_.lutBy, size)) { + LOG(RPiCac, Error) << "Bad CAC lut_by table"; + return -EINVAL; + } + + double strength = params["strength"].get<double>(1); + cacStatus_.lutRx = config_.lutRx; + cacStatus_.lutRy = config_.lutRy; + cacStatus_.lutBx = config_.lutBx; + cacStatus_.lutBy = config_.lutBy; + setStrength(config_.lutRx, cacStatus_.lutRx, strength); + setStrength(config_.lutBx, cacStatus_.lutBx, strength); + setStrength(config_.lutRy, cacStatus_.lutRy, strength); + setStrength(config_.lutBy, cacStatus_.lutBy, strength); + + return 0; +} + void Cac::prepare(Metadata *imageMetadata) { - imageMetadata->set("cac.status", cacStatus_); + if (config_.enabled) + imageMetadata->set("cac.status", cacStatus_); } // Register algorithm with the system. diff --git a/src/ipa/rpi/controller/rpi/cac.h b/src/ipa/rpi/controller/rpi/cac.h index 419180ab..a7b14c00 100644 --- a/src/ipa/rpi/controller/rpi/cac.h +++ b/src/ipa/rpi/controller/rpi/cac.h @@ -12,6 +12,7 @@ namespace RPiController { struct CacConfig { + bool enabled; std::vector<double> lutRx; std::vector<double> lutRy; std::vector<double> lutBx; @@ -24,15 +25,11 @@ public: Cac(Controller *controller = NULL); char const *name() const override; int read(const libcamera::YamlObject ¶ms) override; - void initialise() override; void prepare(Metadata *imageMetadata) override; - void setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray, - double strengthFactor); private: CacConfig config_; CacStatus cacStatus_; - void arrayToSet(const libcamera::YamlObject ¶ms, std::vector<double> &inputArray); }; } // namespace RPiController diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp index 2e2e6664..8607f152 100644 --- a/src/ipa/rpi/controller/rpi/ccm.cpp +++ b/src/ipa/rpi/controller/rpi/ccm.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * ccm.cpp - CCM (colour correction matrix) control algorithm + * CCM (colour correction matrix) control algorithm */ #include <libcamera/base/log.h> @@ -29,34 +29,7 @@ LOG_DEFINE_CATEGORY(RPiCcm) #define NAME "rpi.ccm" -Matrix::Matrix() -{ - memset(m, 0, sizeof(m)); -} -Matrix::Matrix(double m0, double m1, double m2, double m3, double m4, double m5, - double m6, double m7, double m8) -{ - m[0][0] = m0, m[0][1] = m1, m[0][2] = m2, m[1][0] = m3, m[1][1] = m4, - m[1][2] = m5, m[2][0] = m6, m[2][1] = m7, m[2][2] = m8; -} -int Matrix::read(const libcamera::YamlObject ¶ms) -{ - double *ptr = (double *)m; - - if (params.size() != 9) { - LOG(RPiCcm, Error) << "Wrong number of values in CCM"; - return -EINVAL; - } - - for (const auto ¶m : params.asList()) { - auto value = param.get<double>(); - if (!value) - return -EINVAL; - *ptr++ = *value; - } - - return 0; -} +using Matrix3x3 = Matrix<double, 3, 3>; Ccm::Ccm(Controller *controller) : CcmAlgorithm(controller), saturation_(1.0) {} @@ -68,12 +41,10 @@ char const *Ccm::name() const int Ccm::read(const libcamera::YamlObject ¶ms) { - int ret; - if (params.contains("saturation")) { - ret = config_.saturation.read(params["saturation"]); - if (ret) - return ret; + config_.saturation = params["saturation"].get<ipa::Pwl>(ipa::Pwl{}); + if (config_.saturation.empty()) + return -EINVAL; } for (auto &p : params["ccms"].asList()) { @@ -83,9 +54,12 @@ int Ccm::read(const libcamera::YamlObject ¶ms) CtCcm ctCcm; ctCcm.ct = *value; - ret = ctCcm.ccm.read(p["ccm"]); - if (ret) - return ret; + + auto ccm = p["ccm"].get<Matrix3x3>(); + if (!ccm) + return -EINVAL; + + ctCcm.ccm = *ccm; if (!config_.ccms.empty() && ctCcm.ct <= config_.ccms.back().ct) { LOG(RPiCcm, Error) @@ -113,8 +87,10 @@ void Ccm::initialise() { } +namespace { + template<typename T> -static bool getLocked(Metadata *metadata, std::string const &tag, T &value) +bool getLocked(Metadata *metadata, std::string const &tag, T &value) { T *ptr = metadata->getLocked<T>(tag); if (ptr == nullptr) @@ -123,7 +99,7 @@ static bool getLocked(Metadata *metadata, std::string const &tag, T &value) return true; } -Matrix calculateCcm(std::vector<CtCcm> const &ccms, double ct) +Matrix3x3 calculateCcm(std::vector<CtCcm> const &ccms, double ct) { if (ct <= ccms.front().ct) return ccms.front().ccm; @@ -139,16 +115,25 @@ Matrix calculateCcm(std::vector<CtCcm> const &ccms, double ct) } } -Matrix applySaturation(Matrix const &ccm, double saturation) +Matrix3x3 applySaturation(Matrix3x3 const &ccm, double saturation) { - Matrix RGB2Y(0.299, 0.587, 0.114, -0.169, -0.331, 0.500, 0.500, -0.419, - -0.081); - Matrix Y2RGB(1.000, 0.000, 1.402, 1.000, -0.345, -0.714, 1.000, 1.771, - 0.000); - Matrix S(1, 0, 0, 0, saturation, 0, 0, 0, saturation); + static const Matrix3x3 RGB2Y({ 0.299, 0.587, 0.114, + -0.169, -0.331, 0.500, + 0.500, -0.419, -0.081 }); + + static const Matrix3x3 Y2RGB({ 1.000, 0.000, 1.402, + 1.000, -0.345, -0.714, + 1.000, 1.771, 0.000 }); + + Matrix3x3 S({ 1, 0, 0, + 0, saturation, 0, + 0, 0, saturation }); + return Y2RGB * S * RGB2Y * ccm; } +} /* namespace */ + void Ccm::prepare(Metadata *imageMetadata) { bool awbOk = false, luxOk = false; @@ -166,18 +151,18 @@ void Ccm::prepare(Metadata *imageMetadata) LOG(RPiCcm, Warning) << "no colour temperature found"; if (!luxOk) LOG(RPiCcm, Warning) << "no lux value found"; - Matrix ccm = calculateCcm(config_.ccms, awb.temperatureK); + Matrix3x3 ccm = calculateCcm(config_.ccms, awb.temperatureK); double saturation = saturation_; struct CcmStatus ccmStatus; ccmStatus.saturation = saturation; if (!config_.saturation.empty()) saturation *= config_.saturation.eval( - config_.saturation.domain().clip(lux.lux)); + config_.saturation.domain().clamp(lux.lux)); ccm = applySaturation(ccm, saturation); for (int j = 0; j < 3; j++) for (int i = 0; i < 3; i++) ccmStatus.matrix[j * 3 + i] = - std::max(-8.0, std::min(7.9999, ccm.m[j][i])); + std::max(-8.0, std::min(7.9999, ccm[j][i])); LOG(RPiCcm, Debug) << "colour temperature " << awb.temperatureK << "K"; LOG(RPiCcm, Debug) diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h index 286d0b33..c05dbb17 100644 --- a/src/ipa/rpi/controller/rpi/ccm.h +++ b/src/ipa/rpi/controller/rpi/ccm.h @@ -2,59 +2,29 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * ccm.h - CCM (colour correction matrix) control algorithm + * CCM (colour correction matrix) control algorithm */ #pragma once #include <vector> +#include "libcamera/internal/matrix.h" +#include <libipa/pwl.h> + #include "../ccm_algorithm.h" -#include "../pwl.h" namespace RPiController { /* Algorithm to calculate colour matrix. Should be placed after AWB. */ -struct Matrix { - Matrix(double m0, double m1, double m2, double m3, double m4, double m5, - double m6, double m7, double m8); - Matrix(); - double m[3][3]; - int read(const libcamera::YamlObject ¶ms); -}; -static inline Matrix operator*(double d, Matrix const &m) -{ - return Matrix(m.m[0][0] * d, m.m[0][1] * d, m.m[0][2] * d, - m.m[1][0] * d, m.m[1][1] * d, m.m[1][2] * d, - m.m[2][0] * d, m.m[2][1] * d, m.m[2][2] * d); -} -static inline Matrix operator*(Matrix const &m1, Matrix const &m2) -{ - Matrix m; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - m.m[i][j] = m1.m[i][0] * m2.m[0][j] + - m1.m[i][1] * m2.m[1][j] + - m1.m[i][2] * m2.m[2][j]; - return m; -} -static inline Matrix operator+(Matrix const &m1, Matrix const &m2) -{ - Matrix m; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - m.m[i][j] = m1.m[i][j] + m2.m[i][j]; - return m; -} - struct CtCcm { double ct; - Matrix ccm; + libcamera::Matrix<double, 3, 3> ccm; }; struct CcmConfig { std::vector<CtCcm> ccms; - Pwl saturation; + libcamera::ipa::Pwl saturation; }; class Ccm : public CcmAlgorithm diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp index 4e038a02..fe866a54 100644 --- a/src/ipa/rpi/controller/rpi/contrast.cpp +++ b/src/ipa/rpi/controller/rpi/contrast.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * contrast.cpp - contrast (gamma) control algorithm + * contrast (gamma) control algorithm */ #include <stdint.h> @@ -53,7 +53,9 @@ int Contrast::read(const libcamera::YamlObject ¶ms) config_.hiHistogram = params["hi_histogram"].get<double>(0.95); config_.hiLevel = params["hi_level"].get<double>(0.95); config_.hiMax = params["hi_max"].get<double>(2000); - return config_.gammaCurve.read(params["gamma_curve"]); + + config_.gammaCurve = params["gamma_curve"].get<ipa::Pwl>(ipa::Pwl{}); + return config_.gammaCurve.empty() ? -EINVAL : 0; } void Contrast::setBrightness(double brightness) @@ -92,10 +94,12 @@ void Contrast::prepare(Metadata *imageMetadata) imageMetadata->set("contrast.status", status_); } -Pwl computeStretchCurve(Histogram const &histogram, +namespace { + +ipa::Pwl computeStretchCurve(Histogram const &histogram, ContrastConfig const &config) { - Pwl enhance; + ipa::Pwl enhance; enhance.append(0, 0); /* * If the start of the histogram is rather empty, try to pull it down a @@ -136,10 +140,10 @@ Pwl computeStretchCurve(Histogram const &histogram, return enhance; } -Pwl applyManualContrast(Pwl const &gammaCurve, double brightness, - double contrast) +ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness, + double contrast) { - Pwl newGammaCurve; + ipa::Pwl newGammaCurve; LOG(RPiContrast, Debug) << "Manual brightness " << brightness << " contrast " << contrast; gammaCurve.map([&](double x, double y) { @@ -151,6 +155,8 @@ Pwl applyManualContrast(Pwl const &gammaCurve, double brightness, return newGammaCurve; } +} /* namespace */ + void Contrast::process(StatisticsPtr &stats, [[maybe_unused]] Metadata *imageMetadata) { @@ -160,7 +166,7 @@ void Contrast::process(StatisticsPtr &stats, * ways: 1. Adjust the gamma curve so as to pull the start of the * histogram down, and possibly push the end up. */ - Pwl gammaCurve = config_.gammaCurve; + ipa::Pwl gammaCurve = config_.gammaCurve; if (ceEnable_) { if (config_.loMax != 0 || config_.hiMax != 0) gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve); diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h index 59aa70dc..c0f7db98 100644 --- a/src/ipa/rpi/controller/rpi/contrast.h +++ b/src/ipa/rpi/controller/rpi/contrast.h @@ -2,14 +2,15 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * contrast.h - contrast (gamma) control algorithm + * contrast (gamma) control algorithm */ #pragma once #include <mutex> +#include <libipa/pwl.h> + #include "../contrast_algorithm.h" -#include "../pwl.h" namespace RPiController { @@ -26,7 +27,7 @@ struct ContrastConfig { double hiHistogram; double hiLevel; double hiMax; - Pwl gammaCurve; + libcamera::ipa::Pwl gammaCurve; }; class Contrast : public ContrastAlgorithm diff --git a/src/ipa/rpi/controller/rpi/denoise.cpp b/src/ipa/rpi/controller/rpi/denoise.cpp index 154ee604..ba851658 100644 --- a/src/ipa/rpi/controller/rpi/denoise.cpp +++ b/src/ipa/rpi/controller/rpi/denoise.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022 Raspberry Pi Ltd * - * Denoise.cpp - Denoise (spatial, colour, temporal) control algorithm + * Denoise (spatial, colour, temporal) control algorithm */ #include "denoise.h" diff --git a/src/ipa/rpi/controller/rpi/dpc.cpp b/src/ipa/rpi/controller/rpi/dpc.cpp index be3871df..8aac03f7 100644 --- a/src/ipa/rpi/controller/rpi/dpc.cpp +++ b/src/ipa/rpi/controller/rpi/dpc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * dpc.cpp - DPC (defective pixel correction) control algorithm + * DPC (defective pixel correction) control algorithm */ #include <libcamera/base/log.h> diff --git a/src/ipa/rpi/controller/rpi/dpc.h b/src/ipa/rpi/controller/rpi/dpc.h index 84a05604..9cefb06d 100644 --- a/src/ipa/rpi/controller/rpi/dpc.h +++ b/src/ipa/rpi/controller/rpi/dpc.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * dpc.h - DPC (defective pixel correction) control algorithm + * DPC (defective pixel correction) control algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/focus.h b/src/ipa/rpi/controller/rpi/focus.h index 8556039d..ee014be9 100644 --- a/src/ipa/rpi/controller/rpi/focus.h +++ b/src/ipa/rpi/controller/rpi/focus.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * focus.h - focus algorithm + * focus algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp index 510870e9..40e7191b 100644 --- a/src/ipa/rpi/controller/rpi/geq.cpp +++ b/src/ipa/rpi/controller/rpi/geq.cpp @@ -2,14 +2,13 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * geq.cpp - GEQ (green equalisation) control algorithm + * GEQ (green equalisation) control algorithm */ #include <libcamera/base/log.h> #include "../device_status.h" #include "../lux_status.h" -#include "../pwl.h" #include "geq.h" @@ -45,9 +44,9 @@ int Geq::read(const libcamera::YamlObject ¶ms) } if (params.contains("strength")) { - int ret = config_.strength.read(params["strength"]); - if (ret) - return ret; + config_.strength = params["strength"].get<ipa::Pwl>(ipa::Pwl{}); + if (config_.strength.empty()) + return -EINVAL; } return 0; @@ -67,7 +66,7 @@ void Geq::prepare(Metadata *imageMetadata) GeqStatus geqStatus = {}; double strength = config_.strength.empty() ? 1.0 - : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux)); + : config_.strength.eval(config_.strength.domain().clamp(luxStatus.lux)); strength *= deviceStatus.analogueGain; double offset = config_.offset * strength; double slope = config_.slope * strength; diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h index ee3a52ff..e8b9f427 100644 --- a/src/ipa/rpi/controller/rpi/geq.h +++ b/src/ipa/rpi/controller/rpi/geq.h @@ -2,10 +2,12 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * geq.h - GEQ (green equalisation) control algorithm + * GEQ (green equalisation) control algorithm */ #pragma once +#include <libipa/pwl.h> + #include "../algorithm.h" #include "../geq_status.h" @@ -16,7 +18,7 @@ namespace RPiController { struct GeqConfig { uint16_t offset; double slope; - Pwl strength; /* lux to strength factor */ + libcamera::ipa::Pwl strength; /* lux to strength factor */ }; class Geq : public Algorithm diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp index fb580548..f3da8291 100644 --- a/src/ipa/rpi/controller/rpi/hdr.cpp +++ b/src/ipa/rpi/controller/rpi/hdr.cpp @@ -2,11 +2,13 @@ /* * Copyright (C) 2023 Raspberry Pi Ltd * - * hdr.cpp - HDR control algorithm + * HDR control algorithm */ #include "hdr.h" +#include <cmath> + #include <libcamera/base/log.h> #include "../agc_status.h" @@ -39,25 +41,52 @@ void HdrConfig::read(const libcamera::YamlObject ¶ms, const std::string &mod channelMap[v.get<unsigned int>().value()] = k; /* Lens shading related parameters. */ - if (params.contains("spatial_gain")) { - spatialGain.read(params["spatial_gain"]); - diffusion = params["diffusion"].get<unsigned int>(3); - /* Clip to an arbitrary limit just to stop typos from killing the system! */ - const unsigned int MAX_DIFFUSION = 15; - if (diffusion > MAX_DIFFUSION) { - diffusion = MAX_DIFFUSION; - LOG(RPiHdr, Warning) << "Diffusion value clipped to " << MAX_DIFFUSION; - } + if (params.contains("spatial_gain_curve")) { + spatialGainCurve = params["spatial_gain_curve"].get<ipa::Pwl>(ipa::Pwl{}); + } else if (params.contains("spatial_gain")) { + double spatialGain = params["spatial_gain"].get<double>(2.0); + spatialGainCurve.append(0.0, spatialGain); + spatialGainCurve.append(0.01, spatialGain); + spatialGainCurve.append(0.06, 1.0); /* maybe make this programmable? */ + spatialGainCurve.append(1.0, 1.0); + } + + diffusion = params["diffusion"].get<unsigned int>(3); + /* Clip to an arbitrary limit just to stop typos from killing the system! */ + const unsigned int MAX_DIFFUSION = 15; + if (diffusion > MAX_DIFFUSION) { + diffusion = MAX_DIFFUSION; + LOG(RPiHdr, Warning) << "Diffusion value clipped to " << MAX_DIFFUSION; } /* Read any tonemap parameters. */ tonemapEnable = params["tonemap_enable"].get<int>(0); - detailConstant = params["detail_constant"].get<uint16_t>(50); - detailSlope = params["detail_slope"].get<double>(8.0); + detailConstant = params["detail_constant"].get<uint16_t>(0); + detailSlope = params["detail_slope"].get<double>(0.0); iirStrength = params["iir_strength"].get<double>(8.0); strength = params["strength"].get<double>(1.5); if (tonemapEnable) - tonemap.read(params["tonemap"]); + tonemap = params["tonemap"].get<ipa::Pwl>(ipa::Pwl{}); + speed = params["speed"].get<double>(1.0); + if (params.contains("hi_quantile_targets")) { + hiQuantileTargets = params["hi_quantile_targets"].getList<double>().value(); + if (hiQuantileTargets.empty() || hiQuantileTargets.size() % 2) + LOG(RPiHdr, Fatal) << "hi_quantile_targets much be even and non-empty"; + } else + hiQuantileTargets = { 0.95, 0.65, 0.5, 0.28, 0.3, 0.25 }; + hiQuantileMaxGain = params["hi_quantile_max_gain"].get<double>(1.6); + if (params.contains("quantile_targets")) { + quantileTargets = params["quantile_targets"].getList<double>().value(); + if (quantileTargets.empty() || quantileTargets.size() % 2) + LOG(RPiHdr, Fatal) << "quantile_targets much be even and non-empty"; + } else + quantileTargets = { 0.2, 0.03, 1.0, 0.15 }; + powerMin = params["power_min"].get<double>(0.65); + powerMax = params["power_max"].get<double>(1.0); + if (params.contains("contrast_adjustments")) { + contrastAdjustments = params["contrast_adjustments"].getList<double>().value(); + } else + contrastAdjustments = { 0.5, 0.75 }; /* Read any stitch parameters. */ stitchEnable = params["stitch_enable"].get<int>(0); @@ -159,7 +188,7 @@ void Hdr::prepare(Metadata *imageMetadata) } HdrConfig &config = it->second; - if (config.spatialGain.empty()) + if (config.spatialGainCurve.empty()) return; AlscStatus alscStatus{}; /* some compilers seem to require the braces */ @@ -183,7 +212,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config /* When there's a change of HDR mode we start over with a new tonemap curve. */ if (delayedStatus_.mode != previousMode_) { previousMode_ = delayedStatus_.mode; - tonemap_ = Pwl(); + tonemap_ = ipa::Pwl(); } /* No tonemapping. No need to output a tonemap.status. */ @@ -205,10 +234,61 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config return true; /* - * If we wanted to build or adjust tonemaps dynamically, this would be the place - * to do it. But for now we seem to be getting by without. + * Create a tonemap dynamically. We have three ingredients. + * + * 1. We have a list of "hi quantiles" and "targets". We use these to judge if + * the image does seem to be reasonably saturated. If it isn't, we calculate + * a gain that we will feed as a linear factor into the tonemap generation. + * This prevents unsaturated images from beoming quite so "flat". + * + * 2. We have a list of quantile/target pairs for the bottom of the histogram. + * We use these to calculate how much gain we must apply to the bottom of the + * tonemap. We apply this gain as a power curve so as not to blow out the top + * end. + * + * 3. Finally, when we generate the tonemap, we have some contrast adjustments + * for the bottom because we know that power curves can start quite steeply and + * cause a washed-out look. */ + /* Compute the linear gain from the headroom for saturation at the top. */ + double gain = 10; /* arbitrary, but hiQuantileMaxGain will clamp it later */ + for (unsigned int i = 0; i < config.hiQuantileTargets.size(); i += 2) { + double quantile = config.hiQuantileTargets[i]; + double target = config.hiQuantileTargets[i + 1]; + double value = stats->yHist.interQuantileMean(quantile, 1.0) / 1024.0; + double newGain = target / (value + 0.01); + gain = std::min(gain, newGain); + } + gain = std::clamp(gain, 1.0, config.hiQuantileMaxGain); + + /* Compute the power curve from the amount of gain needed at the bottom. */ + double min_power = 2; /* arbitrary, but config.powerMax will clamp it later */ + for (unsigned int i = 0; i < config.quantileTargets.size(); i += 2) { + double quantile = config.quantileTargets[i]; + double target = config.quantileTargets[i + 1]; + double value = stats->yHist.interQuantileMean(0, quantile) / 1024.0; + value = std::min(value * gain, 1.0); + double power = log(target + 1e-6) / log(value + 1e-6); + min_power = std::min(min_power, power); + } + double power = std::clamp(min_power, config.powerMin, config.powerMax); + + /* Generate the tonemap, including the contrast adjustment factors. */ + libcamera::ipa::Pwl tonemap; + tonemap.append(0, 0); + for (unsigned int i = 0; i <= 6; i++) { + double x = 1 << (i + 9); /* x loops from 512 to 32768 inclusive */ + double y = pow(std::min(x * gain, 65535.0) / 65536.0, power) * 65536; + if (i < config.contrastAdjustments.size()) + y *= config.contrastAdjustments[i]; + if (!tonemap_.empty()) + y = y * config.speed + tonemap_.eval(x) * (1 - config.speed); + tonemap.append(x, y); + } + tonemap.append(65535, 65535); + tonemap_ = tonemap; + return true; } @@ -255,7 +335,7 @@ static void averageGains(std::vector<double> &src, std::vector<double> &dst, con void Hdr::updateGains(StatisticsPtr &stats, HdrConfig &config) { - if (config.spatialGain.empty()) + if (config.spatialGainCurve.empty()) return; /* When alternating exposures, only compute these gains for the short frame. */ @@ -270,7 +350,7 @@ void Hdr::updateGains(StatisticsPtr &stats, HdrConfig &config) double g = region.val.gSum / counted; double b = region.val.bSum / counted; double brightness = std::max({ r, g, b }) / 65535; - gains_[0][i] = config.spatialGain.eval(brightness); + gains_[0][i] = config.spatialGainCurve.eval(brightness); } /* Ping-pong between the two gains_ buffers. */ diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h index 980aa3d1..5c2f3988 100644 --- a/src/ipa/rpi/controller/rpi/hdr.h +++ b/src/ipa/rpi/controller/rpi/hdr.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Raspberry Pi Ltd * - * hdr.h - HDR control algorithm + * HDR control algorithm */ #pragma once @@ -12,9 +12,10 @@ #include <libcamera/geometry.h> +#include <libipa/pwl.h> + #include "../hdr_algorithm.h" #include "../hdr_status.h" -#include "../pwl.h" /* This is our implementation of an HDR algorithm. */ @@ -26,7 +27,7 @@ struct HdrConfig { std::map<unsigned int, std::string> channelMap; /* Lens shading related parameters. */ - Pwl spatialGain; /* Brightness to gain curve for different image regions. */ + libcamera::ipa::Pwl spatialGainCurve; /* Brightness to gain curve for different image regions. */ unsigned int diffusion; /* How much to diffuse the gain spatially. */ /* Tonemap related parameters. */ @@ -35,7 +36,15 @@ struct HdrConfig { double detailSlope; double iirStrength; double strength; - Pwl tonemap; + libcamera::ipa::Pwl tonemap; + /* These relate to adaptive tonemap calculation. */ + double speed; + std::vector<double> hiQuantileTargets; /* quantiles to check for unsaturated images */ + double hiQuantileMaxGain; /* the max gain we'll apply when unsaturated */ + std::vector<double> quantileTargets; /* target values for histogram quantiles */ + double powerMin; /* minimum tonemap power */ + double powerMax; /* maximum tonemap power */ + std::vector<double> contrastAdjustments; /* any contrast adjustment factors */ /* Stitch related parameters. */ bool stitchEnable; @@ -67,7 +76,7 @@ private: HdrStatus status_; /* track the current HDR mode and channel */ HdrStatus delayedStatus_; /* track the delayed HDR mode and channel */ std::string previousMode_; - Pwl tonemap_; + libcamera::ipa::Pwl tonemap_; libcamera::Size regions_; /* stats regions */ unsigned int numRegions_; /* total number of stats regions */ std::vector<double> gains_[2]; diff --git a/src/ipa/rpi/controller/rpi/lux.cpp b/src/ipa/rpi/controller/rpi/lux.cpp index 06625f3a..27b89a8f 100644 --- a/src/ipa/rpi/controller/rpi/lux.cpp +++ b/src/ipa/rpi/controller/rpi/lux.cpp @@ -2,9 +2,8 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * lux.cpp - Lux control algorithm + * Lux control algorithm */ -#include <math.h> #include <libcamera/base/log.h> @@ -41,7 +40,7 @@ int Lux::read(const libcamera::YamlObject ¶ms) auto value = params["reference_shutter_speed"].get<double>(); if (!value) return -EINVAL; - referenceShutterSpeed_ = *value * 1.0us; + referenceExposureTime_ = *value * 1.0us; value = params["reference_gain"].get<double>(); if (!value) @@ -83,11 +82,11 @@ void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata) double currentAperture = deviceStatus.aperture.value_or(currentAperture_); double currentY = stats->yHist.interQuantileMean(0, 1); double gainRatio = referenceGain_ / currentGain; - double shutterSpeedRatio = - referenceShutterSpeed_ / deviceStatus.shutterSpeed; + double exposureTimeRatio = + referenceExposureTime_ / deviceStatus.exposureTime; double apertureRatio = referenceAperture_ / currentAperture; double yRatio = currentY * (65536 / stats->yHist.bins()) / referenceY_; - double estimatedLux = shutterSpeedRatio * gainRatio * + double estimatedLux = exposureTimeRatio * gainRatio * apertureRatio * apertureRatio * yRatio * referenceLux_; LuxStatus status; diff --git a/src/ipa/rpi/controller/rpi/lux.h b/src/ipa/rpi/controller/rpi/lux.h index 89411a54..da007fe9 100644 --- a/src/ipa/rpi/controller/rpi/lux.h +++ b/src/ipa/rpi/controller/rpi/lux.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * lux.h - Lux control algorithm + * Lux control algorithm */ #pragma once @@ -32,7 +32,7 @@ private: * These values define the conditions of the reference image, against * which we compare the new image. */ - libcamera::utils::Duration referenceShutterSpeed_; + libcamera::utils::Duration referenceExposureTime_; double referenceGain_; double referenceAperture_; /* units of 1/f */ double referenceY_; /* out of 65536 */ diff --git a/src/ipa/rpi/controller/rpi/noise.cpp b/src/ipa/rpi/controller/rpi/noise.cpp index bcd8b9ed..145175fb 100644 --- a/src/ipa/rpi/controller/rpi/noise.cpp +++ b/src/ipa/rpi/controller/rpi/noise.cpp @@ -2,10 +2,10 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * noise.cpp - Noise control algorithm + * Noise control algorithm */ -#include <math.h> +#include <cmath> #include <libcamera/base/log.h> @@ -69,7 +69,7 @@ void Noise::prepare(Metadata *imageMetadata) * make some adjustments based on the camera mode (such as * binning), if we knew how to discover it... */ - double factor = sqrt(deviceStatus.analogueGain) / modeFactor_; + double factor = std::sqrt(deviceStatus.analogueGain) / modeFactor_; struct NoiseStatus status; status.noiseConstant = referenceConstant_ * factor; status.noiseSlope = referenceSlope_ * factor; diff --git a/src/ipa/rpi/controller/rpi/noise.h b/src/ipa/rpi/controller/rpi/noise.h index 74c31e64..6deae1f0 100644 --- a/src/ipa/rpi/controller/rpi/noise.h +++ b/src/ipa/rpi/controller/rpi/noise.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * noise.h - Noise control algorithm + * Noise control algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/saturation.cpp b/src/ipa/rpi/controller/rpi/saturation.cpp index 813540e5..b83c5887 100644 --- a/src/ipa/rpi/controller/rpi/saturation.cpp +++ b/src/ipa/rpi/controller/rpi/saturation.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022 Raspberry Pi Ltd * - * saturation.cpp - Saturation control algorithm + * Saturation control algorithm */ #include "saturation.h" diff --git a/src/ipa/rpi/controller/rpi/sdn.cpp b/src/ipa/rpi/controller/rpi/sdn.cpp index 6743919e..619178a8 100644 --- a/src/ipa/rpi/controller/rpi/sdn.cpp +++ b/src/ipa/rpi/controller/rpi/sdn.cpp @@ -2,10 +2,11 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * sdn.cpp - SDN (spatial denoise) control algorithm + * SDN (spatial denoise) control algorithm */ #include <libcamera/base/log.h> +#include <libcamera/base/utils.h> #include "../denoise_status.h" #include "../noise_status.h" @@ -60,7 +61,7 @@ void Sdn::prepare(Metadata *imageMetadata) status.noiseConstant = noiseStatus.noiseConstant * deviation_; status.noiseSlope = noiseStatus.noiseSlope * deviation_; status.strength = strength_; - status.mode = static_cast<std::underlying_type_t<DenoiseMode>>(mode_); + status.mode = utils::to_underlying(mode_); imageMetadata->set("denoise.status", status); LOG(RPiSdn, Debug) << "programmed constant " << status.noiseConstant diff --git a/src/ipa/rpi/controller/rpi/sdn.h b/src/ipa/rpi/controller/rpi/sdn.h index 9dd73c38..cb226de8 100644 --- a/src/ipa/rpi/controller/rpi/sdn.h +++ b/src/ipa/rpi/controller/rpi/sdn.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * sdn.h - SDN (spatial denoise) control algorithm + * SDN (spatial denoise) control algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/sharpen.cpp b/src/ipa/rpi/controller/rpi/sharpen.cpp index 4f6f020a..1d143ff5 100644 --- a/src/ipa/rpi/controller/rpi/sharpen.cpp +++ b/src/ipa/rpi/controller/rpi/sharpen.cpp @@ -2,10 +2,10 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * sharpen.cpp - sharpening control algorithm + * sharpening control algorithm */ -#include <math.h> +#include <cmath> #include <libcamera/base/log.h> @@ -68,7 +68,7 @@ void Sharpen::prepare(Metadata *imageMetadata) * we adjust the limit and threshold less aggressively. Using a sqrt * function is an arbitrary but gentle way of accomplishing this. */ - double userStrengthSqrt = sqrt(userStrength_); + double userStrengthSqrt = std::sqrt(userStrength_); struct SharpenStatus status; /* * Binned modes seem to need the sharpening toned down with this diff --git a/src/ipa/rpi/controller/rpi/sharpen.h b/src/ipa/rpi/controller/rpi/sharpen.h index 8bb7631e..96ccd609 100644 --- a/src/ipa/rpi/controller/rpi/sharpen.h +++ b/src/ipa/rpi/controller/rpi/sharpen.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * sharpen.h - sharpening control algorithm + * sharpening control algorithm */ #pragma once diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp index 5f8b2bf2..3422adfe 100644 --- a/src/ipa/rpi/controller/rpi/tonemap.cpp +++ b/src/ipa/rpi/controller/rpi/tonemap.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022 Raspberry Pi Ltd * - * tonemap.cpp - Tonemap control algorithm + * Tonemap control algorithm */ #include "tonemap.h" @@ -33,7 +33,7 @@ int Tonemap::read(const libcamera::YamlObject ¶ms) config_.detailSlope = params["detail_slope"].get<double>(0.1); config_.iirStrength = params["iir_strength"].get<double>(1.0); config_.strength = params["strength"].get<double>(1.0); - config_.tonemap.read(params["tone_curve"]); + config_.tonemap = params["tone_curve"].get<ipa::Pwl>(ipa::Pwl{}); return 0; } diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h index f25aa47f..ba0cf5c4 100644 --- a/src/ipa/rpi/controller/rpi/tonemap.h +++ b/src/ipa/rpi/controller/rpi/tonemap.h @@ -6,8 +6,9 @@ */ #pragma once +#include <libipa/pwl.h> + #include "algorithm.h" -#include "pwl.h" namespace RPiController { @@ -16,7 +17,7 @@ struct TonemapConfig { double detailSlope; double iirStrength; double strength; - Pwl tonemap; + libcamera::ipa::Pwl tonemap; }; class Tonemap : public Algorithm diff --git a/src/ipa/rpi/controller/saturation_status.h b/src/ipa/rpi/controller/saturation_status.h index 337b66a3..c7fadc99 100644 --- a/src/ipa/rpi/controller/saturation_status.h +++ b/src/ipa/rpi/controller/saturation_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022 Raspberry Pi Ltd * - * saturation_status.h - Saturation control algorithm status + * Saturation control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/sharpen_algorithm.h b/src/ipa/rpi/controller/sharpen_algorithm.h index 3be21c32..abd82cb2 100644 --- a/src/ipa/rpi/controller/sharpen_algorithm.h +++ b/src/ipa/rpi/controller/sharpen_algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * sharpen_algorithm.h - sharpness control algorithm interface + * sharpness control algorithm interface */ #pragma once diff --git a/src/ipa/rpi/controller/sharpen_status.h b/src/ipa/rpi/controller/sharpen_status.h index 106166db..74910199 100644 --- a/src/ipa/rpi/controller/sharpen_status.h +++ b/src/ipa/rpi/controller/sharpen_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Raspberry Pi Ltd * - * sharpen_status.h - Sharpen control algorithm status + * Sharpen control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/statistics.h b/src/ipa/rpi/controller/statistics.h index 015d4efc..cbd81161 100644 --- a/src/ipa/rpi/controller/statistics.h +++ b/src/ipa/rpi/controller/statistics.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Raspberry Pi Ltd * - * statistics.h - Raspberry Pi generic statistics structure + * Raspberry Pi generic statistics structure */ #pragma once diff --git a/src/ipa/rpi/controller/stitch_status.h b/src/ipa/rpi/controller/stitch_status.h index b17800ed..7812f3e3 100644 --- a/src/ipa/rpi/controller/stitch_status.h +++ b/src/ipa/rpi/controller/stitch_status.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2023 Raspberry Pi Ltd * - * stitch_status.h - stitch control algorithm status + * stitch control algorithm status */ #pragma once diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h index 0e639946..0364ff66 100644 --- a/src/ipa/rpi/controller/tonemap_status.h +++ b/src/ipa/rpi/controller/tonemap_status.h @@ -2,16 +2,16 @@ /* * Copyright (C) 2022 Raspberry Pi Ltd * - * hdr.h - Tonemap control algorithm status + * Tonemap control algorithm status */ #pragma once -#include "pwl.h" +#include <libipa/pwl.h> struct TonemapStatus { uint16_t detailConstant; double detailSlope; double iirStrength; double strength; - RPiController::Pwl tonemap; + libcamera::ipa::Pwl tonemap; }; diff --git a/src/ipa/rpi/pisp/data/imx219.json b/src/ipa/rpi/pisp/data/imx219.json new file mode 100644 index 00000000..5254e60d --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx219.json @@ -0,0 +1,1187 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 21965, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 800, + "reference_Y": 11460 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 3.661 + } + }, + { + "rpi.geq": + { + "offset": 239, + "slope": 0.00766 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2860.0, 0.9514, 0.4156, + 2960.0, 0.9289, 0.4372, + 3603.0, 0.8305, 0.5251, + 4650.0, 0.6756, 0.6433, + 5858.0, 0.6193, 0.6807, + 7580.0, 0.5019, 0.7495 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.03392, + "transverse_neg": 0.034 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 10.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 10.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.418, 1.428, 1.446, 1.454, 1.454, 1.451, 1.441, 1.428, 1.411, 1.391, 1.371, 1.349, 1.334, 1.327, 1.325, 1.325, 1.325, 1.325, 1.331, 1.344, 1.363, 1.383, 1.402, 1.418, 1.433, 1.446, 1.452, 1.453, 1.446, 1.435, 1.415, 1.404, + 1.428, 1.442, 1.453, 1.455, 1.454, 1.447, 1.431, 1.413, 1.392, 1.371, 1.349, 1.331, 1.318, 1.307, 1.299, 1.299, 1.299, 1.303, 1.313, 1.328, 1.344, 1.363, 1.383, 1.404, 1.424, 1.439, 1.451, 1.453, 1.453, 1.445, 1.431, 1.415, + 1.436, 1.448, 1.453, 1.455, 1.449, 1.435, 1.415, 1.393, 1.369, 1.345, 1.322, 1.303, 1.287, 1.276, 1.269, 1.268, 1.268, 1.272, 1.283, 1.298, 1.316, 1.337, 1.362, 1.384, 1.406, 1.427, 1.444, 1.454, 1.454, 1.452, 1.438, 1.426, + 1.441, 1.451, 1.454, 1.451, 1.439, 1.422, 1.396, 1.372, 1.345, 1.319, 1.295, 1.274, 1.257, 1.245, 1.239, 1.238, 1.238, 1.245, 1.255, 1.269, 1.289, 1.311, 1.336, 1.362, 1.388, 1.412, 1.433, 1.448, 1.454, 1.453, 1.445, 1.433, + 1.445, 1.452, 1.452, 1.445, 1.428, 1.405, 1.379, 1.349, 1.319, 1.295, 1.269, 1.247, 1.229, 1.219, 1.212, 1.211, 1.211, 1.217, 1.228, 1.242, 1.261, 1.286, 1.311, 1.339, 1.367, 1.395, 1.419, 1.439, 1.452, 1.452, 1.451, 1.436, + 1.448, 1.451, 1.451, 1.435, 1.414, 1.387, 1.358, 1.327, 1.296, 1.269, 1.245, 1.222, 1.205, 1.193, 1.187, 1.185, 1.186, 1.191, 1.202, 1.217, 1.237, 1.261, 1.286, 1.316, 1.346, 1.378, 1.404, 1.429, 1.445, 1.451, 1.451, 1.442, + 1.448, 1.448, 1.445, 1.427, 1.401, 1.371, 1.338, 1.306, 1.274, 1.245, 1.222, 1.199, 1.183, 1.171, 1.164, 1.162, 1.162, 1.168, 1.181, 1.194, 1.215, 1.237, 1.264, 1.294, 1.325, 1.359, 1.389, 1.418, 1.441, 1.449, 1.449, 1.443, + 1.449, 1.448, 1.438, 1.415, 1.387, 1.352, 1.318, 1.284, 1.252, 1.223, 1.199, 1.179, 1.161, 1.149, 1.142, 1.142, 1.142, 1.149, 1.159, 1.174, 1.194, 1.215, 1.242, 1.272, 1.307, 1.341, 1.376, 1.405, 1.431, 1.447, 1.447, 1.444, + 1.448, 1.447, 1.431, 1.405, 1.373, 1.336, 1.301, 1.264, 1.234, 1.204, 1.179, 1.161, 1.143, 1.131, 1.124, 1.123, 1.123, 1.131, 1.141, 1.156, 1.174, 1.197, 1.224, 1.254, 1.288, 1.324, 1.361, 1.394, 1.423, 1.442, 1.444, 1.444, + 1.447, 1.442, 1.424, 1.393, 1.359, 1.322, 1.284, 1.248, 1.216, 1.187, 1.162, 1.143, 1.128, 1.115, 1.109, 1.108, 1.108, 1.113, 1.124, 1.139, 1.156, 1.179, 1.206, 1.236, 1.272, 1.309, 1.347, 1.382, 1.411, 1.435, 1.443, 1.444, + 1.444, 1.439, 1.417, 1.383, 1.347, 1.308, 1.271, 1.233, 1.201, 1.173, 1.147, 1.128, 1.115, 1.101, 1.095, 1.093, 1.093, 1.099, 1.111, 1.124, 1.142, 1.165, 1.191, 1.222, 1.258, 1.296, 1.333, 1.372, 1.404, 1.429, 1.441, 1.442, + 1.443, 1.434, 1.409, 1.375, 1.336, 1.297, 1.257, 1.221, 1.189, 1.159, 1.136, 1.116, 1.101, 1.092, 1.083, 1.082, 1.082, 1.089, 1.099, 1.111, 1.131, 1.153, 1.181, 1.211, 1.246, 1.284, 1.324, 1.361, 1.398, 1.425, 1.441, 1.441, + 1.443, 1.431, 1.405, 1.369, 1.328, 1.287, 1.247, 1.211, 1.178, 1.149, 1.126, 1.107, 1.092, 1.083, 1.075, 1.073, 1.073, 1.082, 1.089, 1.101, 1.121, 1.143, 1.171, 1.201, 1.237, 1.274, 1.314, 1.353, 1.389, 1.421, 1.439, 1.441, + 1.442, 1.429, 1.401, 1.364, 1.323, 1.279, 1.241, 1.205, 1.172, 1.144, 1.119, 1.101, 1.085, 1.075, 1.071, 1.067, 1.067, 1.073, 1.082, 1.096, 1.114, 1.136, 1.163, 1.194, 1.229, 1.268, 1.308, 1.348, 1.387, 1.417, 1.439, 1.439, + 1.443, 1.429, 1.399, 1.362, 1.319, 1.276, 1.237, 1.199, 1.169, 1.141, 1.115, 1.096, 1.081, 1.071, 1.066, 1.063, 1.066, 1.068, 1.078, 1.092, 1.109, 1.132, 1.159, 1.191, 1.226, 1.263, 1.304, 1.346, 1.384, 1.416, 1.438, 1.439, + 1.443, 1.428, 1.399, 1.361, 1.319, 1.276, 1.236, 1.199, 1.167, 1.139, 1.115, 1.096, 1.081, 1.071, 1.064, 1.062, 1.062, 1.067, 1.077, 1.091, 1.109, 1.131, 1.158, 1.189, 1.224, 1.262, 1.303, 1.345, 1.383, 1.416, 1.438, 1.439, + 1.444, 1.429, 1.399, 1.361, 1.319, 1.276, 1.236, 1.199, 1.167, 1.139, 1.116, 1.096, 1.081, 1.071, 1.064, 1.063, 1.063, 1.067, 1.077, 1.091, 1.109, 1.131, 1.159, 1.189, 1.224, 1.262, 1.303, 1.345, 1.384, 1.416, 1.438, 1.441, + 1.444, 1.431, 1.402, 1.364, 1.322, 1.281, 1.239, 1.202, 1.171, 1.142, 1.118, 1.099, 1.084, 1.073, 1.069, 1.065, 1.067, 1.071, 1.079, 1.094, 1.112, 1.135, 1.163, 1.191, 1.227, 1.265, 1.307, 1.348, 1.386, 1.418, 1.438, 1.441, + 1.447, 1.433, 1.406, 1.369, 1.328, 1.286, 1.244, 1.209, 1.177, 1.148, 1.124, 1.105, 1.089, 1.081, 1.073, 1.071, 1.071, 1.079, 1.085, 1.099, 1.118, 1.141, 1.168, 1.198, 1.233, 1.271, 1.312, 1.352, 1.391, 1.422, 1.441, 1.444, + 1.448, 1.438, 1.412, 1.376, 1.335, 1.295, 1.255, 1.218, 1.186, 1.157, 1.134, 1.113, 1.098, 1.089, 1.081, 1.079, 1.079, 1.085, 1.094, 1.107, 1.125, 1.149, 1.175, 1.207, 1.242, 1.281, 1.319, 1.359, 1.396, 1.425, 1.445, 1.447, + 1.449, 1.443, 1.417, 1.384, 1.345, 1.305, 1.266, 1.229, 1.197, 1.169, 1.145, 1.124, 1.111, 1.098, 1.091, 1.089, 1.089, 1.094, 1.107, 1.118, 1.137, 1.159, 1.187, 1.218, 1.253, 1.291, 1.329, 1.369, 1.405, 1.433, 1.447, 1.449, + 1.453, 1.449, 1.425, 1.395, 1.358, 1.318, 1.281, 1.244, 1.211, 1.183, 1.158, 1.138, 1.124, 1.111, 1.104, 1.103, 1.103, 1.107, 1.118, 1.133, 1.151, 1.174, 1.201, 1.232, 1.267, 1.304, 1.344, 1.379, 1.413, 1.437, 1.449, 1.449, + 1.457, 1.453, 1.434, 1.405, 1.371, 1.335, 1.297, 1.261, 1.229, 1.199, 1.174, 1.155, 1.138, 1.126, 1.119, 1.117, 1.117, 1.124, 1.133, 1.149, 1.167, 1.189, 1.217, 1.248, 1.284, 1.319, 1.357, 1.393, 1.423, 1.444, 1.452, 1.452, + 1.459, 1.457, 1.443, 1.418, 1.385, 1.352, 1.314, 1.279, 1.246, 1.218, 1.193, 1.174, 1.155, 1.144, 1.137, 1.136, 1.136, 1.141, 1.151, 1.167, 1.187, 1.208, 1.236, 1.267, 1.301, 1.337, 1.373, 1.405, 1.434, 1.453, 1.455, 1.455, + 1.461, 1.461, 1.454, 1.429, 1.401, 1.369, 1.333, 1.301, 1.269, 1.239, 1.216, 1.193, 1.177, 1.165, 1.158, 1.156, 1.156, 1.161, 1.171, 1.187, 1.208, 1.229, 1.258, 1.288, 1.321, 1.356, 1.389, 1.419, 1.445, 1.459, 1.459, 1.455, + 1.462, 1.462, 1.459, 1.442, 1.418, 1.386, 1.354, 1.322, 1.292, 1.262, 1.239, 1.216, 1.199, 1.187, 1.179, 1.178, 1.178, 1.184, 1.194, 1.208, 1.229, 1.253, 1.279, 1.309, 1.342, 1.375, 1.406, 1.433, 1.452, 1.464, 1.464, 1.454, + 1.461, 1.465, 1.465, 1.454, 1.431, 1.405, 1.376, 1.346, 1.316, 1.288, 1.262, 1.242, 1.223, 1.212, 1.205, 1.203, 1.203, 1.208, 1.218, 1.234, 1.253, 1.279, 1.305, 1.334, 1.363, 1.393, 1.421, 1.445, 1.461, 1.465, 1.464, 1.452, + 1.459, 1.465, 1.466, 1.461, 1.443, 1.421, 1.395, 1.368, 1.341, 1.316, 1.288, 1.268, 1.251, 1.238, 1.232, 1.229, 1.229, 1.235, 1.246, 1.261, 1.279, 1.305, 1.331, 1.356, 1.385, 1.411, 1.435, 1.454, 1.466, 1.466, 1.464, 1.451, + 1.454, 1.465, 1.467, 1.466, 1.456, 1.436, 1.414, 1.389, 1.367, 1.341, 1.318, 1.297, 1.279, 1.269, 1.261, 1.259, 1.259, 1.265, 1.274, 1.288, 1.308, 1.331, 1.355, 1.381, 1.404, 1.428, 1.447, 1.462, 1.468, 1.467, 1.457, 1.445, + 1.447, 1.459, 1.466, 1.467, 1.463, 1.451, 1.434, 1.411, 1.389, 1.367, 1.344, 1.325, 1.311, 1.297, 1.292, 1.289, 1.289, 1.295, 1.303, 1.317, 1.336, 1.356, 1.381, 1.402, 1.423, 1.441, 1.457, 1.467, 1.468, 1.463, 1.451, 1.439, + 1.438, 1.449, 1.462, 1.464, 1.464, 1.459, 1.446, 1.429, 1.408, 1.388, 1.369, 1.353, 1.339, 1.329, 1.321, 1.321, 1.321, 1.325, 1.333, 1.348, 1.362, 1.379, 1.401, 1.421, 1.439, 1.454, 1.463, 1.465, 1.465, 1.456, 1.442, 1.427, + 1.429, 1.439, 1.454, 1.464, 1.464, 1.459, 1.449, 1.435, 1.421, 1.402, 1.385, 1.369, 1.353, 1.341, 1.338, 1.337, 1.337, 1.338, 1.348, 1.362, 1.378, 1.395, 1.411, 1.429, 1.445, 1.455, 1.463, 1.464, 1.457, 1.447, 1.427, 1.419 + ] + }, + { + "ct": 5000, + "table": + [ + 2.163, 2.177, 2.194, 2.196, 2.197, 2.192, 2.181, 2.161, 2.139, 2.113, 2.088, 2.063, 2.047, 2.041, 2.036, 2.036, 2.036, 2.037, 2.046, 2.059, 2.083, 2.113, 2.135, 2.158, 2.181, 2.193, 2.205, 2.205, 2.202, 2.189, 2.171, 2.158, + 2.169, 2.184, 2.195, 2.196, 2.194, 2.182, 2.163, 2.141, 2.116, 2.088, 2.063, 2.042, 2.025, 2.013, 2.004, 2.004, 2.006, 2.011, 2.022, 2.038, 2.059, 2.083, 2.113, 2.137, 2.162, 2.182, 2.197, 2.204, 2.203, 2.199, 2.183, 2.171, + 2.177, 2.187, 2.193, 2.193, 2.184, 2.166, 2.142, 2.116, 2.087, 2.057, 2.033, 2.008, 1.991, 1.977, 1.969, 1.969, 1.969, 1.975, 1.988, 2.006, 2.028, 2.055, 2.083, 2.114, 2.139, 2.166, 2.187, 2.199, 2.202, 2.201, 2.189, 2.179, + 2.183, 2.189, 2.192, 2.186, 2.172, 2.146, 2.119, 2.089, 2.058, 2.026, 2.001, 1.975, 1.956, 1.942, 1.934, 1.932, 1.933, 1.941, 1.955, 1.971, 1.995, 2.023, 2.055, 2.084, 2.119, 2.146, 2.171, 2.191, 2.201, 2.201, 2.194, 2.183, + 2.186, 2.189, 2.189, 2.177, 2.158, 2.127, 2.096, 2.059, 2.026, 1.998, 1.969, 1.944, 1.925, 1.911, 1.901, 1.901, 1.903, 1.912, 1.924, 1.941, 1.964, 1.995, 2.023, 2.058, 2.091, 2.126, 2.155, 2.181, 2.195, 2.199, 2.198, 2.188, + 2.189, 2.189, 2.184, 2.166, 2.138, 2.108, 2.071, 2.036, 1.999, 1.969, 1.941, 1.914, 1.894, 1.879, 1.871, 1.871, 1.872, 1.879, 1.893, 1.913, 1.937, 1.964, 1.997, 2.029, 2.065, 2.104, 2.137, 2.169, 2.187, 2.199, 2.199, 2.189, + 2.187, 2.186, 2.176, 2.154, 2.123, 2.087, 2.044, 2.011, 1.974, 1.941, 1.913, 1.887, 1.868, 1.852, 1.844, 1.843, 1.844, 1.852, 1.866, 1.885, 1.912, 1.937, 1.972, 2.004, 2.042, 2.081, 2.119, 2.154, 2.179, 2.195, 2.196, 2.193, + 2.187, 2.181, 2.167, 2.141, 2.103, 2.062, 2.023, 1.984, 1.947, 1.916, 1.887, 1.864, 1.841, 1.828, 1.821, 1.819, 1.819, 1.828, 1.842, 1.862, 1.885, 1.913, 1.945, 1.982, 2.021, 2.058, 2.102, 2.137, 2.168, 2.192, 2.193, 2.193, + 2.182, 2.181, 2.161, 2.127, 2.083, 2.044, 2.002, 1.961, 1.924, 1.891, 1.864, 1.841, 1.819, 1.806, 1.797, 1.797, 1.797, 1.805, 1.819, 1.841, 1.862, 1.892, 1.924, 1.959, 1.999, 2.041, 2.082, 2.123, 2.161, 2.185, 2.191, 2.192, + 2.182, 2.172, 2.149, 2.112, 2.069, 2.026, 1.982, 1.941, 1.904, 1.871, 1.841, 1.819, 1.799, 1.785, 1.776, 1.776, 1.778, 1.784, 1.798, 1.819, 1.841, 1.869, 1.903, 1.939, 1.977, 2.021, 2.067, 2.108, 2.145, 2.174, 2.189, 2.191, + 2.181, 2.167, 2.139, 2.098, 2.056, 2.006, 1.965, 1.921, 1.883, 1.851, 1.823, 1.799, 1.783, 1.767, 1.759, 1.758, 1.758, 1.767, 1.783, 1.798, 1.825, 1.851, 1.883, 1.919, 1.959, 2.004, 2.049, 2.094, 2.136, 2.167, 2.187, 2.189, + 2.179, 2.163, 2.131, 2.087, 2.041, 1.994, 1.948, 1.907, 1.871, 1.835, 1.806, 1.784, 1.767, 1.754, 1.744, 1.742, 1.742, 1.752, 1.767, 1.783, 1.808, 1.838, 1.869, 1.905, 1.945, 1.989, 2.036, 2.083, 2.128, 2.159, 2.183, 2.187, + 2.178, 2.161, 2.126, 2.082, 2.032, 1.982, 1.936, 1.896, 1.857, 1.823, 1.795, 1.772, 1.754, 1.744, 1.732, 1.731, 1.732, 1.742, 1.752, 1.771, 1.796, 1.824, 1.857, 1.895, 1.934, 1.977, 2.024, 2.071, 2.116, 2.154, 2.181, 2.185, + 2.177, 2.157, 2.121, 2.074, 2.025, 1.973, 1.927, 1.886, 1.849, 1.815, 1.787, 1.765, 1.746, 1.732, 1.725, 1.722, 1.724, 1.732, 1.743, 1.762, 1.786, 1.813, 1.848, 1.886, 1.924, 1.969, 2.017, 2.066, 2.111, 2.153, 2.179, 2.183, + 2.177, 2.155, 2.119, 2.072, 2.022, 1.969, 1.925, 1.881, 1.844, 1.811, 1.782, 1.758, 1.739, 1.725, 1.721, 1.717, 1.721, 1.724, 1.739, 1.757, 1.781, 1.809, 1.842, 1.879, 1.921, 1.965, 2.012, 2.062, 2.108, 2.151, 2.179, 2.182, + 2.177, 2.156, 2.121, 2.071, 2.021, 1.968, 1.922, 1.879, 1.842, 1.811, 1.781, 1.757, 1.739, 1.725, 1.717, 1.715, 1.715, 1.723, 1.737, 1.757, 1.779, 1.808, 1.841, 1.877, 1.918, 1.963, 2.011, 2.061, 2.107, 2.148, 2.179, 2.183, + 2.178, 2.157, 2.121, 2.072, 2.021, 1.969, 1.922, 1.881, 1.842, 1.811, 1.781, 1.758, 1.739, 1.726, 1.718, 1.717, 1.718, 1.723, 1.737, 1.757, 1.781, 1.809, 1.841, 1.877, 1.918, 1.964, 2.012, 2.061, 2.108, 2.149, 2.179, 2.183, + 2.178, 2.159, 2.124, 2.074, 2.024, 1.974, 1.926, 1.885, 1.847, 1.813, 1.784, 1.762, 1.743, 1.731, 1.725, 1.719, 1.723, 1.728, 1.742, 1.762, 1.785, 1.814, 1.847, 1.881, 1.922, 1.966, 2.017, 2.065, 2.109, 2.151, 2.181, 2.184, + 2.181, 2.163, 2.129, 2.082, 2.032, 1.982, 1.934, 1.891, 1.854, 1.822, 1.794, 1.769, 1.751, 1.739, 1.731, 1.727, 1.728, 1.739, 1.747, 1.768, 1.791, 1.821, 1.852, 1.889, 1.929, 1.972, 2.022, 2.071, 2.117, 2.155, 2.182, 2.189, + 2.184, 2.169, 2.135, 2.091, 2.041, 1.994, 1.947, 1.902, 1.865, 1.833, 1.805, 1.779, 1.762, 1.751, 1.739, 1.739, 1.739, 1.747, 1.761, 1.779, 1.803, 1.831, 1.864, 1.898, 1.941, 1.984, 2.033, 2.079, 2.123, 2.163, 2.188, 2.193, + 2.185, 2.174, 2.142, 2.099, 2.054, 2.004, 1.959, 1.917, 1.879, 1.846, 1.819, 1.794, 1.779, 1.762, 1.754, 1.753, 1.753, 1.761, 1.777, 1.793, 1.816, 1.843, 1.877, 1.913, 1.953, 1.995, 2.043, 2.091, 2.135, 2.169, 2.191, 2.196, + 2.191, 2.179, 2.154, 2.118, 2.069, 2.023, 1.977, 1.935, 1.898, 1.865, 1.834, 1.813, 1.794, 1.779, 1.769, 1.769, 1.769, 1.777, 1.793, 1.809, 1.834, 1.863, 1.895, 1.929, 1.972, 2.015, 2.061, 2.105, 2.145, 2.178, 2.195, 2.199, + 2.197, 2.188, 2.166, 2.129, 2.087, 2.041, 1.997, 1.956, 1.918, 1.884, 1.855, 1.834, 1.813, 1.798, 1.788, 1.788, 1.788, 1.796, 1.809, 1.832, 1.853, 1.881, 1.912, 1.949, 1.991, 2.033, 2.076, 2.119, 2.159, 2.187, 2.202, 2.205, + 2.202, 2.197, 2.176, 2.148, 2.106, 2.065, 2.021, 1.979, 1.943, 1.909, 1.879, 1.855, 1.835, 1.819, 1.811, 1.811, 1.811, 1.818, 1.832, 1.853, 1.875, 1.904, 1.937, 1.972, 2.013, 2.055, 2.097, 2.138, 2.175, 2.197, 2.206, 2.207, + 2.205, 2.202, 2.189, 2.162, 2.126, 2.084, 2.044, 2.004, 1.967, 1.935, 1.907, 1.879, 1.861, 1.845, 1.838, 1.835, 1.835, 1.844, 1.855, 1.875, 1.902, 1.928, 1.961, 1.998, 2.033, 2.076, 2.118, 2.155, 2.186, 2.205, 2.208, 2.208, + 2.207, 2.205, 2.195, 2.175, 2.145, 2.108, 2.069, 2.029, 1.996, 1.963, 1.934, 1.908, 1.885, 1.872, 1.864, 1.863, 1.863, 1.869, 1.884, 1.902, 1.928, 1.956, 1.989, 2.023, 2.059, 2.099, 2.137, 2.172, 2.199, 2.212, 2.213, 2.209, + 2.207, 2.207, 2.203, 2.188, 2.162, 2.128, 2.094, 2.058, 2.023, 1.993, 1.963, 1.936, 1.916, 1.899, 1.893, 1.892, 1.893, 1.899, 1.912, 1.929, 1.956, 1.986, 2.016, 2.049, 2.084, 2.121, 2.156, 2.187, 2.208, 2.215, 2.215, 2.208, + 2.205, 2.208, 2.209, 2.199, 2.178, 2.149, 2.117, 2.083, 2.052, 2.023, 1.993, 1.967, 1.947, 1.933, 1.925, 1.922, 1.922, 1.929, 1.943, 1.961, 1.986, 2.015, 2.045, 2.076, 2.109, 2.143, 2.173, 2.198, 2.214, 2.218, 2.216, 2.205, + 2.201, 2.207, 2.211, 2.211, 2.193, 2.168, 2.141, 2.112, 2.082, 2.052, 2.025, 2.001, 1.981, 1.967, 1.959, 1.958, 1.958, 1.967, 1.975, 1.992, 2.018, 2.046, 2.076, 2.105, 2.136, 2.163, 2.189, 2.208, 2.217, 2.217, 2.212, 2.203, + 2.194, 2.204, 2.212, 2.213, 2.203, 2.187, 2.165, 2.139, 2.112, 2.083, 2.055, 2.034, 2.016, 2.001, 1.993, 1.993, 1.994, 1.999, 2.011, 2.027, 2.051, 2.077, 2.105, 2.133, 2.158, 2.181, 2.202, 2.217, 2.218, 2.218, 2.206, 2.193, + 2.185, 2.198, 2.213, 2.214, 2.212, 2.201, 2.184, 2.163, 2.135, 2.111, 2.089, 2.071, 2.052, 2.039, 2.032, 2.031, 2.031, 2.036, 2.048, 2.065, 2.085, 2.106, 2.131, 2.155, 2.178, 2.198, 2.212, 2.219, 2.219, 2.215, 2.201, 2.185, + 2.176, 2.191, 2.208, 2.217, 2.216, 2.205, 2.195, 2.177, 2.156, 2.133, 2.109, 2.089, 2.071, 2.055, 2.053, 2.053, 2.053, 2.057, 2.065, 2.085, 2.105, 2.123, 2.149, 2.171, 2.192, 2.205, 2.217, 2.219, 2.219, 2.202, 2.185, 2.181 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.518, 2.513, 2.503, 2.496, 2.488, 2.484, 2.485, 2.485, 2.486, 2.487, 2.487, 2.489, 2.494, 2.496, 2.496, 2.497, 2.499, 2.499, 2.496, 2.495, 2.492, 2.491, 2.491, 2.491, 2.492, 2.493, 2.495, 2.501, 2.508, 2.516, 2.528, 2.533, + 2.515, 2.508, 2.495, 2.487, 2.483, 2.481, 2.482, 2.483, 2.485, 2.487, 2.489, 2.491, 2.495, 2.497, 2.498, 2.501, 2.502, 2.502, 2.499, 2.496, 2.494, 2.491, 2.491, 2.489, 2.489, 2.491, 2.493, 2.496, 2.502, 2.511, 2.521, 2.531, + 2.507, 2.495, 2.486, 2.482, 2.478, 2.477, 2.481, 2.482, 2.484, 2.488, 2.491, 2.495, 2.499, 2.502, 2.506, 2.508, 2.509, 2.508, 2.505, 2.501, 2.497, 2.493, 2.491, 2.489, 2.488, 2.489, 2.489, 2.492, 2.496, 2.501, 2.511, 2.524, + 2.501, 2.487, 2.482, 2.481, 2.478, 2.477, 2.481, 2.483, 2.487, 2.491, 2.501, 2.503, 2.509, 2.511, 2.518, 2.519, 2.519, 2.519, 2.516, 2.509, 2.504, 2.498, 2.495, 2.493, 2.489, 2.489, 2.488, 2.489, 2.492, 2.498, 2.505, 2.523, + 2.499, 2.484, 2.481, 2.476, 2.476, 2.476, 2.481, 2.485, 2.492, 2.501, 2.509, 2.514, 2.519, 2.524, 2.528, 2.531, 2.533, 2.533, 2.525, 2.519, 2.514, 2.507, 2.501, 2.497, 2.493, 2.489, 2.489, 2.488, 2.491, 2.494, 2.501, 2.514, + 2.497, 2.483, 2.478, 2.476, 2.476, 2.478, 2.482, 2.491, 2.499, 2.509, 2.515, 2.522, 2.528, 2.535, 2.539, 2.541, 2.543, 2.542, 2.539, 2.529, 2.522, 2.516, 2.507, 2.502, 2.497, 2.491, 2.489, 2.488, 2.489, 2.492, 2.498, 2.514, + 2.492, 2.479, 2.476, 2.475, 2.476, 2.481, 2.488, 2.496, 2.505, 2.516, 2.524, 2.532, 2.541, 2.545, 2.552, 2.554, 2.554, 2.554, 2.548, 2.541, 2.532, 2.522, 2.516, 2.507, 2.502, 2.494, 2.491, 2.489, 2.489, 2.492, 2.494, 2.511, + 2.491, 2.479, 2.476, 2.477, 2.478, 2.482, 2.491, 2.502, 2.514, 2.524, 2.533, 2.543, 2.548, 2.555, 2.562, 2.566, 2.567, 2.562, 2.557, 2.551, 2.541, 2.531, 2.523, 2.512, 2.506, 2.498, 2.493, 2.491, 2.491, 2.491, 2.493, 2.507, + 2.489, 2.478, 2.476, 2.477, 2.481, 2.485, 2.494, 2.507, 2.517, 2.529, 2.542, 2.548, 2.557, 2.563, 2.567, 2.571, 2.572, 2.571, 2.565, 2.558, 2.549, 2.538, 2.528, 2.521, 2.509, 2.501, 2.494, 2.492, 2.491, 2.491, 2.491, 2.505, + 2.488, 2.478, 2.477, 2.478, 2.482, 2.489, 2.499, 2.509, 2.523, 2.538, 2.548, 2.556, 2.563, 2.568, 2.573, 2.577, 2.578, 2.577, 2.573, 2.564, 2.555, 2.543, 2.535, 2.524, 2.515, 2.504, 2.495, 2.492, 2.489, 2.488, 2.489, 2.501, + 2.486, 2.476, 2.475, 2.477, 2.483, 2.491, 2.503, 2.515, 2.529, 2.542, 2.553, 2.562, 2.568, 2.574, 2.581, 2.583, 2.584, 2.581, 2.578, 2.571, 2.562, 2.551, 2.539, 2.531, 2.517, 2.508, 2.497, 2.492, 2.488, 2.487, 2.489, 2.498, + 2.486, 2.476, 2.475, 2.479, 2.484, 2.492, 2.504, 2.519, 2.533, 2.544, 2.557, 2.566, 2.573, 2.581, 2.584, 2.588, 2.588, 2.586, 2.581, 2.575, 2.567, 2.555, 2.546, 2.534, 2.517, 2.509, 2.499, 2.492, 2.489, 2.485, 2.488, 2.497, + 2.487, 2.476, 2.476, 2.479, 2.486, 2.494, 2.506, 2.521, 2.535, 2.549, 2.559, 2.571, 2.578, 2.583, 2.589, 2.591, 2.591, 2.591, 2.587, 2.579, 2.571, 2.559, 2.551, 2.538, 2.523, 2.513, 2.503, 2.493, 2.489, 2.486, 2.487, 2.499, + 2.486, 2.475, 2.475, 2.479, 2.486, 2.495, 2.509, 2.525, 2.541, 2.555, 2.563, 2.573, 2.582, 2.588, 2.591, 2.594, 2.595, 2.592, 2.591, 2.585, 2.574, 2.564, 2.552, 2.541, 2.525, 2.514, 2.503, 2.493, 2.489, 2.486, 2.486, 2.501, + 2.486, 2.475, 2.475, 2.479, 2.488, 2.497, 2.509, 2.526, 2.542, 2.556, 2.564, 2.575, 2.584, 2.591, 2.595, 2.596, 2.597, 2.595, 2.592, 2.587, 2.577, 2.568, 2.554, 2.542, 2.527, 2.515, 2.504, 2.494, 2.491, 2.487, 2.487, 2.505, + 2.484, 2.476, 2.475, 2.478, 2.488, 2.498, 2.509, 2.526, 2.542, 2.555, 2.565, 2.576, 2.584, 2.589, 2.595, 2.598, 2.598, 2.597, 2.593, 2.587, 2.578, 2.569, 2.556, 2.543, 2.528, 2.515, 2.504, 2.494, 2.489, 2.485, 2.485, 2.501, + 2.484, 2.475, 2.475, 2.478, 2.489, 2.498, 2.509, 2.524, 2.539, 2.553, 2.565, 2.576, 2.584, 2.589, 2.594, 2.597, 2.597, 2.596, 2.593, 2.587, 2.577, 2.569, 2.555, 2.543, 2.529, 2.515, 2.503, 2.496, 2.491, 2.485, 2.486, 2.497, + 2.484, 2.474, 2.474, 2.479, 2.487, 2.497, 2.509, 2.523, 2.539, 2.551, 2.563, 2.574, 2.581, 2.587, 2.592, 2.595, 2.596, 2.595, 2.591, 2.584, 2.574, 2.567, 2.554, 2.541, 2.526, 2.514, 2.503, 2.495, 2.489, 2.485, 2.486, 2.497, + 2.484, 2.475, 2.475, 2.478, 2.485, 2.494, 2.507, 2.522, 2.535, 2.546, 2.559, 2.568, 2.579, 2.584, 2.589, 2.592, 2.593, 2.592, 2.588, 2.579, 2.571, 2.562, 2.551, 2.537, 2.524, 2.514, 2.501, 2.493, 2.489, 2.486, 2.487, 2.498, + 2.485, 2.476, 2.475, 2.477, 2.485, 2.491, 2.506, 2.519, 2.531, 2.544, 2.555, 2.563, 2.571, 2.581, 2.584, 2.589, 2.589, 2.588, 2.583, 2.576, 2.566, 2.555, 2.546, 2.534, 2.522, 2.511, 2.499, 2.491, 2.488, 2.486, 2.487, 2.502, + 2.487, 2.477, 2.475, 2.477, 2.483, 2.489, 2.503, 2.515, 2.525, 2.541, 2.551, 2.559, 2.567, 2.573, 2.579, 2.582, 2.583, 2.582, 2.576, 2.569, 2.562, 2.549, 2.542, 2.527, 2.518, 2.505, 2.497, 2.491, 2.489, 2.487, 2.487, 2.502, + 2.487, 2.478, 2.475, 2.477, 2.482, 2.489, 2.497, 2.512, 2.522, 2.536, 2.544, 2.551, 2.562, 2.566, 2.573, 2.578, 2.578, 2.575, 2.571, 2.564, 2.556, 2.548, 2.536, 2.523, 2.513, 2.503, 2.493, 2.489, 2.487, 2.486, 2.487, 2.502, + 2.488, 2.479, 2.477, 2.478, 2.482, 2.488, 2.496, 2.505, 2.516, 2.528, 2.538, 2.547, 2.553, 2.561, 2.565, 2.569, 2.569, 2.568, 2.564, 2.558, 2.549, 2.541, 2.531, 2.517, 2.509, 2.499, 2.492, 2.488, 2.486, 2.484, 2.486, 2.503, + 2.492, 2.482, 2.479, 2.479, 2.482, 2.487, 2.491, 2.501, 2.512, 2.523, 2.531, 2.541, 2.549, 2.552, 2.558, 2.561, 2.562, 2.559, 2.558, 2.552, 2.542, 2.535, 2.525, 2.514, 2.505, 2.497, 2.491, 2.486, 2.485, 2.484, 2.487, 2.503, + 2.495, 2.483, 2.479, 2.479, 2.482, 2.487, 2.491, 2.498, 2.508, 2.515, 2.526, 2.533, 2.541, 2.547, 2.551, 2.554, 2.555, 2.554, 2.552, 2.541, 2.537, 2.527, 2.519, 2.507, 2.502, 2.495, 2.488, 2.485, 2.484, 2.485, 2.488, 2.503, + 2.499, 2.485, 2.483, 2.481, 2.482, 2.486, 2.489, 2.494, 2.504, 2.511, 2.519, 2.527, 2.531, 2.539, 2.542, 2.546, 2.546, 2.545, 2.539, 2.535, 2.527, 2.522, 2.509, 2.505, 2.497, 2.491, 2.486, 2.485, 2.485, 2.487, 2.491, 2.506, + 2.499, 2.489, 2.483, 2.481, 2.481, 2.483, 2.488, 2.491, 2.499, 2.506, 2.512, 2.519, 2.524, 2.529, 2.535, 2.537, 2.536, 2.534, 2.532, 2.525, 2.522, 2.514, 2.506, 2.499, 2.492, 2.489, 2.485, 2.484, 2.485, 2.488, 2.492, 2.506, + 2.507, 2.494, 2.486, 2.483, 2.482, 2.482, 2.486, 2.488, 2.495, 2.501, 2.507, 2.511, 2.517, 2.519, 2.523, 2.525, 2.525, 2.523, 2.523, 2.521, 2.514, 2.506, 2.502, 2.496, 2.491, 2.488, 2.485, 2.485, 2.487, 2.489, 2.496, 2.516, + 2.511, 2.503, 2.489, 2.486, 2.485, 2.485, 2.485, 2.487, 2.489, 2.495, 2.501, 2.505, 2.509, 2.514, 2.517, 2.519, 2.518, 2.517, 2.515, 2.511, 2.505, 2.501, 2.495, 2.492, 2.488, 2.486, 2.485, 2.486, 2.488, 2.492, 2.499, 2.519, + 2.517, 2.505, 2.494, 2.489, 2.487, 2.486, 2.486, 2.486, 2.489, 2.491, 2.496, 2.499, 2.503, 2.506, 2.508, 2.509, 2.511, 2.509, 2.507, 2.503, 2.501, 2.496, 2.493, 2.489, 2.485, 2.485, 2.486, 2.487, 2.491, 2.495, 2.505, 2.526, + 2.526, 2.516, 2.504, 2.494, 2.493, 2.489, 2.489, 2.489, 2.489, 2.491, 2.496, 2.498, 2.501, 2.504, 2.506, 2.506, 2.506, 2.505, 2.503, 2.501, 2.499, 2.496, 2.494, 2.491, 2.487, 2.486, 2.489, 2.492, 2.497, 2.505, 2.517, 2.528, + 2.529, 2.526, 2.508, 2.502, 2.501, 2.498, 2.495, 2.495, 2.495, 2.495, 2.497, 2.499, 2.501, 2.503, 2.504, 2.506, 2.505, 2.505, 2.503, 2.501, 2.499, 2.496, 2.495, 2.494, 2.492, 2.494, 2.494, 2.498, 2.504, 2.513, 2.525, 2.536 + ] + }, + { + "ct": 5000, + "table": + [ + 1.427, 1.425, 1.423, 1.422, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.422, 1.423, 1.424, 1.425, 1.426, 1.426, 1.426, 1.425, 1.425, 1.424, 1.422, 1.421, 1.421, 1.421, 1.421, 1.422, 1.422, 1.422, 1.424, 1.424, 1.426, 1.428, + 1.426, 1.424, 1.422, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.422, 1.423, 1.424, 1.425, 1.426, 1.427, 1.427, 1.427, 1.426, 1.425, 1.424, 1.422, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.422, 1.424, 1.427, + 1.423, 1.421, 1.421, 1.419, 1.419, 1.418, 1.419, 1.419, 1.421, 1.423, 1.425, 1.426, 1.428, 1.429, 1.431, 1.431, 1.431, 1.431, 1.429, 1.426, 1.424, 1.422, 1.421, 1.421, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.422, 1.425, + 1.422, 1.419, 1.419, 1.419, 1.418, 1.418, 1.419, 1.421, 1.422, 1.426, 1.428, 1.429, 1.433, 1.434, 1.436, 1.436, 1.436, 1.434, 1.432, 1.429, 1.426, 1.424, 1.423, 1.422, 1.421, 1.419, 1.419, 1.419, 1.419, 1.419, 1.421, 1.425, + 1.422, 1.419, 1.419, 1.418, 1.418, 1.419, 1.419, 1.422, 1.425, 1.429, 1.432, 1.435, 1.436, 1.438, 1.439, 1.439, 1.441, 1.439, 1.435, 1.433, 1.429, 1.427, 1.425, 1.423, 1.422, 1.419, 1.419, 1.418, 1.418, 1.418, 1.419, 1.425, + 1.422, 1.419, 1.418, 1.418, 1.418, 1.419, 1.421, 1.424, 1.428, 1.432, 1.436, 1.437, 1.439, 1.442, 1.443, 1.445, 1.444, 1.443, 1.441, 1.436, 1.434, 1.431, 1.427, 1.425, 1.422, 1.421, 1.419, 1.418, 1.418, 1.418, 1.419, 1.424, + 1.422, 1.418, 1.417, 1.418, 1.419, 1.421, 1.423, 1.427, 1.431, 1.436, 1.438, 1.442, 1.444, 1.446, 1.448, 1.449, 1.448, 1.446, 1.445, 1.441, 1.436, 1.434, 1.429, 1.427, 1.423, 1.421, 1.419, 1.418, 1.418, 1.418, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.418, 1.419, 1.421, 1.424, 1.429, 1.434, 1.438, 1.442, 1.445, 1.447, 1.449, 1.451, 1.452, 1.452, 1.449, 1.447, 1.445, 1.441, 1.436, 1.433, 1.429, 1.425, 1.422, 1.419, 1.419, 1.418, 1.417, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.419, 1.419, 1.423, 1.426, 1.432, 1.436, 1.441, 1.445, 1.448, 1.449, 1.452, 1.453, 1.454, 1.454, 1.453, 1.451, 1.447, 1.444, 1.439, 1.433, 1.431, 1.427, 1.422, 1.421, 1.419, 1.418, 1.417, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.419, 1.421, 1.423, 1.428, 1.433, 1.439, 1.443, 1.448, 1.449, 1.453, 1.454, 1.455, 1.456, 1.456, 1.454, 1.453, 1.449, 1.446, 1.441, 1.437, 1.433, 1.429, 1.423, 1.421, 1.419, 1.418, 1.416, 1.417, 1.423, + 1.421, 1.417, 1.417, 1.419, 1.422, 1.424, 1.429, 1.435, 1.441, 1.444, 1.449, 1.453, 1.454, 1.456, 1.458, 1.459, 1.458, 1.456, 1.454, 1.451, 1.448, 1.442, 1.439, 1.435, 1.429, 1.426, 1.421, 1.419, 1.418, 1.416, 1.417, 1.422, + 1.419, 1.418, 1.417, 1.419, 1.422, 1.425, 1.429, 1.436, 1.442, 1.446, 1.451, 1.454, 1.456, 1.458, 1.461, 1.461, 1.461, 1.459, 1.456, 1.453, 1.451, 1.446, 1.441, 1.436, 1.431, 1.427, 1.422, 1.419, 1.418, 1.416, 1.417, 1.422, + 1.419, 1.418, 1.418, 1.421, 1.423, 1.426, 1.431, 1.437, 1.444, 1.449, 1.452, 1.456, 1.458, 1.461, 1.462, 1.463, 1.463, 1.461, 1.458, 1.454, 1.452, 1.447, 1.443, 1.438, 1.432, 1.428, 1.423, 1.421, 1.419, 1.417, 1.417, 1.421, + 1.419, 1.418, 1.417, 1.421, 1.423, 1.428, 1.432, 1.439, 1.445, 1.451, 1.453, 1.457, 1.459, 1.462, 1.464, 1.465, 1.465, 1.463, 1.461, 1.457, 1.453, 1.449, 1.444, 1.441, 1.432, 1.429, 1.425, 1.421, 1.419, 1.417, 1.418, 1.422, + 1.418, 1.417, 1.417, 1.419, 1.423, 1.428, 1.433, 1.439, 1.446, 1.451, 1.453, 1.457, 1.461, 1.464, 1.465, 1.466, 1.466, 1.464, 1.462, 1.459, 1.454, 1.451, 1.445, 1.441, 1.436, 1.429, 1.425, 1.422, 1.421, 1.417, 1.417, 1.423, + 1.417, 1.416, 1.416, 1.419, 1.423, 1.428, 1.433, 1.441, 1.446, 1.451, 1.454, 1.458, 1.461, 1.463, 1.465, 1.466, 1.466, 1.465, 1.463, 1.459, 1.454, 1.451, 1.446, 1.441, 1.437, 1.431, 1.426, 1.422, 1.421, 1.418, 1.418, 1.423, + 1.417, 1.416, 1.417, 1.418, 1.423, 1.428, 1.433, 1.439, 1.445, 1.451, 1.453, 1.457, 1.461, 1.463, 1.465, 1.466, 1.466, 1.464, 1.462, 1.459, 1.454, 1.451, 1.446, 1.441, 1.437, 1.431, 1.426, 1.422, 1.419, 1.417, 1.417, 1.422, + 1.417, 1.416, 1.416, 1.418, 1.422, 1.428, 1.433, 1.438, 1.444, 1.449, 1.453, 1.456, 1.459, 1.462, 1.464, 1.465, 1.465, 1.463, 1.461, 1.458, 1.453, 1.449, 1.445, 1.441, 1.435, 1.429, 1.426, 1.421, 1.419, 1.417, 1.417, 1.422, + 1.418, 1.416, 1.416, 1.418, 1.421, 1.426, 1.432, 1.438, 1.443, 1.447, 1.451, 1.454, 1.458, 1.459, 1.462, 1.463, 1.463, 1.462, 1.459, 1.455, 1.451, 1.447, 1.443, 1.439, 1.434, 1.429, 1.425, 1.421, 1.419, 1.417, 1.417, 1.422, + 1.418, 1.416, 1.416, 1.418, 1.421, 1.425, 1.431, 1.435, 1.442, 1.445, 1.449, 1.452, 1.455, 1.458, 1.458, 1.461, 1.461, 1.459, 1.456, 1.453, 1.449, 1.445, 1.442, 1.436, 1.433, 1.427, 1.425, 1.421, 1.419, 1.418, 1.418, 1.422, + 1.419, 1.416, 1.415, 1.417, 1.419, 1.424, 1.429, 1.434, 1.439, 1.443, 1.446, 1.449, 1.452, 1.454, 1.456, 1.457, 1.457, 1.456, 1.453, 1.451, 1.447, 1.443, 1.441, 1.435, 1.431, 1.426, 1.424, 1.421, 1.419, 1.418, 1.418, 1.422, + 1.419, 1.416, 1.415, 1.416, 1.419, 1.422, 1.426, 1.433, 1.437, 1.441, 1.444, 1.447, 1.449, 1.452, 1.453, 1.455, 1.455, 1.453, 1.451, 1.447, 1.444, 1.441, 1.438, 1.432, 1.428, 1.424, 1.421, 1.419, 1.418, 1.417, 1.417, 1.421, + 1.419, 1.416, 1.415, 1.416, 1.418, 1.421, 1.425, 1.431, 1.435, 1.438, 1.442, 1.445, 1.446, 1.449, 1.451, 1.451, 1.451, 1.451, 1.447, 1.445, 1.443, 1.439, 1.434, 1.431, 1.427, 1.422, 1.421, 1.418, 1.417, 1.417, 1.417, 1.421, + 1.418, 1.416, 1.415, 1.416, 1.417, 1.421, 1.423, 1.428, 1.433, 1.437, 1.439, 1.442, 1.444, 1.446, 1.448, 1.449, 1.449, 1.447, 1.445, 1.443, 1.439, 1.437, 1.432, 1.429, 1.425, 1.422, 1.419, 1.417, 1.417, 1.416, 1.416, 1.419, + 1.418, 1.416, 1.416, 1.416, 1.417, 1.421, 1.422, 1.426, 1.429, 1.433, 1.436, 1.438, 1.441, 1.443, 1.445, 1.446, 1.445, 1.445, 1.443, 1.439, 1.437, 1.434, 1.431, 1.427, 1.424, 1.421, 1.419, 1.417, 1.417, 1.416, 1.416, 1.421, + 1.419, 1.417, 1.416, 1.416, 1.417, 1.421, 1.422, 1.424, 1.427, 1.429, 1.432, 1.436, 1.437, 1.439, 1.442, 1.443, 1.443, 1.441, 1.439, 1.437, 1.434, 1.431, 1.429, 1.425, 1.422, 1.421, 1.419, 1.417, 1.416, 1.416, 1.417, 1.419, + 1.421, 1.418, 1.416, 1.417, 1.418, 1.421, 1.421, 1.423, 1.424, 1.427, 1.429, 1.432, 1.434, 1.436, 1.438, 1.439, 1.439, 1.438, 1.436, 1.434, 1.431, 1.429, 1.426, 1.423, 1.422, 1.421, 1.418, 1.417, 1.417, 1.417, 1.417, 1.421, + 1.423, 1.419, 1.418, 1.418, 1.419, 1.419, 1.421, 1.422, 1.423, 1.424, 1.427, 1.429, 1.432, 1.432, 1.434, 1.435, 1.435, 1.434, 1.433, 1.431, 1.429, 1.426, 1.424, 1.422, 1.421, 1.419, 1.418, 1.417, 1.417, 1.417, 1.418, 1.421, + 1.425, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.421, 1.421, 1.423, 1.424, 1.426, 1.428, 1.431, 1.431, 1.432, 1.432, 1.431, 1.431, 1.428, 1.425, 1.425, 1.422, 1.421, 1.419, 1.419, 1.418, 1.418, 1.418, 1.418, 1.419, 1.425, + 1.426, 1.422, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.421, 1.422, 1.424, 1.426, 1.427, 1.428, 1.429, 1.429, 1.429, 1.427, 1.424, 1.423, 1.422, 1.421, 1.419, 1.418, 1.418, 1.418, 1.418, 1.418, 1.418, 1.419, 1.426, + 1.428, 1.425, 1.421, 1.421, 1.421, 1.421, 1.421, 1.419, 1.419, 1.421, 1.422, 1.423, 1.424, 1.426, 1.426, 1.426, 1.426, 1.425, 1.424, 1.424, 1.422, 1.422, 1.421, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.423, 1.426, + 1.429, 1.427, 1.424, 1.422, 1.422, 1.422, 1.421, 1.421, 1.421, 1.422, 1.422, 1.422, 1.424, 1.425, 1.426, 1.426, 1.425, 1.425, 1.424, 1.423, 1.422, 1.422, 1.421, 1.421, 1.421, 1.421, 1.419, 1.419, 1.421, 1.422, 1.424, 1.426 + ] + } + ], + "luminance_lut": + [ + 2.964, 2.872, 2.691, 2.544, 2.416, 2.302, 2.196, 2.093, 2.006, 1.928, 1.852, 1.801, 1.769, 1.752, 1.743, 1.743, 1.743, 1.746, 1.759, 1.784, 1.824, 1.888, 1.968, 2.052, 2.149, 2.253, 2.359, 2.483, 2.626, 2.785, 2.988, 3.051, + 2.872, 2.748, 2.583, 2.442, 2.313, 2.201, 2.104, 2.012, 1.928, 1.852, 1.791, 1.742, 1.701, 1.671, 1.651, 1.643, 1.643, 1.659, 1.685, 1.721, 1.768, 1.824, 1.888, 1.971, 2.068, 2.152, 2.259, 2.381, 2.514, 2.669, 2.853, 2.988, + 2.761, 2.655, 2.497, 2.356, 2.226, 2.114, 2.012, 1.928, 1.845, 1.769, 1.707, 1.653, 1.612, 1.583, 1.562, 1.556, 1.556, 1.572, 1.599, 1.635, 1.681, 1.742, 1.806, 1.888, 1.971, 2.068, 2.175, 2.292, 2.431, 2.576, 2.747, 2.853, + 2.679, 2.571, 2.415, 2.275, 2.151, 2.035, 1.936, 1.845, 1.769, 1.689, 1.623, 1.572, 1.532, 1.501, 1.481, 1.473, 1.473, 1.492, 1.517, 1.556, 1.599, 1.659, 1.731, 1.806, 1.895, 1.992, 2.101, 2.218, 2.349, 2.493, 2.664, 2.753, + 2.609, 2.492, 2.339, 2.204, 2.079, 1.971, 1.865, 1.772, 1.689, 1.619, 1.551, 1.499, 1.457, 1.423, 1.405, 1.397, 1.397, 1.411, 1.438, 1.477, 1.525, 1.585, 1.659, 1.731, 1.823, 1.922, 2.027, 2.148, 2.275, 2.422, 2.586, 2.683, + 2.545, 2.426, 2.279, 2.139, 2.014, 1.903, 1.799, 1.702, 1.619, 1.551, 1.482, 1.427, 1.385, 1.353, 1.331, 1.325, 1.325, 1.338, 1.364, 1.403, 1.455, 1.522, 1.585, 1.665, 1.757, 1.858, 1.963, 2.081, 2.207, 2.356, 2.518, 2.615, + 2.489, 2.367, 2.218, 2.079, 1.956, 1.844, 1.739, 1.642, 1.559, 1.482, 1.426, 1.363, 1.321, 1.287, 1.266, 1.259, 1.259, 1.274, 1.301, 1.339, 1.395, 1.455, 1.523, 1.606, 1.697, 1.797, 1.905, 2.024, 2.154, 2.296, 2.455, 2.563, + 2.439, 2.316, 2.164, 2.028, 1.906, 1.793, 1.686, 1.589, 1.505, 1.427, 1.363, 1.308, 1.261, 1.229, 1.207, 1.202, 1.202, 1.215, 1.242, 1.283, 1.339, 1.395, 1.467, 1.551, 1.639, 1.742, 1.851, 1.972, 2.104, 2.243, 2.402, 2.515, + 2.398, 2.262, 2.116, 1.982, 1.861, 1.745, 1.639, 1.541, 1.456, 1.377, 1.308, 1.261, 1.208, 1.177, 1.157, 1.153, 1.153, 1.167, 1.191, 1.233, 1.283, 1.343, 1.418, 1.499, 1.591, 1.696, 1.804, 1.928, 2.057, 2.194, 2.352, 2.471, + 2.363, 2.222, 2.078, 1.942, 1.818, 1.706, 1.597, 1.501, 1.412, 1.334, 1.266, 1.208, 1.171, 1.134, 1.113, 1.109, 1.109, 1.123, 1.149, 1.191, 1.233, 1.296, 1.371, 1.457, 1.546, 1.654, 1.768, 1.886, 2.014, 2.155, 2.312, 2.436, + 2.334, 2.188, 2.042, 1.909, 1.783, 1.668, 1.561, 1.464, 1.374, 1.295, 1.228, 1.171, 1.134, 1.098, 1.076, 1.072, 1.072, 1.087, 1.119, 1.149, 1.196, 1.259, 1.332, 1.419, 1.514, 1.616, 1.728, 1.849, 1.981, 2.123, 2.276, 2.406, + 2.306, 2.159, 2.015, 1.881, 1.753, 1.639, 1.533, 1.434, 1.341, 1.263, 1.195, 1.139, 1.098, 1.074, 1.046, 1.044, 1.045, 1.059, 1.087, 1.119, 1.165, 1.227, 1.302, 1.387, 1.482, 1.586, 1.698, 1.819, 1.953, 2.093, 2.248, 2.383, + 2.291, 2.141, 1.991, 1.856, 1.732, 1.615, 1.508, 1.409, 1.318, 1.238, 1.171, 1.114, 1.074, 1.046, 1.027, 1.023, 1.025, 1.043, 1.059, 1.095, 1.142, 1.203, 1.278, 1.362, 1.456, 1.559, 1.673, 1.796, 1.928, 2.071, 2.225, 2.359, + 2.279, 2.118, 1.972, 1.839, 1.715, 1.599, 1.488, 1.389, 1.298, 1.219, 1.153, 1.097, 1.057, 1.027, 1.018, 1.009, 1.013, 1.025, 1.044, 1.078, 1.125, 1.186, 1.258, 1.342, 1.438, 1.541, 1.655, 1.779, 1.909, 2.053, 2.211, 2.351, + 2.274, 2.108, 1.963, 1.831, 1.706, 1.588, 1.477, 1.376, 1.288, 1.207, 1.139, 1.086, 1.049, 1.021, 1.005, 1.002, 1.004, 1.013, 1.035, 1.069, 1.116, 1.176, 1.246, 1.331, 1.427, 1.531, 1.645, 1.767, 1.899, 2.045, 2.197, 2.351, + 2.274, 2.106, 1.961, 1.827, 1.701, 1.585, 1.474, 1.374, 1.285, 1.206, 1.139, 1.085, 1.047, 1.019, 1.003, 1.001, 1.001, 1.012, 1.033, 1.067, 1.113, 1.173, 1.245, 1.329, 1.423, 1.529, 1.642, 1.765, 1.897, 2.042, 2.196, 2.349, + 2.274, 2.108, 1.961, 1.827, 1.701, 1.585, 1.474, 1.374, 1.285, 1.206, 1.139, 1.085, 1.047, 1.021, 1.005, 1.001, 1.004, 1.012, 1.033, 1.068, 1.113, 1.173, 1.246, 1.329, 1.423, 1.529, 1.642, 1.766, 1.897, 2.042, 2.198, 2.349, + 2.278, 2.116, 1.968, 1.833, 1.707, 1.591, 1.482, 1.382, 1.291, 1.214, 1.147, 1.091, 1.055, 1.028, 1.016, 1.006, 1.012, 1.018, 1.039, 1.074, 1.121, 1.182, 1.255, 1.339, 1.433, 1.538, 1.651, 1.777, 1.911, 2.051, 2.207, 2.351, + 2.283, 2.127, 1.979, 1.846, 1.723, 1.605, 1.496, 1.397, 1.309, 1.229, 1.162, 1.108, 1.067, 1.041, 1.027, 1.018, 1.018, 1.036, 1.051, 1.087, 1.136, 1.197, 1.269, 1.354, 1.448, 1.554, 1.664, 1.789, 1.922, 2.065, 2.222, 2.365, + 2.298, 2.145, 1.999, 1.865, 1.744, 1.627, 1.518, 1.421, 1.331, 1.251, 1.183, 1.129, 1.087, 1.065, 1.041, 1.036, 1.036, 1.051, 1.074, 1.107, 1.158, 1.219, 1.292, 1.378, 1.471, 1.575, 1.687, 1.809, 1.942, 2.085, 2.239, 2.378, + 2.315, 2.174, 2.024, 1.893, 1.768, 1.652, 1.543, 1.445, 1.355, 1.278, 1.211, 1.155, 1.116, 1.087, 1.066, 1.061, 1.061, 1.074, 1.105, 1.137, 1.186, 1.248, 1.322, 1.405, 1.498, 1.602, 1.713, 1.835, 1.965, 2.109, 2.267, 2.399, + 2.341, 2.206, 2.057, 1.923, 1.799, 1.685, 1.576, 1.479, 1.392, 1.312, 1.244, 1.187, 1.154, 1.116, 1.096, 1.092, 1.092, 1.106, 1.137, 1.173, 1.221, 1.282, 1.356, 1.439, 1.532, 1.635, 1.747, 1.869, 1.997, 2.141, 2.298, 2.425, + 2.375, 2.244, 2.098, 1.965, 1.839, 1.722, 1.614, 1.519, 1.434, 1.355, 1.288, 1.234, 1.187, 1.155, 1.136, 1.132, 1.132, 1.147, 1.173, 1.219, 1.263, 1.324, 1.398, 1.479, 1.571, 1.674, 1.784, 1.904, 2.035, 2.177, 2.336, 2.455, + 2.414, 2.286, 2.144, 2.011, 1.883, 1.767, 1.661, 1.566, 1.479, 1.401, 1.335, 1.286, 1.234, 1.202, 1.183, 1.178, 1.178, 1.195, 1.222, 1.263, 1.313, 1.372, 1.444, 1.526, 1.618, 1.718, 1.827, 1.951, 2.081, 2.221, 2.379, 2.498, + 2.463, 2.339, 2.191, 2.056, 1.931, 1.819, 1.712, 1.616, 1.529, 1.452, 1.392, 1.335, 1.286, 1.254, 1.235, 1.232, 1.232, 1.248, 1.275, 1.313, 1.371, 1.425, 1.495, 1.576, 1.671, 1.768, 1.877, 1.999, 2.128, 2.269, 2.428, 2.541, + 2.514, 2.396, 2.247, 2.112, 1.988, 1.873, 1.766, 1.671, 1.588, 1.513, 1.452, 1.392, 1.348, 1.316, 1.298, 1.292, 1.292, 1.307, 1.336, 1.373, 1.425, 1.486, 1.552, 1.636, 1.728, 1.826, 1.933, 2.051, 2.183, 2.327, 2.488, 2.587, + 2.573, 2.459, 2.307, 2.171, 2.049, 1.931, 1.828, 1.731, 1.649, 1.582, 1.513, 1.459, 1.415, 1.381, 1.363, 1.358, 1.358, 1.373, 1.399, 1.439, 1.486, 1.552, 1.617, 1.696, 1.787, 1.888, 1.995, 2.112, 2.244, 2.391, 2.552, 2.652, + 2.635, 2.525, 2.377, 2.239, 2.111, 1.996, 1.895, 1.799, 1.719, 1.649, 1.582, 1.531, 1.486, 1.454, 1.434, 1.429, 1.429, 1.444, 1.469, 1.507, 1.555, 1.617, 1.692, 1.766, 1.854, 1.954, 2.065, 2.181, 2.313, 2.459, 2.623, 2.722, + 2.714, 2.604, 2.452, 2.313, 2.188, 2.071, 1.966, 1.876, 1.799, 1.719, 1.656, 1.604, 1.562, 1.529, 1.511, 1.504, 1.504, 1.519, 1.544, 1.583, 1.632, 1.692, 1.766, 1.839, 1.929, 2.029, 2.138, 2.259, 2.391, 2.539, 2.712, 2.811, + 2.809, 2.698, 2.537, 2.396, 2.277, 2.163, 2.053, 1.965, 1.876, 1.799, 1.741, 1.688, 1.643, 1.613, 1.592, 1.586, 1.586, 1.601, 1.628, 1.666, 1.715, 1.773, 1.839, 1.927, 2.012, 2.111, 2.222, 2.342, 2.477, 2.625, 2.811, 2.926, + 2.921, 2.809, 2.637, 2.493, 2.376, 2.256, 2.149, 2.053, 1.966, 1.893, 1.832, 1.778, 1.736, 1.708, 1.687, 1.681, 1.681, 1.696, 1.721, 1.757, 1.806, 1.864, 1.929, 2.012, 2.106, 2.199, 2.313, 2.437, 2.577, 2.731, 2.926, 3.051, + 3.029, 2.921, 2.745, 2.591, 2.474, 2.355, 2.246, 2.146, 2.049, 1.966, 1.893, 1.832, 1.799, 1.776, 1.768, 1.768, 1.768, 1.771, 1.783, 1.809, 1.864, 1.929, 2.012, 2.097, 2.195, 2.297, 2.412, 2.539, 2.682, 2.846, 3.051, 3.123 + ], + "sigma": 0.00463, + "sigma_Cb": 0.00149 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "lo_max": 1000, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2860, + "ccm": + [ + 2.12089, -0.52461, -0.59629, + -0.85342, 2.80445, -0.95103, + -0.26897, -1.14788, 2.41685 + ] + }, + { + "ct": 2960, + "ccm": + [ + 2.26962, -0.54174, -0.72789, + -0.77008, 2.60271, -0.83262, + -0.26036, -1.51254, 2.77289 + ] + }, + { + "ct": 3603, + "ccm": + [ + 2.18644, -0.66148, -0.52496, + -0.77828, 2.69474, -0.91645, + -0.25239, -0.83059, 2.08298 + ] + }, + { + "ct": 4650, + "ccm": + [ + 2.18174, -0.70887, -0.47287, + -0.70196, 2.76426, -1.06231, + -0.25157, -0.71978, 1.97135 + ] + }, + { + "ct": 5858, + "ccm": + [ + 2.32392, -0.88421, -0.43971, + -0.63821, 2.58348, -0.94527, + -0.28541, -0.54112, 1.82653 + ] + }, + { + "ct": 7580, + "ccm": + [ + 2.21175, -0.53242, -0.67933, + -0.57875, 3.07922, -1.50047, + -0.27709, -0.73338, 2.01048 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx219_noir.json b/src/ipa/rpi/pisp/data/imx219_noir.json new file mode 100644 index 00000000..8a8ad330 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx219_noir.json @@ -0,0 +1,1112 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 21965, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 800, + "reference_Y": 11460 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 3.661 + } + }, + { + "rpi.geq": + { + "offset": 239, + "slope": 0.00766 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 10.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 10.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.418, 1.428, 1.446, 1.454, 1.454, 1.451, 1.441, 1.428, 1.411, 1.391, 1.371, 1.349, 1.334, 1.327, 1.325, 1.325, 1.325, 1.325, 1.331, 1.344, 1.363, 1.383, 1.402, 1.418, 1.433, 1.446, 1.452, 1.453, 1.446, 1.435, 1.415, 1.404, + 1.428, 1.442, 1.453, 1.455, 1.454, 1.447, 1.431, 1.413, 1.392, 1.371, 1.349, 1.331, 1.318, 1.307, 1.299, 1.299, 1.299, 1.303, 1.313, 1.328, 1.344, 1.363, 1.383, 1.404, 1.424, 1.439, 1.451, 1.453, 1.453, 1.445, 1.431, 1.415, + 1.436, 1.448, 1.453, 1.455, 1.449, 1.435, 1.415, 1.393, 1.369, 1.345, 1.322, 1.303, 1.287, 1.276, 1.269, 1.268, 1.268, 1.272, 1.283, 1.298, 1.316, 1.337, 1.362, 1.384, 1.406, 1.427, 1.444, 1.454, 1.454, 1.452, 1.438, 1.426, + 1.441, 1.451, 1.454, 1.451, 1.439, 1.422, 1.396, 1.372, 1.345, 1.319, 1.295, 1.274, 1.257, 1.245, 1.239, 1.238, 1.238, 1.245, 1.255, 1.269, 1.289, 1.311, 1.336, 1.362, 1.388, 1.412, 1.433, 1.448, 1.454, 1.453, 1.445, 1.433, + 1.445, 1.452, 1.452, 1.445, 1.428, 1.405, 1.379, 1.349, 1.319, 1.295, 1.269, 1.247, 1.229, 1.219, 1.212, 1.211, 1.211, 1.217, 1.228, 1.242, 1.261, 1.286, 1.311, 1.339, 1.367, 1.395, 1.419, 1.439, 1.452, 1.452, 1.451, 1.436, + 1.448, 1.451, 1.451, 1.435, 1.414, 1.387, 1.358, 1.327, 1.296, 1.269, 1.245, 1.222, 1.205, 1.193, 1.187, 1.185, 1.186, 1.191, 1.202, 1.217, 1.237, 1.261, 1.286, 1.316, 1.346, 1.378, 1.404, 1.429, 1.445, 1.451, 1.451, 1.442, + 1.448, 1.448, 1.445, 1.427, 1.401, 1.371, 1.338, 1.306, 1.274, 1.245, 1.222, 1.199, 1.183, 1.171, 1.164, 1.162, 1.162, 1.168, 1.181, 1.194, 1.215, 1.237, 1.264, 1.294, 1.325, 1.359, 1.389, 1.418, 1.441, 1.449, 1.449, 1.443, + 1.449, 1.448, 1.438, 1.415, 1.387, 1.352, 1.318, 1.284, 1.252, 1.223, 1.199, 1.179, 1.161, 1.149, 1.142, 1.142, 1.142, 1.149, 1.159, 1.174, 1.194, 1.215, 1.242, 1.272, 1.307, 1.341, 1.376, 1.405, 1.431, 1.447, 1.447, 1.444, + 1.448, 1.447, 1.431, 1.405, 1.373, 1.336, 1.301, 1.264, 1.234, 1.204, 1.179, 1.161, 1.143, 1.131, 1.124, 1.123, 1.123, 1.131, 1.141, 1.156, 1.174, 1.197, 1.224, 1.254, 1.288, 1.324, 1.361, 1.394, 1.423, 1.442, 1.444, 1.444, + 1.447, 1.442, 1.424, 1.393, 1.359, 1.322, 1.284, 1.248, 1.216, 1.187, 1.162, 1.143, 1.128, 1.115, 1.109, 1.108, 1.108, 1.113, 1.124, 1.139, 1.156, 1.179, 1.206, 1.236, 1.272, 1.309, 1.347, 1.382, 1.411, 1.435, 1.443, 1.444, + 1.444, 1.439, 1.417, 1.383, 1.347, 1.308, 1.271, 1.233, 1.201, 1.173, 1.147, 1.128, 1.115, 1.101, 1.095, 1.093, 1.093, 1.099, 1.111, 1.124, 1.142, 1.165, 1.191, 1.222, 1.258, 1.296, 1.333, 1.372, 1.404, 1.429, 1.441, 1.442, + 1.443, 1.434, 1.409, 1.375, 1.336, 1.297, 1.257, 1.221, 1.189, 1.159, 1.136, 1.116, 1.101, 1.092, 1.083, 1.082, 1.082, 1.089, 1.099, 1.111, 1.131, 1.153, 1.181, 1.211, 1.246, 1.284, 1.324, 1.361, 1.398, 1.425, 1.441, 1.441, + 1.443, 1.431, 1.405, 1.369, 1.328, 1.287, 1.247, 1.211, 1.178, 1.149, 1.126, 1.107, 1.092, 1.083, 1.075, 1.073, 1.073, 1.082, 1.089, 1.101, 1.121, 1.143, 1.171, 1.201, 1.237, 1.274, 1.314, 1.353, 1.389, 1.421, 1.439, 1.441, + 1.442, 1.429, 1.401, 1.364, 1.323, 1.279, 1.241, 1.205, 1.172, 1.144, 1.119, 1.101, 1.085, 1.075, 1.071, 1.067, 1.067, 1.073, 1.082, 1.096, 1.114, 1.136, 1.163, 1.194, 1.229, 1.268, 1.308, 1.348, 1.387, 1.417, 1.439, 1.439, + 1.443, 1.429, 1.399, 1.362, 1.319, 1.276, 1.237, 1.199, 1.169, 1.141, 1.115, 1.096, 1.081, 1.071, 1.066, 1.063, 1.066, 1.068, 1.078, 1.092, 1.109, 1.132, 1.159, 1.191, 1.226, 1.263, 1.304, 1.346, 1.384, 1.416, 1.438, 1.439, + 1.443, 1.428, 1.399, 1.361, 1.319, 1.276, 1.236, 1.199, 1.167, 1.139, 1.115, 1.096, 1.081, 1.071, 1.064, 1.062, 1.062, 1.067, 1.077, 1.091, 1.109, 1.131, 1.158, 1.189, 1.224, 1.262, 1.303, 1.345, 1.383, 1.416, 1.438, 1.439, + 1.444, 1.429, 1.399, 1.361, 1.319, 1.276, 1.236, 1.199, 1.167, 1.139, 1.116, 1.096, 1.081, 1.071, 1.064, 1.063, 1.063, 1.067, 1.077, 1.091, 1.109, 1.131, 1.159, 1.189, 1.224, 1.262, 1.303, 1.345, 1.384, 1.416, 1.438, 1.441, + 1.444, 1.431, 1.402, 1.364, 1.322, 1.281, 1.239, 1.202, 1.171, 1.142, 1.118, 1.099, 1.084, 1.073, 1.069, 1.065, 1.067, 1.071, 1.079, 1.094, 1.112, 1.135, 1.163, 1.191, 1.227, 1.265, 1.307, 1.348, 1.386, 1.418, 1.438, 1.441, + 1.447, 1.433, 1.406, 1.369, 1.328, 1.286, 1.244, 1.209, 1.177, 1.148, 1.124, 1.105, 1.089, 1.081, 1.073, 1.071, 1.071, 1.079, 1.085, 1.099, 1.118, 1.141, 1.168, 1.198, 1.233, 1.271, 1.312, 1.352, 1.391, 1.422, 1.441, 1.444, + 1.448, 1.438, 1.412, 1.376, 1.335, 1.295, 1.255, 1.218, 1.186, 1.157, 1.134, 1.113, 1.098, 1.089, 1.081, 1.079, 1.079, 1.085, 1.094, 1.107, 1.125, 1.149, 1.175, 1.207, 1.242, 1.281, 1.319, 1.359, 1.396, 1.425, 1.445, 1.447, + 1.449, 1.443, 1.417, 1.384, 1.345, 1.305, 1.266, 1.229, 1.197, 1.169, 1.145, 1.124, 1.111, 1.098, 1.091, 1.089, 1.089, 1.094, 1.107, 1.118, 1.137, 1.159, 1.187, 1.218, 1.253, 1.291, 1.329, 1.369, 1.405, 1.433, 1.447, 1.449, + 1.453, 1.449, 1.425, 1.395, 1.358, 1.318, 1.281, 1.244, 1.211, 1.183, 1.158, 1.138, 1.124, 1.111, 1.104, 1.103, 1.103, 1.107, 1.118, 1.133, 1.151, 1.174, 1.201, 1.232, 1.267, 1.304, 1.344, 1.379, 1.413, 1.437, 1.449, 1.449, + 1.457, 1.453, 1.434, 1.405, 1.371, 1.335, 1.297, 1.261, 1.229, 1.199, 1.174, 1.155, 1.138, 1.126, 1.119, 1.117, 1.117, 1.124, 1.133, 1.149, 1.167, 1.189, 1.217, 1.248, 1.284, 1.319, 1.357, 1.393, 1.423, 1.444, 1.452, 1.452, + 1.459, 1.457, 1.443, 1.418, 1.385, 1.352, 1.314, 1.279, 1.246, 1.218, 1.193, 1.174, 1.155, 1.144, 1.137, 1.136, 1.136, 1.141, 1.151, 1.167, 1.187, 1.208, 1.236, 1.267, 1.301, 1.337, 1.373, 1.405, 1.434, 1.453, 1.455, 1.455, + 1.461, 1.461, 1.454, 1.429, 1.401, 1.369, 1.333, 1.301, 1.269, 1.239, 1.216, 1.193, 1.177, 1.165, 1.158, 1.156, 1.156, 1.161, 1.171, 1.187, 1.208, 1.229, 1.258, 1.288, 1.321, 1.356, 1.389, 1.419, 1.445, 1.459, 1.459, 1.455, + 1.462, 1.462, 1.459, 1.442, 1.418, 1.386, 1.354, 1.322, 1.292, 1.262, 1.239, 1.216, 1.199, 1.187, 1.179, 1.178, 1.178, 1.184, 1.194, 1.208, 1.229, 1.253, 1.279, 1.309, 1.342, 1.375, 1.406, 1.433, 1.452, 1.464, 1.464, 1.454, + 1.461, 1.465, 1.465, 1.454, 1.431, 1.405, 1.376, 1.346, 1.316, 1.288, 1.262, 1.242, 1.223, 1.212, 1.205, 1.203, 1.203, 1.208, 1.218, 1.234, 1.253, 1.279, 1.305, 1.334, 1.363, 1.393, 1.421, 1.445, 1.461, 1.465, 1.464, 1.452, + 1.459, 1.465, 1.466, 1.461, 1.443, 1.421, 1.395, 1.368, 1.341, 1.316, 1.288, 1.268, 1.251, 1.238, 1.232, 1.229, 1.229, 1.235, 1.246, 1.261, 1.279, 1.305, 1.331, 1.356, 1.385, 1.411, 1.435, 1.454, 1.466, 1.466, 1.464, 1.451, + 1.454, 1.465, 1.467, 1.466, 1.456, 1.436, 1.414, 1.389, 1.367, 1.341, 1.318, 1.297, 1.279, 1.269, 1.261, 1.259, 1.259, 1.265, 1.274, 1.288, 1.308, 1.331, 1.355, 1.381, 1.404, 1.428, 1.447, 1.462, 1.468, 1.467, 1.457, 1.445, + 1.447, 1.459, 1.466, 1.467, 1.463, 1.451, 1.434, 1.411, 1.389, 1.367, 1.344, 1.325, 1.311, 1.297, 1.292, 1.289, 1.289, 1.295, 1.303, 1.317, 1.336, 1.356, 1.381, 1.402, 1.423, 1.441, 1.457, 1.467, 1.468, 1.463, 1.451, 1.439, + 1.438, 1.449, 1.462, 1.464, 1.464, 1.459, 1.446, 1.429, 1.408, 1.388, 1.369, 1.353, 1.339, 1.329, 1.321, 1.321, 1.321, 1.325, 1.333, 1.348, 1.362, 1.379, 1.401, 1.421, 1.439, 1.454, 1.463, 1.465, 1.465, 1.456, 1.442, 1.427, + 1.429, 1.439, 1.454, 1.464, 1.464, 1.459, 1.449, 1.435, 1.421, 1.402, 1.385, 1.369, 1.353, 1.341, 1.338, 1.337, 1.337, 1.338, 1.348, 1.362, 1.378, 1.395, 1.411, 1.429, 1.445, 1.455, 1.463, 1.464, 1.457, 1.447, 1.427, 1.419 + ] + }, + { + "ct": 5000, + "table": + [ + 2.163, 2.177, 2.194, 2.196, 2.197, 2.192, 2.181, 2.161, 2.139, 2.113, 2.088, 2.063, 2.047, 2.041, 2.036, 2.036, 2.036, 2.037, 2.046, 2.059, 2.083, 2.113, 2.135, 2.158, 2.181, 2.193, 2.205, 2.205, 2.202, 2.189, 2.171, 2.158, + 2.169, 2.184, 2.195, 2.196, 2.194, 2.182, 2.163, 2.141, 2.116, 2.088, 2.063, 2.042, 2.025, 2.013, 2.004, 2.004, 2.006, 2.011, 2.022, 2.038, 2.059, 2.083, 2.113, 2.137, 2.162, 2.182, 2.197, 2.204, 2.203, 2.199, 2.183, 2.171, + 2.177, 2.187, 2.193, 2.193, 2.184, 2.166, 2.142, 2.116, 2.087, 2.057, 2.033, 2.008, 1.991, 1.977, 1.969, 1.969, 1.969, 1.975, 1.988, 2.006, 2.028, 2.055, 2.083, 2.114, 2.139, 2.166, 2.187, 2.199, 2.202, 2.201, 2.189, 2.179, + 2.183, 2.189, 2.192, 2.186, 2.172, 2.146, 2.119, 2.089, 2.058, 2.026, 2.001, 1.975, 1.956, 1.942, 1.934, 1.932, 1.933, 1.941, 1.955, 1.971, 1.995, 2.023, 2.055, 2.084, 2.119, 2.146, 2.171, 2.191, 2.201, 2.201, 2.194, 2.183, + 2.186, 2.189, 2.189, 2.177, 2.158, 2.127, 2.096, 2.059, 2.026, 1.998, 1.969, 1.944, 1.925, 1.911, 1.901, 1.901, 1.903, 1.912, 1.924, 1.941, 1.964, 1.995, 2.023, 2.058, 2.091, 2.126, 2.155, 2.181, 2.195, 2.199, 2.198, 2.188, + 2.189, 2.189, 2.184, 2.166, 2.138, 2.108, 2.071, 2.036, 1.999, 1.969, 1.941, 1.914, 1.894, 1.879, 1.871, 1.871, 1.872, 1.879, 1.893, 1.913, 1.937, 1.964, 1.997, 2.029, 2.065, 2.104, 2.137, 2.169, 2.187, 2.199, 2.199, 2.189, + 2.187, 2.186, 2.176, 2.154, 2.123, 2.087, 2.044, 2.011, 1.974, 1.941, 1.913, 1.887, 1.868, 1.852, 1.844, 1.843, 1.844, 1.852, 1.866, 1.885, 1.912, 1.937, 1.972, 2.004, 2.042, 2.081, 2.119, 2.154, 2.179, 2.195, 2.196, 2.193, + 2.187, 2.181, 2.167, 2.141, 2.103, 2.062, 2.023, 1.984, 1.947, 1.916, 1.887, 1.864, 1.841, 1.828, 1.821, 1.819, 1.819, 1.828, 1.842, 1.862, 1.885, 1.913, 1.945, 1.982, 2.021, 2.058, 2.102, 2.137, 2.168, 2.192, 2.193, 2.193, + 2.182, 2.181, 2.161, 2.127, 2.083, 2.044, 2.002, 1.961, 1.924, 1.891, 1.864, 1.841, 1.819, 1.806, 1.797, 1.797, 1.797, 1.805, 1.819, 1.841, 1.862, 1.892, 1.924, 1.959, 1.999, 2.041, 2.082, 2.123, 2.161, 2.185, 2.191, 2.192, + 2.182, 2.172, 2.149, 2.112, 2.069, 2.026, 1.982, 1.941, 1.904, 1.871, 1.841, 1.819, 1.799, 1.785, 1.776, 1.776, 1.778, 1.784, 1.798, 1.819, 1.841, 1.869, 1.903, 1.939, 1.977, 2.021, 2.067, 2.108, 2.145, 2.174, 2.189, 2.191, + 2.181, 2.167, 2.139, 2.098, 2.056, 2.006, 1.965, 1.921, 1.883, 1.851, 1.823, 1.799, 1.783, 1.767, 1.759, 1.758, 1.758, 1.767, 1.783, 1.798, 1.825, 1.851, 1.883, 1.919, 1.959, 2.004, 2.049, 2.094, 2.136, 2.167, 2.187, 2.189, + 2.179, 2.163, 2.131, 2.087, 2.041, 1.994, 1.948, 1.907, 1.871, 1.835, 1.806, 1.784, 1.767, 1.754, 1.744, 1.742, 1.742, 1.752, 1.767, 1.783, 1.808, 1.838, 1.869, 1.905, 1.945, 1.989, 2.036, 2.083, 2.128, 2.159, 2.183, 2.187, + 2.178, 2.161, 2.126, 2.082, 2.032, 1.982, 1.936, 1.896, 1.857, 1.823, 1.795, 1.772, 1.754, 1.744, 1.732, 1.731, 1.732, 1.742, 1.752, 1.771, 1.796, 1.824, 1.857, 1.895, 1.934, 1.977, 2.024, 2.071, 2.116, 2.154, 2.181, 2.185, + 2.177, 2.157, 2.121, 2.074, 2.025, 1.973, 1.927, 1.886, 1.849, 1.815, 1.787, 1.765, 1.746, 1.732, 1.725, 1.722, 1.724, 1.732, 1.743, 1.762, 1.786, 1.813, 1.848, 1.886, 1.924, 1.969, 2.017, 2.066, 2.111, 2.153, 2.179, 2.183, + 2.177, 2.155, 2.119, 2.072, 2.022, 1.969, 1.925, 1.881, 1.844, 1.811, 1.782, 1.758, 1.739, 1.725, 1.721, 1.717, 1.721, 1.724, 1.739, 1.757, 1.781, 1.809, 1.842, 1.879, 1.921, 1.965, 2.012, 2.062, 2.108, 2.151, 2.179, 2.182, + 2.177, 2.156, 2.121, 2.071, 2.021, 1.968, 1.922, 1.879, 1.842, 1.811, 1.781, 1.757, 1.739, 1.725, 1.717, 1.715, 1.715, 1.723, 1.737, 1.757, 1.779, 1.808, 1.841, 1.877, 1.918, 1.963, 2.011, 2.061, 2.107, 2.148, 2.179, 2.183, + 2.178, 2.157, 2.121, 2.072, 2.021, 1.969, 1.922, 1.881, 1.842, 1.811, 1.781, 1.758, 1.739, 1.726, 1.718, 1.717, 1.718, 1.723, 1.737, 1.757, 1.781, 1.809, 1.841, 1.877, 1.918, 1.964, 2.012, 2.061, 2.108, 2.149, 2.179, 2.183, + 2.178, 2.159, 2.124, 2.074, 2.024, 1.974, 1.926, 1.885, 1.847, 1.813, 1.784, 1.762, 1.743, 1.731, 1.725, 1.719, 1.723, 1.728, 1.742, 1.762, 1.785, 1.814, 1.847, 1.881, 1.922, 1.966, 2.017, 2.065, 2.109, 2.151, 2.181, 2.184, + 2.181, 2.163, 2.129, 2.082, 2.032, 1.982, 1.934, 1.891, 1.854, 1.822, 1.794, 1.769, 1.751, 1.739, 1.731, 1.727, 1.728, 1.739, 1.747, 1.768, 1.791, 1.821, 1.852, 1.889, 1.929, 1.972, 2.022, 2.071, 2.117, 2.155, 2.182, 2.189, + 2.184, 2.169, 2.135, 2.091, 2.041, 1.994, 1.947, 1.902, 1.865, 1.833, 1.805, 1.779, 1.762, 1.751, 1.739, 1.739, 1.739, 1.747, 1.761, 1.779, 1.803, 1.831, 1.864, 1.898, 1.941, 1.984, 2.033, 2.079, 2.123, 2.163, 2.188, 2.193, + 2.185, 2.174, 2.142, 2.099, 2.054, 2.004, 1.959, 1.917, 1.879, 1.846, 1.819, 1.794, 1.779, 1.762, 1.754, 1.753, 1.753, 1.761, 1.777, 1.793, 1.816, 1.843, 1.877, 1.913, 1.953, 1.995, 2.043, 2.091, 2.135, 2.169, 2.191, 2.196, + 2.191, 2.179, 2.154, 2.118, 2.069, 2.023, 1.977, 1.935, 1.898, 1.865, 1.834, 1.813, 1.794, 1.779, 1.769, 1.769, 1.769, 1.777, 1.793, 1.809, 1.834, 1.863, 1.895, 1.929, 1.972, 2.015, 2.061, 2.105, 2.145, 2.178, 2.195, 2.199, + 2.197, 2.188, 2.166, 2.129, 2.087, 2.041, 1.997, 1.956, 1.918, 1.884, 1.855, 1.834, 1.813, 1.798, 1.788, 1.788, 1.788, 1.796, 1.809, 1.832, 1.853, 1.881, 1.912, 1.949, 1.991, 2.033, 2.076, 2.119, 2.159, 2.187, 2.202, 2.205, + 2.202, 2.197, 2.176, 2.148, 2.106, 2.065, 2.021, 1.979, 1.943, 1.909, 1.879, 1.855, 1.835, 1.819, 1.811, 1.811, 1.811, 1.818, 1.832, 1.853, 1.875, 1.904, 1.937, 1.972, 2.013, 2.055, 2.097, 2.138, 2.175, 2.197, 2.206, 2.207, + 2.205, 2.202, 2.189, 2.162, 2.126, 2.084, 2.044, 2.004, 1.967, 1.935, 1.907, 1.879, 1.861, 1.845, 1.838, 1.835, 1.835, 1.844, 1.855, 1.875, 1.902, 1.928, 1.961, 1.998, 2.033, 2.076, 2.118, 2.155, 2.186, 2.205, 2.208, 2.208, + 2.207, 2.205, 2.195, 2.175, 2.145, 2.108, 2.069, 2.029, 1.996, 1.963, 1.934, 1.908, 1.885, 1.872, 1.864, 1.863, 1.863, 1.869, 1.884, 1.902, 1.928, 1.956, 1.989, 2.023, 2.059, 2.099, 2.137, 2.172, 2.199, 2.212, 2.213, 2.209, + 2.207, 2.207, 2.203, 2.188, 2.162, 2.128, 2.094, 2.058, 2.023, 1.993, 1.963, 1.936, 1.916, 1.899, 1.893, 1.892, 1.893, 1.899, 1.912, 1.929, 1.956, 1.986, 2.016, 2.049, 2.084, 2.121, 2.156, 2.187, 2.208, 2.215, 2.215, 2.208, + 2.205, 2.208, 2.209, 2.199, 2.178, 2.149, 2.117, 2.083, 2.052, 2.023, 1.993, 1.967, 1.947, 1.933, 1.925, 1.922, 1.922, 1.929, 1.943, 1.961, 1.986, 2.015, 2.045, 2.076, 2.109, 2.143, 2.173, 2.198, 2.214, 2.218, 2.216, 2.205, + 2.201, 2.207, 2.211, 2.211, 2.193, 2.168, 2.141, 2.112, 2.082, 2.052, 2.025, 2.001, 1.981, 1.967, 1.959, 1.958, 1.958, 1.967, 1.975, 1.992, 2.018, 2.046, 2.076, 2.105, 2.136, 2.163, 2.189, 2.208, 2.217, 2.217, 2.212, 2.203, + 2.194, 2.204, 2.212, 2.213, 2.203, 2.187, 2.165, 2.139, 2.112, 2.083, 2.055, 2.034, 2.016, 2.001, 1.993, 1.993, 1.994, 1.999, 2.011, 2.027, 2.051, 2.077, 2.105, 2.133, 2.158, 2.181, 2.202, 2.217, 2.218, 2.218, 2.206, 2.193, + 2.185, 2.198, 2.213, 2.214, 2.212, 2.201, 2.184, 2.163, 2.135, 2.111, 2.089, 2.071, 2.052, 2.039, 2.032, 2.031, 2.031, 2.036, 2.048, 2.065, 2.085, 2.106, 2.131, 2.155, 2.178, 2.198, 2.212, 2.219, 2.219, 2.215, 2.201, 2.185, + 2.176, 2.191, 2.208, 2.217, 2.216, 2.205, 2.195, 2.177, 2.156, 2.133, 2.109, 2.089, 2.071, 2.055, 2.053, 2.053, 2.053, 2.057, 2.065, 2.085, 2.105, 2.123, 2.149, 2.171, 2.192, 2.205, 2.217, 2.219, 2.219, 2.202, 2.185, 2.181 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.518, 2.513, 2.503, 2.496, 2.488, 2.484, 2.485, 2.485, 2.486, 2.487, 2.487, 2.489, 2.494, 2.496, 2.496, 2.497, 2.499, 2.499, 2.496, 2.495, 2.492, 2.491, 2.491, 2.491, 2.492, 2.493, 2.495, 2.501, 2.508, 2.516, 2.528, 2.533, + 2.515, 2.508, 2.495, 2.487, 2.483, 2.481, 2.482, 2.483, 2.485, 2.487, 2.489, 2.491, 2.495, 2.497, 2.498, 2.501, 2.502, 2.502, 2.499, 2.496, 2.494, 2.491, 2.491, 2.489, 2.489, 2.491, 2.493, 2.496, 2.502, 2.511, 2.521, 2.531, + 2.507, 2.495, 2.486, 2.482, 2.478, 2.477, 2.481, 2.482, 2.484, 2.488, 2.491, 2.495, 2.499, 2.502, 2.506, 2.508, 2.509, 2.508, 2.505, 2.501, 2.497, 2.493, 2.491, 2.489, 2.488, 2.489, 2.489, 2.492, 2.496, 2.501, 2.511, 2.524, + 2.501, 2.487, 2.482, 2.481, 2.478, 2.477, 2.481, 2.483, 2.487, 2.491, 2.501, 2.503, 2.509, 2.511, 2.518, 2.519, 2.519, 2.519, 2.516, 2.509, 2.504, 2.498, 2.495, 2.493, 2.489, 2.489, 2.488, 2.489, 2.492, 2.498, 2.505, 2.523, + 2.499, 2.484, 2.481, 2.476, 2.476, 2.476, 2.481, 2.485, 2.492, 2.501, 2.509, 2.514, 2.519, 2.524, 2.528, 2.531, 2.533, 2.533, 2.525, 2.519, 2.514, 2.507, 2.501, 2.497, 2.493, 2.489, 2.489, 2.488, 2.491, 2.494, 2.501, 2.514, + 2.497, 2.483, 2.478, 2.476, 2.476, 2.478, 2.482, 2.491, 2.499, 2.509, 2.515, 2.522, 2.528, 2.535, 2.539, 2.541, 2.543, 2.542, 2.539, 2.529, 2.522, 2.516, 2.507, 2.502, 2.497, 2.491, 2.489, 2.488, 2.489, 2.492, 2.498, 2.514, + 2.492, 2.479, 2.476, 2.475, 2.476, 2.481, 2.488, 2.496, 2.505, 2.516, 2.524, 2.532, 2.541, 2.545, 2.552, 2.554, 2.554, 2.554, 2.548, 2.541, 2.532, 2.522, 2.516, 2.507, 2.502, 2.494, 2.491, 2.489, 2.489, 2.492, 2.494, 2.511, + 2.491, 2.479, 2.476, 2.477, 2.478, 2.482, 2.491, 2.502, 2.514, 2.524, 2.533, 2.543, 2.548, 2.555, 2.562, 2.566, 2.567, 2.562, 2.557, 2.551, 2.541, 2.531, 2.523, 2.512, 2.506, 2.498, 2.493, 2.491, 2.491, 2.491, 2.493, 2.507, + 2.489, 2.478, 2.476, 2.477, 2.481, 2.485, 2.494, 2.507, 2.517, 2.529, 2.542, 2.548, 2.557, 2.563, 2.567, 2.571, 2.572, 2.571, 2.565, 2.558, 2.549, 2.538, 2.528, 2.521, 2.509, 2.501, 2.494, 2.492, 2.491, 2.491, 2.491, 2.505, + 2.488, 2.478, 2.477, 2.478, 2.482, 2.489, 2.499, 2.509, 2.523, 2.538, 2.548, 2.556, 2.563, 2.568, 2.573, 2.577, 2.578, 2.577, 2.573, 2.564, 2.555, 2.543, 2.535, 2.524, 2.515, 2.504, 2.495, 2.492, 2.489, 2.488, 2.489, 2.501, + 2.486, 2.476, 2.475, 2.477, 2.483, 2.491, 2.503, 2.515, 2.529, 2.542, 2.553, 2.562, 2.568, 2.574, 2.581, 2.583, 2.584, 2.581, 2.578, 2.571, 2.562, 2.551, 2.539, 2.531, 2.517, 2.508, 2.497, 2.492, 2.488, 2.487, 2.489, 2.498, + 2.486, 2.476, 2.475, 2.479, 2.484, 2.492, 2.504, 2.519, 2.533, 2.544, 2.557, 2.566, 2.573, 2.581, 2.584, 2.588, 2.588, 2.586, 2.581, 2.575, 2.567, 2.555, 2.546, 2.534, 2.517, 2.509, 2.499, 2.492, 2.489, 2.485, 2.488, 2.497, + 2.487, 2.476, 2.476, 2.479, 2.486, 2.494, 2.506, 2.521, 2.535, 2.549, 2.559, 2.571, 2.578, 2.583, 2.589, 2.591, 2.591, 2.591, 2.587, 2.579, 2.571, 2.559, 2.551, 2.538, 2.523, 2.513, 2.503, 2.493, 2.489, 2.486, 2.487, 2.499, + 2.486, 2.475, 2.475, 2.479, 2.486, 2.495, 2.509, 2.525, 2.541, 2.555, 2.563, 2.573, 2.582, 2.588, 2.591, 2.594, 2.595, 2.592, 2.591, 2.585, 2.574, 2.564, 2.552, 2.541, 2.525, 2.514, 2.503, 2.493, 2.489, 2.486, 2.486, 2.501, + 2.486, 2.475, 2.475, 2.479, 2.488, 2.497, 2.509, 2.526, 2.542, 2.556, 2.564, 2.575, 2.584, 2.591, 2.595, 2.596, 2.597, 2.595, 2.592, 2.587, 2.577, 2.568, 2.554, 2.542, 2.527, 2.515, 2.504, 2.494, 2.491, 2.487, 2.487, 2.505, + 2.484, 2.476, 2.475, 2.478, 2.488, 2.498, 2.509, 2.526, 2.542, 2.555, 2.565, 2.576, 2.584, 2.589, 2.595, 2.598, 2.598, 2.597, 2.593, 2.587, 2.578, 2.569, 2.556, 2.543, 2.528, 2.515, 2.504, 2.494, 2.489, 2.485, 2.485, 2.501, + 2.484, 2.475, 2.475, 2.478, 2.489, 2.498, 2.509, 2.524, 2.539, 2.553, 2.565, 2.576, 2.584, 2.589, 2.594, 2.597, 2.597, 2.596, 2.593, 2.587, 2.577, 2.569, 2.555, 2.543, 2.529, 2.515, 2.503, 2.496, 2.491, 2.485, 2.486, 2.497, + 2.484, 2.474, 2.474, 2.479, 2.487, 2.497, 2.509, 2.523, 2.539, 2.551, 2.563, 2.574, 2.581, 2.587, 2.592, 2.595, 2.596, 2.595, 2.591, 2.584, 2.574, 2.567, 2.554, 2.541, 2.526, 2.514, 2.503, 2.495, 2.489, 2.485, 2.486, 2.497, + 2.484, 2.475, 2.475, 2.478, 2.485, 2.494, 2.507, 2.522, 2.535, 2.546, 2.559, 2.568, 2.579, 2.584, 2.589, 2.592, 2.593, 2.592, 2.588, 2.579, 2.571, 2.562, 2.551, 2.537, 2.524, 2.514, 2.501, 2.493, 2.489, 2.486, 2.487, 2.498, + 2.485, 2.476, 2.475, 2.477, 2.485, 2.491, 2.506, 2.519, 2.531, 2.544, 2.555, 2.563, 2.571, 2.581, 2.584, 2.589, 2.589, 2.588, 2.583, 2.576, 2.566, 2.555, 2.546, 2.534, 2.522, 2.511, 2.499, 2.491, 2.488, 2.486, 2.487, 2.502, + 2.487, 2.477, 2.475, 2.477, 2.483, 2.489, 2.503, 2.515, 2.525, 2.541, 2.551, 2.559, 2.567, 2.573, 2.579, 2.582, 2.583, 2.582, 2.576, 2.569, 2.562, 2.549, 2.542, 2.527, 2.518, 2.505, 2.497, 2.491, 2.489, 2.487, 2.487, 2.502, + 2.487, 2.478, 2.475, 2.477, 2.482, 2.489, 2.497, 2.512, 2.522, 2.536, 2.544, 2.551, 2.562, 2.566, 2.573, 2.578, 2.578, 2.575, 2.571, 2.564, 2.556, 2.548, 2.536, 2.523, 2.513, 2.503, 2.493, 2.489, 2.487, 2.486, 2.487, 2.502, + 2.488, 2.479, 2.477, 2.478, 2.482, 2.488, 2.496, 2.505, 2.516, 2.528, 2.538, 2.547, 2.553, 2.561, 2.565, 2.569, 2.569, 2.568, 2.564, 2.558, 2.549, 2.541, 2.531, 2.517, 2.509, 2.499, 2.492, 2.488, 2.486, 2.484, 2.486, 2.503, + 2.492, 2.482, 2.479, 2.479, 2.482, 2.487, 2.491, 2.501, 2.512, 2.523, 2.531, 2.541, 2.549, 2.552, 2.558, 2.561, 2.562, 2.559, 2.558, 2.552, 2.542, 2.535, 2.525, 2.514, 2.505, 2.497, 2.491, 2.486, 2.485, 2.484, 2.487, 2.503, + 2.495, 2.483, 2.479, 2.479, 2.482, 2.487, 2.491, 2.498, 2.508, 2.515, 2.526, 2.533, 2.541, 2.547, 2.551, 2.554, 2.555, 2.554, 2.552, 2.541, 2.537, 2.527, 2.519, 2.507, 2.502, 2.495, 2.488, 2.485, 2.484, 2.485, 2.488, 2.503, + 2.499, 2.485, 2.483, 2.481, 2.482, 2.486, 2.489, 2.494, 2.504, 2.511, 2.519, 2.527, 2.531, 2.539, 2.542, 2.546, 2.546, 2.545, 2.539, 2.535, 2.527, 2.522, 2.509, 2.505, 2.497, 2.491, 2.486, 2.485, 2.485, 2.487, 2.491, 2.506, + 2.499, 2.489, 2.483, 2.481, 2.481, 2.483, 2.488, 2.491, 2.499, 2.506, 2.512, 2.519, 2.524, 2.529, 2.535, 2.537, 2.536, 2.534, 2.532, 2.525, 2.522, 2.514, 2.506, 2.499, 2.492, 2.489, 2.485, 2.484, 2.485, 2.488, 2.492, 2.506, + 2.507, 2.494, 2.486, 2.483, 2.482, 2.482, 2.486, 2.488, 2.495, 2.501, 2.507, 2.511, 2.517, 2.519, 2.523, 2.525, 2.525, 2.523, 2.523, 2.521, 2.514, 2.506, 2.502, 2.496, 2.491, 2.488, 2.485, 2.485, 2.487, 2.489, 2.496, 2.516, + 2.511, 2.503, 2.489, 2.486, 2.485, 2.485, 2.485, 2.487, 2.489, 2.495, 2.501, 2.505, 2.509, 2.514, 2.517, 2.519, 2.518, 2.517, 2.515, 2.511, 2.505, 2.501, 2.495, 2.492, 2.488, 2.486, 2.485, 2.486, 2.488, 2.492, 2.499, 2.519, + 2.517, 2.505, 2.494, 2.489, 2.487, 2.486, 2.486, 2.486, 2.489, 2.491, 2.496, 2.499, 2.503, 2.506, 2.508, 2.509, 2.511, 2.509, 2.507, 2.503, 2.501, 2.496, 2.493, 2.489, 2.485, 2.485, 2.486, 2.487, 2.491, 2.495, 2.505, 2.526, + 2.526, 2.516, 2.504, 2.494, 2.493, 2.489, 2.489, 2.489, 2.489, 2.491, 2.496, 2.498, 2.501, 2.504, 2.506, 2.506, 2.506, 2.505, 2.503, 2.501, 2.499, 2.496, 2.494, 2.491, 2.487, 2.486, 2.489, 2.492, 2.497, 2.505, 2.517, 2.528, + 2.529, 2.526, 2.508, 2.502, 2.501, 2.498, 2.495, 2.495, 2.495, 2.495, 2.497, 2.499, 2.501, 2.503, 2.504, 2.506, 2.505, 2.505, 2.503, 2.501, 2.499, 2.496, 2.495, 2.494, 2.492, 2.494, 2.494, 2.498, 2.504, 2.513, 2.525, 2.536 + ] + }, + { + "ct": 5000, + "table": + [ + 1.427, 1.425, 1.423, 1.422, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.422, 1.423, 1.424, 1.425, 1.426, 1.426, 1.426, 1.425, 1.425, 1.424, 1.422, 1.421, 1.421, 1.421, 1.421, 1.422, 1.422, 1.422, 1.424, 1.424, 1.426, 1.428, + 1.426, 1.424, 1.422, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.422, 1.423, 1.424, 1.425, 1.426, 1.427, 1.427, 1.427, 1.426, 1.425, 1.424, 1.422, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.421, 1.422, 1.424, 1.427, + 1.423, 1.421, 1.421, 1.419, 1.419, 1.418, 1.419, 1.419, 1.421, 1.423, 1.425, 1.426, 1.428, 1.429, 1.431, 1.431, 1.431, 1.431, 1.429, 1.426, 1.424, 1.422, 1.421, 1.421, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.422, 1.425, + 1.422, 1.419, 1.419, 1.419, 1.418, 1.418, 1.419, 1.421, 1.422, 1.426, 1.428, 1.429, 1.433, 1.434, 1.436, 1.436, 1.436, 1.434, 1.432, 1.429, 1.426, 1.424, 1.423, 1.422, 1.421, 1.419, 1.419, 1.419, 1.419, 1.419, 1.421, 1.425, + 1.422, 1.419, 1.419, 1.418, 1.418, 1.419, 1.419, 1.422, 1.425, 1.429, 1.432, 1.435, 1.436, 1.438, 1.439, 1.439, 1.441, 1.439, 1.435, 1.433, 1.429, 1.427, 1.425, 1.423, 1.422, 1.419, 1.419, 1.418, 1.418, 1.418, 1.419, 1.425, + 1.422, 1.419, 1.418, 1.418, 1.418, 1.419, 1.421, 1.424, 1.428, 1.432, 1.436, 1.437, 1.439, 1.442, 1.443, 1.445, 1.444, 1.443, 1.441, 1.436, 1.434, 1.431, 1.427, 1.425, 1.422, 1.421, 1.419, 1.418, 1.418, 1.418, 1.419, 1.424, + 1.422, 1.418, 1.417, 1.418, 1.419, 1.421, 1.423, 1.427, 1.431, 1.436, 1.438, 1.442, 1.444, 1.446, 1.448, 1.449, 1.448, 1.446, 1.445, 1.441, 1.436, 1.434, 1.429, 1.427, 1.423, 1.421, 1.419, 1.418, 1.418, 1.418, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.418, 1.419, 1.421, 1.424, 1.429, 1.434, 1.438, 1.442, 1.445, 1.447, 1.449, 1.451, 1.452, 1.452, 1.449, 1.447, 1.445, 1.441, 1.436, 1.433, 1.429, 1.425, 1.422, 1.419, 1.419, 1.418, 1.417, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.419, 1.419, 1.423, 1.426, 1.432, 1.436, 1.441, 1.445, 1.448, 1.449, 1.452, 1.453, 1.454, 1.454, 1.453, 1.451, 1.447, 1.444, 1.439, 1.433, 1.431, 1.427, 1.422, 1.421, 1.419, 1.418, 1.417, 1.418, 1.423, + 1.421, 1.418, 1.418, 1.419, 1.421, 1.423, 1.428, 1.433, 1.439, 1.443, 1.448, 1.449, 1.453, 1.454, 1.455, 1.456, 1.456, 1.454, 1.453, 1.449, 1.446, 1.441, 1.437, 1.433, 1.429, 1.423, 1.421, 1.419, 1.418, 1.416, 1.417, 1.423, + 1.421, 1.417, 1.417, 1.419, 1.422, 1.424, 1.429, 1.435, 1.441, 1.444, 1.449, 1.453, 1.454, 1.456, 1.458, 1.459, 1.458, 1.456, 1.454, 1.451, 1.448, 1.442, 1.439, 1.435, 1.429, 1.426, 1.421, 1.419, 1.418, 1.416, 1.417, 1.422, + 1.419, 1.418, 1.417, 1.419, 1.422, 1.425, 1.429, 1.436, 1.442, 1.446, 1.451, 1.454, 1.456, 1.458, 1.461, 1.461, 1.461, 1.459, 1.456, 1.453, 1.451, 1.446, 1.441, 1.436, 1.431, 1.427, 1.422, 1.419, 1.418, 1.416, 1.417, 1.422, + 1.419, 1.418, 1.418, 1.421, 1.423, 1.426, 1.431, 1.437, 1.444, 1.449, 1.452, 1.456, 1.458, 1.461, 1.462, 1.463, 1.463, 1.461, 1.458, 1.454, 1.452, 1.447, 1.443, 1.438, 1.432, 1.428, 1.423, 1.421, 1.419, 1.417, 1.417, 1.421, + 1.419, 1.418, 1.417, 1.421, 1.423, 1.428, 1.432, 1.439, 1.445, 1.451, 1.453, 1.457, 1.459, 1.462, 1.464, 1.465, 1.465, 1.463, 1.461, 1.457, 1.453, 1.449, 1.444, 1.441, 1.432, 1.429, 1.425, 1.421, 1.419, 1.417, 1.418, 1.422, + 1.418, 1.417, 1.417, 1.419, 1.423, 1.428, 1.433, 1.439, 1.446, 1.451, 1.453, 1.457, 1.461, 1.464, 1.465, 1.466, 1.466, 1.464, 1.462, 1.459, 1.454, 1.451, 1.445, 1.441, 1.436, 1.429, 1.425, 1.422, 1.421, 1.417, 1.417, 1.423, + 1.417, 1.416, 1.416, 1.419, 1.423, 1.428, 1.433, 1.441, 1.446, 1.451, 1.454, 1.458, 1.461, 1.463, 1.465, 1.466, 1.466, 1.465, 1.463, 1.459, 1.454, 1.451, 1.446, 1.441, 1.437, 1.431, 1.426, 1.422, 1.421, 1.418, 1.418, 1.423, + 1.417, 1.416, 1.417, 1.418, 1.423, 1.428, 1.433, 1.439, 1.445, 1.451, 1.453, 1.457, 1.461, 1.463, 1.465, 1.466, 1.466, 1.464, 1.462, 1.459, 1.454, 1.451, 1.446, 1.441, 1.437, 1.431, 1.426, 1.422, 1.419, 1.417, 1.417, 1.422, + 1.417, 1.416, 1.416, 1.418, 1.422, 1.428, 1.433, 1.438, 1.444, 1.449, 1.453, 1.456, 1.459, 1.462, 1.464, 1.465, 1.465, 1.463, 1.461, 1.458, 1.453, 1.449, 1.445, 1.441, 1.435, 1.429, 1.426, 1.421, 1.419, 1.417, 1.417, 1.422, + 1.418, 1.416, 1.416, 1.418, 1.421, 1.426, 1.432, 1.438, 1.443, 1.447, 1.451, 1.454, 1.458, 1.459, 1.462, 1.463, 1.463, 1.462, 1.459, 1.455, 1.451, 1.447, 1.443, 1.439, 1.434, 1.429, 1.425, 1.421, 1.419, 1.417, 1.417, 1.422, + 1.418, 1.416, 1.416, 1.418, 1.421, 1.425, 1.431, 1.435, 1.442, 1.445, 1.449, 1.452, 1.455, 1.458, 1.458, 1.461, 1.461, 1.459, 1.456, 1.453, 1.449, 1.445, 1.442, 1.436, 1.433, 1.427, 1.425, 1.421, 1.419, 1.418, 1.418, 1.422, + 1.419, 1.416, 1.415, 1.417, 1.419, 1.424, 1.429, 1.434, 1.439, 1.443, 1.446, 1.449, 1.452, 1.454, 1.456, 1.457, 1.457, 1.456, 1.453, 1.451, 1.447, 1.443, 1.441, 1.435, 1.431, 1.426, 1.424, 1.421, 1.419, 1.418, 1.418, 1.422, + 1.419, 1.416, 1.415, 1.416, 1.419, 1.422, 1.426, 1.433, 1.437, 1.441, 1.444, 1.447, 1.449, 1.452, 1.453, 1.455, 1.455, 1.453, 1.451, 1.447, 1.444, 1.441, 1.438, 1.432, 1.428, 1.424, 1.421, 1.419, 1.418, 1.417, 1.417, 1.421, + 1.419, 1.416, 1.415, 1.416, 1.418, 1.421, 1.425, 1.431, 1.435, 1.438, 1.442, 1.445, 1.446, 1.449, 1.451, 1.451, 1.451, 1.451, 1.447, 1.445, 1.443, 1.439, 1.434, 1.431, 1.427, 1.422, 1.421, 1.418, 1.417, 1.417, 1.417, 1.421, + 1.418, 1.416, 1.415, 1.416, 1.417, 1.421, 1.423, 1.428, 1.433, 1.437, 1.439, 1.442, 1.444, 1.446, 1.448, 1.449, 1.449, 1.447, 1.445, 1.443, 1.439, 1.437, 1.432, 1.429, 1.425, 1.422, 1.419, 1.417, 1.417, 1.416, 1.416, 1.419, + 1.418, 1.416, 1.416, 1.416, 1.417, 1.421, 1.422, 1.426, 1.429, 1.433, 1.436, 1.438, 1.441, 1.443, 1.445, 1.446, 1.445, 1.445, 1.443, 1.439, 1.437, 1.434, 1.431, 1.427, 1.424, 1.421, 1.419, 1.417, 1.417, 1.416, 1.416, 1.421, + 1.419, 1.417, 1.416, 1.416, 1.417, 1.421, 1.422, 1.424, 1.427, 1.429, 1.432, 1.436, 1.437, 1.439, 1.442, 1.443, 1.443, 1.441, 1.439, 1.437, 1.434, 1.431, 1.429, 1.425, 1.422, 1.421, 1.419, 1.417, 1.416, 1.416, 1.417, 1.419, + 1.421, 1.418, 1.416, 1.417, 1.418, 1.421, 1.421, 1.423, 1.424, 1.427, 1.429, 1.432, 1.434, 1.436, 1.438, 1.439, 1.439, 1.438, 1.436, 1.434, 1.431, 1.429, 1.426, 1.423, 1.422, 1.421, 1.418, 1.417, 1.417, 1.417, 1.417, 1.421, + 1.423, 1.419, 1.418, 1.418, 1.419, 1.419, 1.421, 1.422, 1.423, 1.424, 1.427, 1.429, 1.432, 1.432, 1.434, 1.435, 1.435, 1.434, 1.433, 1.431, 1.429, 1.426, 1.424, 1.422, 1.421, 1.419, 1.418, 1.417, 1.417, 1.417, 1.418, 1.421, + 1.425, 1.421, 1.419, 1.419, 1.419, 1.421, 1.421, 1.421, 1.421, 1.423, 1.424, 1.426, 1.428, 1.431, 1.431, 1.432, 1.432, 1.431, 1.431, 1.428, 1.425, 1.425, 1.422, 1.421, 1.419, 1.419, 1.418, 1.418, 1.418, 1.418, 1.419, 1.425, + 1.426, 1.422, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.421, 1.422, 1.424, 1.426, 1.427, 1.428, 1.429, 1.429, 1.429, 1.427, 1.424, 1.423, 1.422, 1.421, 1.419, 1.418, 1.418, 1.418, 1.418, 1.418, 1.418, 1.419, 1.426, + 1.428, 1.425, 1.421, 1.421, 1.421, 1.421, 1.421, 1.419, 1.419, 1.421, 1.422, 1.423, 1.424, 1.426, 1.426, 1.426, 1.426, 1.425, 1.424, 1.424, 1.422, 1.422, 1.421, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.423, 1.426, + 1.429, 1.427, 1.424, 1.422, 1.422, 1.422, 1.421, 1.421, 1.421, 1.422, 1.422, 1.422, 1.424, 1.425, 1.426, 1.426, 1.425, 1.425, 1.424, 1.423, 1.422, 1.422, 1.421, 1.421, 1.421, 1.421, 1.419, 1.419, 1.421, 1.422, 1.424, 1.426 + ] + } + ], + "luminance_lut": + [ + 2.964, 2.872, 2.691, 2.544, 2.416, 2.302, 2.196, 2.093, 2.006, 1.928, 1.852, 1.801, 1.769, 1.752, 1.743, 1.743, 1.743, 1.746, 1.759, 1.784, 1.824, 1.888, 1.968, 2.052, 2.149, 2.253, 2.359, 2.483, 2.626, 2.785, 2.988, 3.051, + 2.872, 2.748, 2.583, 2.442, 2.313, 2.201, 2.104, 2.012, 1.928, 1.852, 1.791, 1.742, 1.701, 1.671, 1.651, 1.643, 1.643, 1.659, 1.685, 1.721, 1.768, 1.824, 1.888, 1.971, 2.068, 2.152, 2.259, 2.381, 2.514, 2.669, 2.853, 2.988, + 2.761, 2.655, 2.497, 2.356, 2.226, 2.114, 2.012, 1.928, 1.845, 1.769, 1.707, 1.653, 1.612, 1.583, 1.562, 1.556, 1.556, 1.572, 1.599, 1.635, 1.681, 1.742, 1.806, 1.888, 1.971, 2.068, 2.175, 2.292, 2.431, 2.576, 2.747, 2.853, + 2.679, 2.571, 2.415, 2.275, 2.151, 2.035, 1.936, 1.845, 1.769, 1.689, 1.623, 1.572, 1.532, 1.501, 1.481, 1.473, 1.473, 1.492, 1.517, 1.556, 1.599, 1.659, 1.731, 1.806, 1.895, 1.992, 2.101, 2.218, 2.349, 2.493, 2.664, 2.753, + 2.609, 2.492, 2.339, 2.204, 2.079, 1.971, 1.865, 1.772, 1.689, 1.619, 1.551, 1.499, 1.457, 1.423, 1.405, 1.397, 1.397, 1.411, 1.438, 1.477, 1.525, 1.585, 1.659, 1.731, 1.823, 1.922, 2.027, 2.148, 2.275, 2.422, 2.586, 2.683, + 2.545, 2.426, 2.279, 2.139, 2.014, 1.903, 1.799, 1.702, 1.619, 1.551, 1.482, 1.427, 1.385, 1.353, 1.331, 1.325, 1.325, 1.338, 1.364, 1.403, 1.455, 1.522, 1.585, 1.665, 1.757, 1.858, 1.963, 2.081, 2.207, 2.356, 2.518, 2.615, + 2.489, 2.367, 2.218, 2.079, 1.956, 1.844, 1.739, 1.642, 1.559, 1.482, 1.426, 1.363, 1.321, 1.287, 1.266, 1.259, 1.259, 1.274, 1.301, 1.339, 1.395, 1.455, 1.523, 1.606, 1.697, 1.797, 1.905, 2.024, 2.154, 2.296, 2.455, 2.563, + 2.439, 2.316, 2.164, 2.028, 1.906, 1.793, 1.686, 1.589, 1.505, 1.427, 1.363, 1.308, 1.261, 1.229, 1.207, 1.202, 1.202, 1.215, 1.242, 1.283, 1.339, 1.395, 1.467, 1.551, 1.639, 1.742, 1.851, 1.972, 2.104, 2.243, 2.402, 2.515, + 2.398, 2.262, 2.116, 1.982, 1.861, 1.745, 1.639, 1.541, 1.456, 1.377, 1.308, 1.261, 1.208, 1.177, 1.157, 1.153, 1.153, 1.167, 1.191, 1.233, 1.283, 1.343, 1.418, 1.499, 1.591, 1.696, 1.804, 1.928, 2.057, 2.194, 2.352, 2.471, + 2.363, 2.222, 2.078, 1.942, 1.818, 1.706, 1.597, 1.501, 1.412, 1.334, 1.266, 1.208, 1.171, 1.134, 1.113, 1.109, 1.109, 1.123, 1.149, 1.191, 1.233, 1.296, 1.371, 1.457, 1.546, 1.654, 1.768, 1.886, 2.014, 2.155, 2.312, 2.436, + 2.334, 2.188, 2.042, 1.909, 1.783, 1.668, 1.561, 1.464, 1.374, 1.295, 1.228, 1.171, 1.134, 1.098, 1.076, 1.072, 1.072, 1.087, 1.119, 1.149, 1.196, 1.259, 1.332, 1.419, 1.514, 1.616, 1.728, 1.849, 1.981, 2.123, 2.276, 2.406, + 2.306, 2.159, 2.015, 1.881, 1.753, 1.639, 1.533, 1.434, 1.341, 1.263, 1.195, 1.139, 1.098, 1.074, 1.046, 1.044, 1.045, 1.059, 1.087, 1.119, 1.165, 1.227, 1.302, 1.387, 1.482, 1.586, 1.698, 1.819, 1.953, 2.093, 2.248, 2.383, + 2.291, 2.141, 1.991, 1.856, 1.732, 1.615, 1.508, 1.409, 1.318, 1.238, 1.171, 1.114, 1.074, 1.046, 1.027, 1.023, 1.025, 1.043, 1.059, 1.095, 1.142, 1.203, 1.278, 1.362, 1.456, 1.559, 1.673, 1.796, 1.928, 2.071, 2.225, 2.359, + 2.279, 2.118, 1.972, 1.839, 1.715, 1.599, 1.488, 1.389, 1.298, 1.219, 1.153, 1.097, 1.057, 1.027, 1.018, 1.009, 1.013, 1.025, 1.044, 1.078, 1.125, 1.186, 1.258, 1.342, 1.438, 1.541, 1.655, 1.779, 1.909, 2.053, 2.211, 2.351, + 2.274, 2.108, 1.963, 1.831, 1.706, 1.588, 1.477, 1.376, 1.288, 1.207, 1.139, 1.086, 1.049, 1.021, 1.005, 1.002, 1.004, 1.013, 1.035, 1.069, 1.116, 1.176, 1.246, 1.331, 1.427, 1.531, 1.645, 1.767, 1.899, 2.045, 2.197, 2.351, + 2.274, 2.106, 1.961, 1.827, 1.701, 1.585, 1.474, 1.374, 1.285, 1.206, 1.139, 1.085, 1.047, 1.019, 1.003, 1.001, 1.001, 1.012, 1.033, 1.067, 1.113, 1.173, 1.245, 1.329, 1.423, 1.529, 1.642, 1.765, 1.897, 2.042, 2.196, 2.349, + 2.274, 2.108, 1.961, 1.827, 1.701, 1.585, 1.474, 1.374, 1.285, 1.206, 1.139, 1.085, 1.047, 1.021, 1.005, 1.001, 1.004, 1.012, 1.033, 1.068, 1.113, 1.173, 1.246, 1.329, 1.423, 1.529, 1.642, 1.766, 1.897, 2.042, 2.198, 2.349, + 2.278, 2.116, 1.968, 1.833, 1.707, 1.591, 1.482, 1.382, 1.291, 1.214, 1.147, 1.091, 1.055, 1.028, 1.016, 1.006, 1.012, 1.018, 1.039, 1.074, 1.121, 1.182, 1.255, 1.339, 1.433, 1.538, 1.651, 1.777, 1.911, 2.051, 2.207, 2.351, + 2.283, 2.127, 1.979, 1.846, 1.723, 1.605, 1.496, 1.397, 1.309, 1.229, 1.162, 1.108, 1.067, 1.041, 1.027, 1.018, 1.018, 1.036, 1.051, 1.087, 1.136, 1.197, 1.269, 1.354, 1.448, 1.554, 1.664, 1.789, 1.922, 2.065, 2.222, 2.365, + 2.298, 2.145, 1.999, 1.865, 1.744, 1.627, 1.518, 1.421, 1.331, 1.251, 1.183, 1.129, 1.087, 1.065, 1.041, 1.036, 1.036, 1.051, 1.074, 1.107, 1.158, 1.219, 1.292, 1.378, 1.471, 1.575, 1.687, 1.809, 1.942, 2.085, 2.239, 2.378, + 2.315, 2.174, 2.024, 1.893, 1.768, 1.652, 1.543, 1.445, 1.355, 1.278, 1.211, 1.155, 1.116, 1.087, 1.066, 1.061, 1.061, 1.074, 1.105, 1.137, 1.186, 1.248, 1.322, 1.405, 1.498, 1.602, 1.713, 1.835, 1.965, 2.109, 2.267, 2.399, + 2.341, 2.206, 2.057, 1.923, 1.799, 1.685, 1.576, 1.479, 1.392, 1.312, 1.244, 1.187, 1.154, 1.116, 1.096, 1.092, 1.092, 1.106, 1.137, 1.173, 1.221, 1.282, 1.356, 1.439, 1.532, 1.635, 1.747, 1.869, 1.997, 2.141, 2.298, 2.425, + 2.375, 2.244, 2.098, 1.965, 1.839, 1.722, 1.614, 1.519, 1.434, 1.355, 1.288, 1.234, 1.187, 1.155, 1.136, 1.132, 1.132, 1.147, 1.173, 1.219, 1.263, 1.324, 1.398, 1.479, 1.571, 1.674, 1.784, 1.904, 2.035, 2.177, 2.336, 2.455, + 2.414, 2.286, 2.144, 2.011, 1.883, 1.767, 1.661, 1.566, 1.479, 1.401, 1.335, 1.286, 1.234, 1.202, 1.183, 1.178, 1.178, 1.195, 1.222, 1.263, 1.313, 1.372, 1.444, 1.526, 1.618, 1.718, 1.827, 1.951, 2.081, 2.221, 2.379, 2.498, + 2.463, 2.339, 2.191, 2.056, 1.931, 1.819, 1.712, 1.616, 1.529, 1.452, 1.392, 1.335, 1.286, 1.254, 1.235, 1.232, 1.232, 1.248, 1.275, 1.313, 1.371, 1.425, 1.495, 1.576, 1.671, 1.768, 1.877, 1.999, 2.128, 2.269, 2.428, 2.541, + 2.514, 2.396, 2.247, 2.112, 1.988, 1.873, 1.766, 1.671, 1.588, 1.513, 1.452, 1.392, 1.348, 1.316, 1.298, 1.292, 1.292, 1.307, 1.336, 1.373, 1.425, 1.486, 1.552, 1.636, 1.728, 1.826, 1.933, 2.051, 2.183, 2.327, 2.488, 2.587, + 2.573, 2.459, 2.307, 2.171, 2.049, 1.931, 1.828, 1.731, 1.649, 1.582, 1.513, 1.459, 1.415, 1.381, 1.363, 1.358, 1.358, 1.373, 1.399, 1.439, 1.486, 1.552, 1.617, 1.696, 1.787, 1.888, 1.995, 2.112, 2.244, 2.391, 2.552, 2.652, + 2.635, 2.525, 2.377, 2.239, 2.111, 1.996, 1.895, 1.799, 1.719, 1.649, 1.582, 1.531, 1.486, 1.454, 1.434, 1.429, 1.429, 1.444, 1.469, 1.507, 1.555, 1.617, 1.692, 1.766, 1.854, 1.954, 2.065, 2.181, 2.313, 2.459, 2.623, 2.722, + 2.714, 2.604, 2.452, 2.313, 2.188, 2.071, 1.966, 1.876, 1.799, 1.719, 1.656, 1.604, 1.562, 1.529, 1.511, 1.504, 1.504, 1.519, 1.544, 1.583, 1.632, 1.692, 1.766, 1.839, 1.929, 2.029, 2.138, 2.259, 2.391, 2.539, 2.712, 2.811, + 2.809, 2.698, 2.537, 2.396, 2.277, 2.163, 2.053, 1.965, 1.876, 1.799, 1.741, 1.688, 1.643, 1.613, 1.592, 1.586, 1.586, 1.601, 1.628, 1.666, 1.715, 1.773, 1.839, 1.927, 2.012, 2.111, 2.222, 2.342, 2.477, 2.625, 2.811, 2.926, + 2.921, 2.809, 2.637, 2.493, 2.376, 2.256, 2.149, 2.053, 1.966, 1.893, 1.832, 1.778, 1.736, 1.708, 1.687, 1.681, 1.681, 1.696, 1.721, 1.757, 1.806, 1.864, 1.929, 2.012, 2.106, 2.199, 2.313, 2.437, 2.577, 2.731, 2.926, 3.051, + 3.029, 2.921, 2.745, 2.591, 2.474, 2.355, 2.246, 2.146, 2.049, 1.966, 1.893, 1.832, 1.799, 1.776, 1.768, 1.768, 1.768, 1.771, 1.783, 1.809, 1.864, 1.929, 2.012, 2.097, 2.195, 2.297, 2.412, 2.539, 2.682, 2.846, 3.051, 3.123 + ], + "sigma": 0.00463, + "sigma_Cb": 0.00149 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "lo_max": 1000, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2498, + "ccm": + [ + 1.58731, -0.18011, -0.40721, + -0.60639, 2.03422, -0.42782, + -0.19612, -1.69203, 2.88815 + ] + }, + { + "ct": 2811, + "ccm": + [ + 1.61593, -0.33164, -0.28429, + -0.55048, 1.97779, -0.42731, + -0.12042, -1.42847, 2.54889 + ] + }, + { + "ct": 2911, + "ccm": + [ + 1.62771, -0.41282, -0.21489, + -0.57991, 2.04176, -0.46186, + -0.07613, -1.13359, 2.20972 + ] + }, + { + "ct": 2919, + "ccm": + [ + 1.62661, -0.37736, -0.24925, + -0.52519, 1.95233, -0.42714, + -0.10842, -1.34929, 2.45771 + ] + }, + { + "ct": 3627, + "ccm": + [ + 1.70385, -0.57231, -0.13154, + -0.47763, 1.85998, -0.38235, + -0.07467, -0.82678, 1.90145 + ] + }, + { + "ct": 4600, + "ccm": + [ + 1.68486, -0.61085, -0.07402, + -0.41927, 2.04016, -0.62089, + -0.08633, -0.67672, 1.76305 + ] + }, + { + "ct": 5716, + "ccm": + [ + 1.80439, -0.73699, -0.06739, + -0.36073, 1.83327, -0.47255, + -0.08378, -0.56403, 1.64781 + ] + }, + { + "ct": 8575, + "ccm": + [ + 1.89357, -0.76427, -0.12931, + -0.27399, 2.15605, -0.88206, + -0.12035, -0.68256, 1.80292 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx283.json b/src/ipa/rpi/pisp/data/imx283.json new file mode 100644 index 00000000..2e90512c --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx283.json @@ -0,0 +1,1334 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3200 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 10857, + "reference_gain": 1.49, + "reference_aperture": 1.0, + "reference_lux": 1050, + "reference_Y": 13959 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.147 + } + }, + { + "rpi.geq": + { + "offset": 249, + "slope": 0.02036 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 6000, + "hi": 6800, + } + }, + "bayes": 1, + "ct_curve": + [ + 2500.0, 0.9437, 0.2866, + 2820.0, 0.8496, 0.3541, + 2830.0, 0.8309, 0.3681, + 2885.0, 0.8183, 0.3778, + 3601.0, 0.6946, 0.4786, + 3615.0, 0.6929, 0.4801, + 3622.0, 0.6905, 0.4821, + 4345.0, 0.6012, 0.5628, + 4410.0, 0.5956, 0.5682, + 4486.0, 0.5892, 0.5743, + 4576.0, 0.5794, 0.5837, + 5672.0, 0.5232, 0.6392, + 5710.0, 0.5188, 0.6436, + 6850.0, 0.4862, 0.6773 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02634, + "transverse_neg": 0.02491 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 2940, + "table": + [ + 1.023, 1.024, 1.028, 1.032, 1.034, 1.036, 1.037, 1.038, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.036, 1.035, 1.033, 1.031, 1.026, 1.022, 1.019, 1.014, 1.012, + 1.021, 1.023, 1.026, 1.027, 1.028, 1.029, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.027, 1.025, 1.021, 1.016, 1.012, 1.009, + 1.019, 1.022, 1.024, 1.026, 1.028, 1.029, 1.029, 1.031, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.027, 1.026, 1.024, 1.021, 1.016, 1.011, 1.009, + 1.021, 1.023, 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.026, 1.024, 1.022, 1.018, 1.012, 1.011, + 1.021, 1.024, 1.025, 1.027, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.025, 1.023, 1.019, 1.014, 1.011, + 1.023, 1.025, 1.026, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.027, 1.025, 1.023, 1.021, 1.015, 1.011, + 1.023, 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.027, 1.028, 1.028, 1.029, 1.029, 1.029, 1.028, 1.026, 1.023, 1.021, 1.015, 1.012, + 1.023, 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.028, 1.029, 1.029, 1.029, 1.028, 1.026, 1.024, 1.021, 1.016, 1.012, + 1.023, 1.025, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.025, 1.025, 1.026, 1.026, 1.027, 1.027, 1.028, 1.029, 1.029, 1.028, 1.027, 1.025, 1.022, 1.017, 1.014, + 1.024, 1.025, 1.027, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.027, 1.025, 1.023, 1.018, 1.015, + 1.024, 1.026, 1.027, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.024, 1.024, 1.025, 1.025, 1.025, 1.025, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.027, 1.026, 1.023, 1.018, 1.016, + 1.025, 1.026, 1.027, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.025, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.024, 1.024, 1.025, 1.026, 1.027, 1.028, 1.028, 1.029, 1.029, 1.027, 1.026, 1.023, 1.018, 1.016, + 1.025, 1.026, 1.027, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.025, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.024, 1.024, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.027, 1.026, 1.023, 1.018, 1.016, + 1.025, 1.026, 1.027, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.024, 1.024, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.028, 1.026, 1.023, 1.018, 1.016, + 1.025, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.024, 1.025, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.027, 1.026, 1.023, 1.018, 1.016, + 1.025, 1.027, 1.028, 1.028, 1.029, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.025, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.025, 1.025, 1.026, 1.026, 1.027, 1.028, 1.028, 1.028, 1.027, 1.025, 1.023, 1.018, 1.016, + 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.025, 1.025, 1.026, 1.027, 1.027, 1.028, 1.028, 1.028, 1.027, 1.025, 1.023, 1.018, 1.015, + 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.026, 1.026, 1.026, 1.025, 1.024, 1.024, 1.024, 1.025, 1.025, 1.025, 1.026, 1.027, 1.027, 1.028, 1.028, 1.028, 1.027, 1.025, 1.023, 1.018, 1.016, + 1.025, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.026, 1.026, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.028, 1.027, 1.025, 1.022, 1.017, 1.015, + 1.025, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.025, 1.025, 1.026, 1.026, 1.026, 1.028, 1.028, 1.029, 1.029, 1.027, 1.025, 1.022, 1.017, 1.015, + 1.025, 1.027, 1.028, 1.029, 1.029, 1.031, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.026, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.027, 1.025, 1.022, 1.017, 1.014, + 1.025, 1.027, 1.028, 1.029, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.028, 1.028, 1.029, 1.028, 1.026, 1.024, 1.021, 1.016, 1.014, + 1.025, 1.027, 1.028, 1.029, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.026, 1.023, 1.021, 1.014, 1.012, + 1.024, 1.027, 1.028, 1.029, 1.031, 1.032, 1.032, 1.031, 1.031, 1.029, 1.029, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.027, 1.026, 1.023, 1.019, 1.014, 1.011, + 1.024, 1.027, 1.028, 1.029, 1.031, 1.032, 1.032, 1.032, 1.031, 1.031, 1.029, 1.029, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.025, 1.022, 1.018, 1.012, 1.009, + 1.024, 1.026, 1.028, 1.029, 1.032, 1.032, 1.032, 1.032, 1.032, 1.031, 1.031, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.028, 1.027, 1.026, 1.025, 1.022, 1.018, 1.011, 1.009, + 1.023, 1.026, 1.028, 1.029, 1.032, 1.033, 1.033, 1.033, 1.033, 1.032, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.025, 1.024, 1.021, 1.016, 1.011, 1.008, + 1.022, 1.026, 1.028, 1.031, 1.032, 1.033, 1.033, 1.034, 1.034, 1.033, 1.032, 1.032, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.028, 1.027, 1.025, 1.023, 1.021, 1.015, 1.009, 1.007, + 1.022, 1.025, 1.028, 1.031, 1.032, 1.033, 1.034, 1.034, 1.035, 1.034, 1.034, 1.033, 1.032, 1.032, 1.032, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.028, 1.027, 1.025, 1.023, 1.019, 1.014, 1.008, 1.006, + 1.021, 1.024, 1.027, 1.029, 1.031, 1.033, 1.033, 1.034, 1.034, 1.034, 1.034, 1.034, 1.033, 1.033, 1.032, 1.032, 1.032, 1.032, 1.032, 1.032, 1.031, 1.031, 1.031, 1.029, 1.027, 1.026, 1.024, 1.021, 1.017, 1.013, 1.007, 1.004, + 1.019, 1.022, 1.026, 1.028, 1.031, 1.032, 1.033, 1.033, 1.034, 1.034, 1.034, 1.034, 1.034, 1.033, 1.033, 1.033, 1.032, 1.032, 1.032, 1.032, 1.031, 1.031, 1.029, 1.028, 1.026, 1.024, 1.021, 1.018, 1.015, 1.011, 1.004, 1.002, + 1.018, 1.021, 1.025, 1.027, 1.029, 1.031, 1.032, 1.033, 1.033, 1.034, 1.034, 1.034, 1.034, 1.034, 1.033, 1.033, 1.032, 1.032, 1.032, 1.031, 1.031, 1.029, 1.029, 1.027, 1.025, 1.023, 1.019, 1.017, 1.013, 1.009, 1.002, 1.001 + ] + }, + { + "ct": 4000, + "table": + [ + 1.027, 1.032, 1.036, 1.039, 1.044, 1.047, 1.049, 1.051, 1.051, 1.052, 1.052, 1.051, 1.051, 1.051, 1.051, 1.052, 1.052, 1.052, 1.053, 1.053, 1.053, 1.053, 1.052, 1.051, 1.049, 1.047, 1.043, 1.039, 1.036, 1.031, 1.021, 1.015, + 1.027, 1.029, 1.035, 1.038, 1.041, 1.042, 1.042, 1.042, 1.043, 1.043, 1.042, 1.042, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.044, 1.044, 1.044, 1.045, 1.045, 1.044, 1.044, 1.043, 1.041, 1.037, 1.033, 1.026, 1.018, 1.015, + 1.025, 1.029, 1.033, 1.036, 1.039, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.043, 1.043, 1.043, 1.044, 1.044, 1.044, 1.044, 1.043, 1.042, 1.039, 1.037, 1.033, 1.026, 1.018, 1.014, + 1.026, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.042, 1.041, 1.038, 1.034, 1.028, 1.021, 1.017, + 1.028, 1.032, 1.035, 1.037, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.041, 1.039, 1.036, 1.031, 1.023, 1.019, + 1.029, 1.033, 1.036, 1.038, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.041, 1.039, 1.036, 1.032, 1.024, 1.019, + 1.029, 1.034, 1.036, 1.038, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.042, 1.043, 1.043, 1.043, 1.043, 1.042, 1.039, 1.036, 1.033, 1.024, 1.021, + 1.029, 1.034, 1.036, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.043, 1.043, 1.043, 1.042, 1.041, 1.037, 1.033, 1.026, 1.022, + 1.029, 1.033, 1.036, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.043, 1.043, 1.042, 1.041, 1.038, 1.034, 1.027, 1.024, + 1.031, 1.033, 1.036, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.042, 1.043, 1.043, 1.042, 1.041, 1.038, 1.034, 1.028, 1.025, + 1.031, 1.034, 1.036, 1.037, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.041, 1.039, 1.035, 1.028, 1.025, + 1.031, 1.034, 1.036, 1.037, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.042, 1.042, 1.042, 1.041, 1.039, 1.035, 1.029, 1.025, + 1.031, 1.034, 1.036, 1.037, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.042, 1.042, 1.041, 1.039, 1.035, 1.029, 1.025, + 1.031, 1.034, 1.036, 1.037, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.042, 1.042, 1.041, 1.039, 1.035, 1.029, 1.025, + 1.031, 1.034, 1.036, 1.037, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.038, 1.034, 1.028, 1.025, + 1.031, 1.035, 1.036, 1.037, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.038, 1.034, 1.028, 1.024, + 1.031, 1.035, 1.036, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.038, 1.034, 1.028, 1.024, + 1.031, 1.035, 1.036, 1.037, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.038, 1.034, 1.028, 1.024, + 1.031, 1.034, 1.037, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.039, 1.037, 1.033, 1.027, 1.024, + 1.031, 1.034, 1.036, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.039, 1.037, 1.033, 1.027, 1.023, + 1.029, 1.033, 1.036, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.039, 1.037, 1.033, 1.026, 1.022, + 1.029, 1.033, 1.036, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.039, 1.038, 1.036, 1.032, 1.025, 1.021, + 1.029, 1.033, 1.036, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.035, 1.031, 1.023, 1.019, + 1.029, 1.033, 1.036, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.037, 1.034, 1.029, 1.021, 1.018, + 1.028, 1.032, 1.035, 1.038, 1.039, 1.039, 1.041, 1.041, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.037, 1.033, 1.028, 1.019, 1.017, + 1.028, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.041, 1.041, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.036, 1.032, 1.027, 1.018, 1.015, + 1.027, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.039, 1.039, 1.038, 1.036, 1.035, 1.031, 1.025, 1.016, 1.013, + 1.025, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.036, 1.034, 1.029, 1.022, 1.014, 1.011, + 1.023, 1.029, 1.034, 1.037, 1.039, 1.041, 1.042, 1.042, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.035, 1.033, 1.028, 1.021, 1.012, 1.009, + 1.022, 1.027, 1.032, 1.036, 1.038, 1.039, 1.041, 1.042, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.035, 1.033, 1.031, 1.025, 1.018, 1.009, 1.005, + 1.019, 1.024, 1.029, 1.034, 1.036, 1.039, 1.039, 1.041, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.036, 1.033, 1.031, 1.026, 1.021, 1.014, 1.005, 1.003, + 1.017, 1.022, 1.028, 1.032, 1.036, 1.038, 1.039, 1.041, 1.041, 1.042, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.036, 1.034, 1.032, 1.028, 1.024, 1.019, 1.012, 1.003, 1.001 + ] + }, + { + "ct": 6000, + "table": + [ + 1.021, 1.033, 1.041, 1.046, 1.051, 1.054, 1.057, 1.061, 1.064, 1.066, 1.068, 1.068, 1.068, 1.068, 1.068, 1.069, 1.069, 1.069, 1.069, 1.069, 1.068, 1.067, 1.064, 1.062, 1.058, 1.056, 1.052, 1.047, 1.041, 1.031, 1.019, 1.012, + 1.021, 1.029, 1.037, 1.043, 1.048, 1.053, 1.056, 1.058, 1.059, 1.059, 1.061, 1.061, 1.061, 1.061, 1.062, 1.062, 1.062, 1.062, 1.062, 1.062, 1.062, 1.062, 1.061, 1.059, 1.058, 1.054, 1.049, 1.044, 1.039, 1.029, 1.018, 1.012, + 1.023, 1.029, 1.037, 1.043, 1.048, 1.052, 1.055, 1.057, 1.058, 1.059, 1.059, 1.061, 1.061, 1.061, 1.062, 1.062, 1.062, 1.062, 1.062, 1.062, 1.062, 1.061, 1.061, 1.059, 1.057, 1.054, 1.049, 1.044, 1.039, 1.029, 1.018, 1.015, + 1.025, 1.032, 1.039, 1.045, 1.049, 1.054, 1.057, 1.058, 1.059, 1.059, 1.061, 1.061, 1.061, 1.062, 1.062, 1.062, 1.062, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.058, 1.055, 1.051, 1.046, 1.041, 1.033, 1.022, 1.018, + 1.027, 1.035, 1.041, 1.046, 1.051, 1.054, 1.057, 1.058, 1.059, 1.061, 1.061, 1.061, 1.062, 1.062, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.061, 1.061, 1.059, 1.056, 1.053, 1.048, 1.043, 1.036, 1.025, 1.019, + 1.029, 1.036, 1.043, 1.049, 1.052, 1.055, 1.057, 1.058, 1.059, 1.061, 1.061, 1.062, 1.062, 1.063, 1.063, 1.063, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.061, 1.061, 1.058, 1.054, 1.051, 1.045, 1.039, 1.027, 1.021, + 1.031, 1.039, 1.045, 1.049, 1.053, 1.056, 1.058, 1.059, 1.059, 1.061, 1.062, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.051, 1.046, 1.039, 1.028, 1.023, + 1.032, 1.039, 1.045, 1.051, 1.054, 1.056, 1.058, 1.059, 1.059, 1.061, 1.062, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.062, 1.062, 1.062, 1.061, 1.061, 1.057, 1.053, 1.048, 1.041, 1.031, 1.025, + 1.033, 1.039, 1.046, 1.051, 1.054, 1.056, 1.058, 1.059, 1.059, 1.061, 1.061, 1.062, 1.063, 1.063, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.062, 1.061, 1.058, 1.054, 1.049, 1.043, 1.032, 1.027, + 1.034, 1.041, 1.046, 1.051, 1.054, 1.056, 1.057, 1.059, 1.059, 1.061, 1.062, 1.063, 1.063, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.062, 1.061, 1.059, 1.054, 1.049, 1.044, 1.034, 1.029, + 1.034, 1.041, 1.046, 1.051, 1.054, 1.056, 1.058, 1.059, 1.059, 1.061, 1.063, 1.063, 1.063, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.062, 1.062, 1.061, 1.059, 1.055, 1.051, 1.044, 1.035, 1.029, + 1.035, 1.041, 1.047, 1.051, 1.054, 1.056, 1.058, 1.059, 1.061, 1.062, 1.063, 1.063, 1.063, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.062, 1.061, 1.059, 1.056, 1.051, 1.045, 1.035, 1.029, + 1.036, 1.042, 1.047, 1.052, 1.054, 1.056, 1.058, 1.059, 1.061, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.052, 1.045, 1.036, 1.031, + 1.036, 1.043, 1.048, 1.052, 1.054, 1.056, 1.058, 1.059, 1.061, 1.062, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.061, 1.056, 1.052, 1.046, 1.036, 1.031, + 1.037, 1.043, 1.048, 1.052, 1.055, 1.057, 1.059, 1.061, 1.061, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.052, 1.045, 1.036, 1.031, + 1.037, 1.044, 1.048, 1.053, 1.055, 1.058, 1.059, 1.061, 1.062, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.052, 1.045, 1.036, 1.031, + 1.037, 1.044, 1.049, 1.053, 1.056, 1.058, 1.059, 1.061, 1.062, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.052, 1.045, 1.036, 1.031, + 1.037, 1.044, 1.049, 1.053, 1.056, 1.058, 1.059, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.065, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.056, 1.051, 1.045, 1.036, 1.031, + 1.037, 1.044, 1.049, 1.053, 1.056, 1.058, 1.061, 1.062, 1.063, 1.063, 1.064, 1.064, 1.065, 1.065, 1.065, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.061, 1.059, 1.056, 1.051, 1.044, 1.035, 1.031, + 1.037, 1.043, 1.049, 1.053, 1.056, 1.058, 1.061, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.061, 1.059, 1.055, 1.051, 1.044, 1.034, 1.029, + 1.035, 1.042, 1.048, 1.053, 1.056, 1.058, 1.059, 1.061, 1.062, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.061, 1.059, 1.055, 1.051, 1.043, 1.034, 1.027, + 1.034, 1.042, 1.048, 1.053, 1.056, 1.058, 1.061, 1.061, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.061, 1.058, 1.054, 1.049, 1.042, 1.032, 1.026, + 1.034, 1.041, 1.047, 1.053, 1.056, 1.058, 1.061, 1.061, 1.062, 1.062, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.061, 1.059, 1.057, 1.053, 1.048, 1.041, 1.031, 1.026, + 1.034, 1.041, 1.047, 1.052, 1.056, 1.059, 1.061, 1.061, 1.062, 1.062, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.058, 1.056, 1.052, 1.046, 1.038, 1.028, 1.024, + 1.032, 1.039, 1.045, 1.052, 1.056, 1.058, 1.061, 1.061, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.062, 1.061, 1.059, 1.057, 1.055, 1.051, 1.045, 1.037, 1.026, 1.022, + 1.032, 1.038, 1.044, 1.049, 1.054, 1.058, 1.061, 1.061, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.062, 1.061, 1.059, 1.057, 1.054, 1.049, 1.044, 1.036, 1.024, 1.019, + 1.029, 1.037, 1.044, 1.049, 1.054, 1.058, 1.059, 1.062, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.063, 1.062, 1.061, 1.058, 1.055, 1.052, 1.048, 1.042, 1.033, 1.022, 1.017, + 1.027, 1.035, 1.042, 1.049, 1.054, 1.057, 1.059, 1.061, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.059, 1.057, 1.054, 1.051, 1.047, 1.039, 1.031, 1.019, 1.014, + 1.025, 1.033, 1.041, 1.047, 1.052, 1.056, 1.058, 1.061, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.064, 1.064, 1.064, 1.064, 1.064, 1.063, 1.063, 1.063, 1.062, 1.059, 1.056, 1.053, 1.049, 1.044, 1.037, 1.028, 1.016, 1.012, + 1.022, 1.031, 1.038, 1.045, 1.051, 1.053, 1.056, 1.059, 1.061, 1.062, 1.063, 1.063, 1.063, 1.063, 1.064, 1.063, 1.063, 1.063, 1.063, 1.063, 1.063, 1.062, 1.059, 1.057, 1.054, 1.051, 1.047, 1.041, 1.034, 1.025, 1.013, 1.007, + 1.019, 1.026, 1.035, 1.042, 1.047, 1.051, 1.054, 1.057, 1.059, 1.061, 1.062, 1.062, 1.062, 1.063, 1.063, 1.063, 1.062, 1.063, 1.062, 1.062, 1.061, 1.059, 1.057, 1.054, 1.051, 1.047, 1.042, 1.036, 1.029, 1.019, 1.007, 1.005, + 1.016, 1.024, 1.033, 1.041, 1.046, 1.049, 1.053, 1.055, 1.057, 1.059, 1.061, 1.062, 1.062, 1.063, 1.063, 1.062, 1.062, 1.062, 1.062, 1.061, 1.059, 1.057, 1.055, 1.052, 1.049, 1.045, 1.039, 1.033, 1.027, 1.016, 1.005, 1.001 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 2940, + "table": + [ + 1.001, 1.001, 1.014, 1.027, 1.038, 1.047, 1.051, 1.054, 1.058, 1.061, 1.062, 1.064, 1.066, 1.066, 1.066, 1.067, 1.067, 1.067, 1.067, 1.067, 1.067, 1.066, 1.064, 1.063, 1.061, 1.057, 1.055, 1.049, 1.043, 1.032, 1.026, 1.018, + 1.001, 1.013, 1.023, 1.032, 1.042, 1.049, 1.056, 1.063, 1.066, 1.069, 1.073, 1.076, 1.077, 1.078, 1.079, 1.079, 1.081, 1.081, 1.079, 1.079, 1.078, 1.077, 1.076, 1.073, 1.071, 1.066, 1.061, 1.053, 1.046, 1.037, 1.031, 1.026, + 1.008, 1.019, 1.031, 1.042, 1.049, 1.056, 1.063, 1.066, 1.069, 1.073, 1.076, 1.078, 1.078, 1.081, 1.082, 1.082, 1.082, 1.081, 1.081, 1.081, 1.079, 1.079, 1.077, 1.076, 1.073, 1.071, 1.066, 1.061, 1.053, 1.046, 1.035, 1.032, + 1.012, 1.023, 1.036, 1.045, 1.053, 1.059, 1.065, 1.068, 1.072, 1.075, 1.078, 1.079, 1.081, 1.083, 1.084, 1.084, 1.084, 1.084, 1.083, 1.083, 1.081, 1.081, 1.079, 1.078, 1.076, 1.073, 1.069, 1.063, 1.056, 1.049, 1.039, 1.034, + 1.015, 1.027, 1.038, 1.048, 1.056, 1.061, 1.067, 1.071, 1.074, 1.076, 1.079, 1.082, 1.084, 1.085, 1.086, 1.086, 1.086, 1.086, 1.085, 1.085, 1.084, 1.083, 1.081, 1.079, 1.077, 1.075, 1.072, 1.066, 1.059, 1.051, 1.041, 1.038, + 1.018, 1.031, 1.041, 1.051, 1.058, 1.064, 1.069, 1.073, 1.075, 1.079, 1.082, 1.084, 1.085, 1.087, 1.088, 1.088, 1.088, 1.088, 1.087, 1.087, 1.086, 1.084, 1.083, 1.081, 1.079, 1.077, 1.074, 1.069, 1.061, 1.052, 1.043, 1.038, + 1.021, 1.033, 1.045, 1.053, 1.059, 1.066, 1.071, 1.074, 1.078, 1.081, 1.084, 1.085, 1.087, 1.088, 1.089, 1.089, 1.089, 1.089, 1.089, 1.088, 1.087, 1.086, 1.085, 1.083, 1.081, 1.079, 1.075, 1.071, 1.062, 1.054, 1.044, 1.039, + 1.023, 1.035, 1.046, 1.055, 1.062, 1.067, 1.072, 1.075, 1.079, 1.083, 1.085, 1.087, 1.088, 1.089, 1.091, 1.091, 1.091, 1.091, 1.091, 1.091, 1.089, 1.087, 1.086, 1.084, 1.082, 1.081, 1.076, 1.072, 1.065, 1.055, 1.045, 1.039, + 1.025, 1.036, 1.048, 1.056, 1.063, 1.069, 1.073, 1.077, 1.081, 1.085, 1.087, 1.088, 1.089, 1.091, 1.091, 1.092, 1.092, 1.092, 1.092, 1.091, 1.091, 1.088, 1.087, 1.085, 1.084, 1.081, 1.078, 1.074, 1.066, 1.057, 1.047, 1.042, + 1.028, 1.039, 1.051, 1.058, 1.065, 1.071, 1.075, 1.079, 1.083, 1.086, 1.088, 1.089, 1.091, 1.092, 1.093, 1.093, 1.093, 1.093, 1.093, 1.093, 1.092, 1.091, 1.088, 1.086, 1.084, 1.082, 1.079, 1.074, 1.067, 1.058, 1.047, 1.043, + 1.029, 1.041, 1.051, 1.059, 1.067, 1.071, 1.076, 1.081, 1.083, 1.087, 1.088, 1.091, 1.092, 1.093, 1.093, 1.094, 1.094, 1.094, 1.094, 1.094, 1.093, 1.092, 1.089, 1.087, 1.085, 1.083, 1.079, 1.074, 1.068, 1.059, 1.048, 1.044, + 1.031, 1.042, 1.053, 1.061, 1.068, 1.072, 1.077, 1.081, 1.084, 1.087, 1.089, 1.091, 1.093, 1.093, 1.094, 1.095, 1.095, 1.095, 1.095, 1.095, 1.094, 1.092, 1.089, 1.088, 1.086, 1.083, 1.081, 1.075, 1.069, 1.061, 1.049, 1.044, + 1.032, 1.043, 1.054, 1.062, 1.068, 1.073, 1.078, 1.081, 1.085, 1.088, 1.089, 1.092, 1.093, 1.094, 1.095, 1.095, 1.096, 1.096, 1.096, 1.095, 1.094, 1.093, 1.091, 1.089, 1.086, 1.083, 1.081, 1.075, 1.069, 1.061, 1.049, 1.044, + 1.033, 1.044, 1.055, 1.062, 1.069, 1.073, 1.079, 1.082, 1.085, 1.088, 1.091, 1.093, 1.094, 1.095, 1.096, 1.096, 1.096, 1.097, 1.096, 1.096, 1.095, 1.093, 1.092, 1.089, 1.087, 1.084, 1.081, 1.076, 1.071, 1.062, 1.049, 1.046, + 1.034, 1.045, 1.056, 1.064, 1.069, 1.074, 1.079, 1.082, 1.086, 1.089, 1.091, 1.093, 1.094, 1.095, 1.096, 1.097, 1.097, 1.097, 1.097, 1.096, 1.095, 1.094, 1.092, 1.089, 1.087, 1.084, 1.081, 1.077, 1.071, 1.062, 1.052, 1.046, + 1.034, 1.046, 1.057, 1.064, 1.069, 1.074, 1.079, 1.083, 1.086, 1.089, 1.091, 1.093, 1.094, 1.096, 1.096, 1.097, 1.097, 1.097, 1.097, 1.096, 1.095, 1.094, 1.092, 1.089, 1.087, 1.084, 1.081, 1.077, 1.071, 1.063, 1.052, 1.046, + 1.035, 1.046, 1.057, 1.064, 1.069, 1.075, 1.079, 1.083, 1.086, 1.089, 1.091, 1.093, 1.094, 1.096, 1.096, 1.097, 1.097, 1.097, 1.097, 1.096, 1.095, 1.094, 1.092, 1.089, 1.087, 1.085, 1.081, 1.077, 1.071, 1.063, 1.052, 1.047, + 1.035, 1.046, 1.057, 1.064, 1.069, 1.075, 1.079, 1.083, 1.086, 1.088, 1.091, 1.092, 1.094, 1.095, 1.096, 1.097, 1.097, 1.097, 1.097, 1.096, 1.095, 1.094, 1.091, 1.089, 1.087, 1.085, 1.082, 1.077, 1.071, 1.063, 1.052, 1.046, + 1.035, 1.046, 1.057, 1.064, 1.069, 1.075, 1.079, 1.083, 1.086, 1.088, 1.091, 1.092, 1.093, 1.095, 1.096, 1.096, 1.097, 1.097, 1.096, 1.095, 1.095, 1.093, 1.091, 1.089, 1.087, 1.084, 1.082, 1.077, 1.071, 1.062, 1.052, 1.047, + 1.035, 1.046, 1.056, 1.063, 1.069, 1.074, 1.077, 1.082, 1.085, 1.087, 1.091, 1.091, 1.093, 1.094, 1.095, 1.096, 1.096, 1.096, 1.095, 1.095, 1.094, 1.093, 1.091, 1.089, 1.086, 1.085, 1.081, 1.077, 1.071, 1.062, 1.052, 1.047, + 1.033, 1.045, 1.055, 1.063, 1.068, 1.073, 1.077, 1.082, 1.084, 1.087, 1.089, 1.091, 1.093, 1.093, 1.094, 1.095, 1.095, 1.095, 1.095, 1.095, 1.094, 1.093, 1.091, 1.089, 1.086, 1.084, 1.081, 1.076, 1.069, 1.062, 1.051, 1.047, + 1.032, 1.045, 1.054, 1.062, 1.067, 1.072, 1.077, 1.081, 1.083, 1.087, 1.089, 1.091, 1.092, 1.093, 1.093, 1.094, 1.094, 1.094, 1.094, 1.094, 1.093, 1.092, 1.091, 1.088, 1.086, 1.083, 1.079, 1.076, 1.069, 1.061, 1.051, 1.047, + 1.031, 1.044, 1.053, 1.061, 1.066, 1.072, 1.077, 1.081, 1.083, 1.087, 1.088, 1.091, 1.091, 1.093, 1.093, 1.093, 1.094, 1.094, 1.094, 1.093, 1.092, 1.091, 1.089, 1.088, 1.086, 1.083, 1.079, 1.075, 1.069, 1.059, 1.051, 1.046, + 1.031, 1.043, 1.051, 1.061, 1.065, 1.072, 1.076, 1.079, 1.083, 1.086, 1.087, 1.091, 1.091, 1.091, 1.092, 1.093, 1.093, 1.093, 1.093, 1.092, 1.091, 1.091, 1.089, 1.086, 1.085, 1.083, 1.079, 1.074, 1.066, 1.059, 1.051, 1.045, + 1.029, 1.039, 1.051, 1.059, 1.065, 1.071, 1.074, 1.077, 1.082, 1.085, 1.087, 1.088, 1.091, 1.091, 1.091, 1.092, 1.092, 1.092, 1.092, 1.091, 1.091, 1.089, 1.088, 1.085, 1.084, 1.082, 1.077, 1.071, 1.065, 1.058, 1.048, 1.043, + 1.028, 1.037, 1.049, 1.057, 1.063, 1.068, 1.073, 1.076, 1.081, 1.082, 1.085, 1.087, 1.089, 1.089, 1.091, 1.091, 1.091, 1.091, 1.091, 1.091, 1.089, 1.088, 1.086, 1.085, 1.083, 1.079, 1.076, 1.071, 1.064, 1.056, 1.046, 1.041, + 1.025, 1.035, 1.047, 1.055, 1.061, 1.067, 1.071, 1.075, 1.078, 1.081, 1.084, 1.086, 1.087, 1.088, 1.089, 1.089, 1.089, 1.089, 1.089, 1.089, 1.088, 1.087, 1.085, 1.084, 1.081, 1.078, 1.075, 1.069, 1.062, 1.054, 1.044, 1.039, + 1.023, 1.032, 1.044, 1.052, 1.059, 1.065, 1.069, 1.073, 1.076, 1.079, 1.081, 1.084, 1.085, 1.086, 1.087, 1.087, 1.088, 1.088, 1.088, 1.087, 1.087, 1.085, 1.084, 1.081, 1.079, 1.076, 1.072, 1.067, 1.059, 1.052, 1.041, 1.037, + 1.019, 1.028, 1.039, 1.048, 1.057, 1.062, 1.067, 1.071, 1.074, 1.077, 1.079, 1.081, 1.083, 1.084, 1.085, 1.085, 1.086, 1.086, 1.085, 1.085, 1.084, 1.083, 1.081, 1.079, 1.077, 1.073, 1.069, 1.063, 1.056, 1.048, 1.038, 1.034, + 1.016, 1.025, 1.035, 1.045, 1.053, 1.059, 1.064, 1.069, 1.072, 1.076, 1.077, 1.079, 1.081, 1.081, 1.082, 1.083, 1.084, 1.084, 1.084, 1.083, 1.083, 1.081, 1.079, 1.077, 1.074, 1.069, 1.065, 1.059, 1.053, 1.044, 1.034, 1.029, + 1.011, 1.022, 1.032, 1.042, 1.051, 1.057, 1.062, 1.067, 1.069, 1.074, 1.076, 1.077, 1.079, 1.079, 1.081, 1.081, 1.081, 1.082, 1.082, 1.081, 1.081, 1.078, 1.077, 1.075, 1.071, 1.066, 1.061, 1.056, 1.049, 1.041, 1.029, 1.028, + 1.008, 1.019, 1.031, 1.039, 1.048, 1.056, 1.061, 1.065, 1.069, 1.073, 1.075, 1.076, 1.078, 1.079, 1.079, 1.079, 1.081, 1.081, 1.081, 1.081, 1.078, 1.078, 1.076, 1.072, 1.069, 1.064, 1.058, 1.054, 1.047, 1.038, 1.029, 1.026 + ] + }, + { + "ct": 4000, + "table": + [ + 1.001, 1.005, 1.011, 1.015, 1.019, 1.021, 1.021, 1.022, 1.024, 1.025, 1.026, 1.027, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.025, 1.025, 1.024, 1.024, 1.024, 1.022, 1.021, 1.018, 1.015, 1.011, + 1.005, 1.008, 1.014, 1.021, 1.025, 1.028, 1.031, 1.032, 1.034, 1.035, 1.036, 1.037, 1.038, 1.038, 1.039, 1.038, 1.038, 1.038, 1.038, 1.037, 1.037, 1.036, 1.036, 1.035, 1.035, 1.034, 1.033, 1.029, 1.028, 1.023, 1.021, 1.021, + 1.009, 1.014, 1.021, 1.025, 1.028, 1.031, 1.032, 1.034, 1.035, 1.037, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.037, 1.036, 1.036, 1.036, 1.036, 1.034, 1.033, 1.029, 1.028, 1.023, 1.022, + 1.011, 1.016, 1.023, 1.027, 1.029, 1.031, 1.034, 1.035, 1.037, 1.038, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.037, 1.036, 1.034, 1.032, 1.029, 1.025, 1.022, + 1.013, 1.018, 1.024, 1.027, 1.031, 1.033, 1.035, 1.037, 1.038, 1.039, 1.041, 1.042, 1.042, 1.043, 1.043, 1.043, 1.042, 1.042, 1.042, 1.041, 1.041, 1.041, 1.039, 1.039, 1.038, 1.038, 1.037, 1.035, 1.034, 1.029, 1.025, 1.023, + 1.014, 1.019, 1.025, 1.029, 1.031, 1.034, 1.037, 1.038, 1.039, 1.041, 1.042, 1.043, 1.043, 1.044, 1.044, 1.044, 1.043, 1.043, 1.043, 1.043, 1.042, 1.041, 1.041, 1.039, 1.039, 1.038, 1.038, 1.037, 1.034, 1.029, 1.026, 1.024, + 1.016, 1.021, 1.027, 1.031, 1.033, 1.035, 1.038, 1.039, 1.041, 1.042, 1.043, 1.044, 1.044, 1.045, 1.045, 1.045, 1.044, 1.044, 1.044, 1.044, 1.043, 1.042, 1.042, 1.041, 1.041, 1.039, 1.038, 1.038, 1.035, 1.031, 1.027, 1.025, + 1.017, 1.022, 1.027, 1.031, 1.034, 1.037, 1.039, 1.041, 1.042, 1.043, 1.044, 1.045, 1.046, 1.046, 1.046, 1.046, 1.046, 1.046, 1.045, 1.045, 1.044, 1.043, 1.043, 1.042, 1.041, 1.041, 1.039, 1.038, 1.036, 1.032, 1.028, 1.025, + 1.018, 1.023, 1.028, 1.032, 1.035, 1.038, 1.041, 1.041, 1.043, 1.045, 1.046, 1.046, 1.046, 1.047, 1.047, 1.047, 1.047, 1.047, 1.047, 1.046, 1.045, 1.044, 1.044, 1.043, 1.042, 1.041, 1.041, 1.039, 1.037, 1.032, 1.028, 1.026, + 1.019, 1.024, 1.029, 1.033, 1.036, 1.039, 1.041, 1.042, 1.044, 1.046, 1.046, 1.047, 1.048, 1.048, 1.048, 1.048, 1.048, 1.048, 1.048, 1.047, 1.047, 1.045, 1.044, 1.044, 1.043, 1.042, 1.041, 1.039, 1.037, 1.033, 1.029, 1.027, + 1.021, 1.026, 1.029, 1.034, 1.037, 1.039, 1.042, 1.043, 1.045, 1.046, 1.047, 1.047, 1.048, 1.048, 1.049, 1.049, 1.049, 1.049, 1.048, 1.048, 1.047, 1.047, 1.046, 1.044, 1.044, 1.043, 1.041, 1.039, 1.037, 1.034, 1.029, 1.027, + 1.022, 1.026, 1.032, 1.035, 1.038, 1.039, 1.043, 1.044, 1.045, 1.046, 1.047, 1.048, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.044, 1.043, 1.041, 1.039, 1.038, 1.034, 1.029, 1.027, + 1.023, 1.027, 1.032, 1.035, 1.039, 1.041, 1.043, 1.044, 1.045, 1.047, 1.048, 1.049, 1.049, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.041, 1.039, 1.035, 1.029, 1.027, + 1.024, 1.028, 1.033, 1.036, 1.039, 1.041, 1.043, 1.045, 1.046, 1.047, 1.049, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.047, 1.046, 1.045, 1.043, 1.042, 1.041, 1.039, 1.035, 1.029, 1.029, + 1.024, 1.029, 1.034, 1.037, 1.039, 1.041, 1.044, 1.045, 1.047, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.045, 1.044, 1.042, 1.041, 1.039, 1.036, 1.031, 1.029, + 1.024, 1.029, 1.034, 1.037, 1.039, 1.042, 1.044, 1.046, 1.047, 1.048, 1.049, 1.049, 1.051, 1.051, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.039, 1.037, 1.031, 1.029, + 1.025, 1.031, 1.035, 1.037, 1.039, 1.042, 1.045, 1.046, 1.047, 1.048, 1.049, 1.049, 1.051, 1.052, 1.052, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.048, 1.047, 1.047, 1.045, 1.043, 1.042, 1.041, 1.037, 1.032, 1.031, + 1.026, 1.031, 1.035, 1.037, 1.039, 1.043, 1.045, 1.046, 1.047, 1.048, 1.049, 1.051, 1.051, 1.052, 1.052, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.048, 1.047, 1.047, 1.045, 1.044, 1.042, 1.041, 1.037, 1.032, 1.031, + 1.026, 1.031, 1.035, 1.038, 1.039, 1.043, 1.044, 1.046, 1.047, 1.048, 1.049, 1.051, 1.051, 1.052, 1.052, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.048, 1.048, 1.047, 1.045, 1.044, 1.043, 1.041, 1.037, 1.032, 1.031, + 1.025, 1.031, 1.035, 1.038, 1.039, 1.042, 1.044, 1.046, 1.047, 1.048, 1.049, 1.051, 1.051, 1.051, 1.052, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.045, 1.044, 1.043, 1.041, 1.037, 1.032, 1.031, + 1.024, 1.031, 1.035, 1.038, 1.039, 1.042, 1.044, 1.046, 1.047, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.052, 1.052, 1.052, 1.052, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.044, 1.043, 1.041, 1.037, 1.033, 1.031, + 1.025, 1.031, 1.035, 1.037, 1.039, 1.041, 1.044, 1.046, 1.047, 1.049, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.037, 1.033, 1.031, + 1.024, 1.031, 1.034, 1.037, 1.038, 1.041, 1.044, 1.045, 1.047, 1.049, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.037, 1.034, 1.032, + 1.024, 1.031, 1.034, 1.037, 1.038, 1.041, 1.043, 1.045, 1.046, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.038, 1.034, 1.032, + 1.024, 1.029, 1.034, 1.037, 1.038, 1.041, 1.043, 1.044, 1.046, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.048, 1.048, 1.047, 1.046, 1.045, 1.043, 1.041, 1.038, 1.034, 1.031, + 1.024, 1.028, 1.034, 1.037, 1.038, 1.041, 1.042, 1.044, 1.046, 1.047, 1.048, 1.049, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.049, 1.048, 1.047, 1.047, 1.045, 1.044, 1.043, 1.039, 1.038, 1.033, 1.031, + 1.022, 1.027, 1.034, 1.035, 1.038, 1.039, 1.041, 1.043, 1.044, 1.046, 1.047, 1.048, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.047, 1.047, 1.046, 1.045, 1.044, 1.043, 1.039, 1.037, 1.032, 1.029, + 1.022, 1.026, 1.032, 1.034, 1.037, 1.039, 1.041, 1.042, 1.044, 1.045, 1.046, 1.047, 1.048, 1.048, 1.048, 1.049, 1.049, 1.049, 1.049, 1.049, 1.049, 1.047, 1.047, 1.046, 1.045, 1.045, 1.044, 1.041, 1.039, 1.036, 1.031, 1.029, + 1.021, 1.025, 1.029, 1.033, 1.036, 1.038, 1.039, 1.041, 1.042, 1.044, 1.045, 1.046, 1.047, 1.047, 1.047, 1.047, 1.048, 1.048, 1.048, 1.047, 1.047, 1.047, 1.045, 1.045, 1.045, 1.044, 1.042, 1.039, 1.038, 1.034, 1.029, 1.028, + 1.019, 1.023, 1.028, 1.032, 1.035, 1.038, 1.039, 1.041, 1.042, 1.044, 1.044, 1.045, 1.045, 1.046, 1.046, 1.046, 1.047, 1.047, 1.047, 1.046, 1.046, 1.045, 1.045, 1.045, 1.044, 1.042, 1.041, 1.038, 1.037, 1.033, 1.029, 1.027, + 1.017, 1.022, 1.027, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.043, 1.044, 1.045, 1.045, 1.045, 1.045, 1.046, 1.046, 1.046, 1.046, 1.046, 1.045, 1.045, 1.045, 1.044, 1.043, 1.041, 1.039, 1.038, 1.035, 1.031, 1.029, 1.027, + 1.016, 1.022, 1.027, 1.031, 1.034, 1.037, 1.039, 1.041, 1.041, 1.043, 1.043, 1.044, 1.045, 1.045, 1.045, 1.045, 1.046, 1.046, 1.046, 1.045, 1.045, 1.045, 1.045, 1.044, 1.043, 1.041, 1.038, 1.038, 1.035, 1.031, 1.029, 1.028 + ] + }, + { + "ct": 6000, + "table": + [ + 1.001, 1.003, 1.005, 1.008, 1.009, 1.011, 1.011, 1.011, 1.011, 1.012, 1.012, 1.012, 1.013, 1.013, 1.013, 1.013, 1.013, 1.013, 1.013, 1.012, 1.012, 1.013, 1.013, 1.013, 1.015, 1.015, 1.015, 1.014, 1.014, 1.014, 1.013, 1.012, + 1.007, 1.008, 1.011, 1.015, 1.017, 1.019, 1.019, 1.021, 1.021, 1.021, 1.021, 1.022, 1.023, 1.023, 1.023, 1.023, 1.023, 1.023, 1.022, 1.022, 1.022, 1.022, 1.023, 1.023, 1.023, 1.024, 1.024, 1.023, 1.023, 1.022, 1.022, 1.021, + 1.008, 1.011, 1.015, 1.017, 1.019, 1.021, 1.021, 1.021, 1.021, 1.022, 1.023, 1.023, 1.023, 1.023, 1.024, 1.024, 1.023, 1.023, 1.023, 1.023, 1.023, 1.023, 1.023, 1.023, 1.024, 1.024, 1.024, 1.024, 1.023, 1.023, 1.022, 1.022, + 1.009, 1.012, 1.016, 1.018, 1.019, 1.021, 1.021, 1.021, 1.022, 1.023, 1.023, 1.023, 1.024, 1.024, 1.025, 1.024, 1.024, 1.024, 1.024, 1.023, 1.023, 1.023, 1.024, 1.024, 1.024, 1.025, 1.025, 1.024, 1.024, 1.023, 1.022, 1.021, + 1.009, 1.013, 1.016, 1.018, 1.019, 1.021, 1.021, 1.022, 1.023, 1.023, 1.024, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.024, 1.024, 1.024, 1.024, 1.025, 1.025, 1.025, 1.025, 1.025, 1.024, 1.023, 1.022, 1.021, + 1.011, 1.014, 1.017, 1.019, 1.019, 1.021, 1.022, 1.023, 1.023, 1.024, 1.025, 1.025, 1.025, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.025, 1.025, 1.025, 1.025, 1.025, 1.025, 1.026, 1.026, 1.026, 1.024, 1.022, 1.022, 1.022, + 1.011, 1.014, 1.018, 1.019, 1.021, 1.022, 1.022, 1.023, 1.024, 1.025, 1.026, 1.026, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.027, 1.026, 1.026, 1.026, 1.025, 1.026, 1.026, 1.026, 1.026, 1.026, 1.025, 1.022, 1.022, 1.022, + 1.012, 1.015, 1.018, 1.019, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.026, 1.027, 1.027, 1.027, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.026, 1.025, 1.023, 1.022, 1.021, + 1.012, 1.015, 1.018, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.026, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.026, 1.025, 1.023, 1.022, 1.021, + 1.013, 1.016, 1.019, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.027, 1.027, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.026, 1.025, 1.024, 1.021, 1.021, + 1.014, 1.017, 1.019, 1.021, 1.022, 1.023, 1.025, 1.026, 1.026, 1.027, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.027, 1.027, 1.026, 1.025, 1.024, 1.021, 1.021, + 1.014, 1.017, 1.021, 1.021, 1.022, 1.024, 1.025, 1.026, 1.026, 1.027, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.024, 1.021, 1.021, + 1.014, 1.017, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.024, 1.021, 1.021, + 1.015, 1.017, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.024, 1.022, 1.021, + 1.015, 1.018, 1.021, 1.022, 1.023, 1.024, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.025, 1.022, 1.021, + 1.016, 1.019, 1.022, 1.022, 1.023, 1.024, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.027, 1.027, 1.026, 1.023, 1.022, + 1.016, 1.019, 1.022, 1.023, 1.023, 1.025, 1.026, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.027, 1.026, 1.023, 1.022, + 1.016, 1.019, 1.022, 1.023, 1.023, 1.025, 1.026, 1.027, 1.027, 1.028, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.027, 1.026, 1.023, 1.022, + 1.016, 1.019, 1.022, 1.023, 1.023, 1.025, 1.025, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.028, 1.028, 1.026, 1.024, 1.023, + 1.016, 1.019, 1.022, 1.023, 1.023, 1.024, 1.025, 1.026, 1.027, 1.028, 1.028, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.026, 1.024, 1.023, + 1.015, 1.019, 1.022, 1.023, 1.023, 1.024, 1.025, 1.026, 1.027, 1.028, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.026, 1.025, 1.024, + 1.016, 1.019, 1.022, 1.023, 1.023, 1.024, 1.025, 1.026, 1.026, 1.028, 1.028, 1.029, 1.029, 1.029, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.026, 1.025, 1.025, + 1.016, 1.019, 1.021, 1.022, 1.023, 1.024, 1.025, 1.026, 1.026, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.031, 1.031, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.026, 1.025, + 1.016, 1.019, 1.021, 1.023, 1.023, 1.024, 1.025, 1.025, 1.027, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.027, 1.027, 1.026, + 1.016, 1.019, 1.021, 1.023, 1.023, 1.024, 1.024, 1.025, 1.026, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.026, + 1.016, 1.018, 1.022, 1.023, 1.023, 1.024, 1.024, 1.025, 1.026, 1.027, 1.027, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.026, + 1.016, 1.018, 1.021, 1.022, 1.023, 1.023, 1.024, 1.024, 1.025, 1.026, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.026, 1.026, + 1.016, 1.017, 1.021, 1.022, 1.022, 1.023, 1.024, 1.024, 1.024, 1.026, 1.026, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.029, 1.028, 1.028, 1.027, 1.026, 1.026, + 1.015, 1.016, 1.019, 1.021, 1.022, 1.023, 1.024, 1.024, 1.024, 1.025, 1.026, 1.026, 1.026, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.026, 1.026, + 1.014, 1.016, 1.019, 1.021, 1.022, 1.023, 1.023, 1.024, 1.024, 1.025, 1.025, 1.026, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.027, 1.027, 1.026, + 1.015, 1.016, 1.019, 1.021, 1.021, 1.023, 1.024, 1.024, 1.025, 1.025, 1.026, 1.026, 1.025, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.027, 1.028, 1.027, 1.027, 1.027, + 1.015, 1.017, 1.019, 1.019, 1.021, 1.023, 1.024, 1.025, 1.025, 1.025, 1.026, 1.026, 1.025, 1.026, 1.026, 1.026, 1.026, 1.027, 1.027, 1.027, 1.028, 1.028, 1.028, 1.028, 1.028, 1.027, 1.027, 1.028, 1.028, 1.028, 1.027, 1.029 + ] + } + ], + "luminance_lut": + [ + 1.319, 1.289, 1.238, 1.198, 1.169, 1.139, 1.116, 1.094, 1.082, 1.074, 1.068, 1.064, 1.062, 1.061, 1.061, 1.061, 1.061, 1.062, 1.064, 1.065, 1.068, 1.074, 1.084, 1.101, 1.123, 1.148, 1.181, 1.221, 1.277, 1.356, 1.468, 1.477, + 1.292, 1.255, 1.208, 1.171, 1.145, 1.121, 1.099, 1.086, 1.073, 1.064, 1.058, 1.053, 1.049, 1.047, 1.046, 1.045, 1.048, 1.049, 1.051, 1.053, 1.059, 1.065, 1.074, 1.089, 1.105, 1.125, 1.152, 1.189, 1.238, 1.309, 1.408, 1.468, + 1.269, 1.234, 1.189, 1.155, 1.136, 1.112, 1.086, 1.073, 1.064, 1.057, 1.052, 1.047, 1.043, 1.041, 1.041, 1.041, 1.043, 1.047, 1.048, 1.049, 1.053, 1.059, 1.065, 1.074, 1.089, 1.109, 1.136, 1.171, 1.216, 1.278, 1.369, 1.408, + 1.249, 1.216, 1.173, 1.142, 1.125, 1.105, 1.078, 1.066, 1.058, 1.053, 1.047, 1.043, 1.038, 1.036, 1.035, 1.035, 1.039, 1.042, 1.043, 1.044, 1.048, 1.053, 1.059, 1.066, 1.079, 1.098, 1.122, 1.155, 1.197, 1.252, 1.337, 1.369, + 1.233, 1.199, 1.159, 1.129, 1.111, 1.095, 1.073, 1.061, 1.054, 1.049, 1.044, 1.038, 1.033, 1.031, 1.029, 1.029, 1.033, 1.037, 1.037, 1.037, 1.042, 1.047, 1.052, 1.059, 1.071, 1.087, 1.111, 1.142, 1.181, 1.232, 1.307, 1.337, + 1.217, 1.185, 1.146, 1.116, 1.097, 1.083, 1.069, 1.055, 1.049, 1.045, 1.039, 1.033, 1.029, 1.027, 1.025, 1.024, 1.025, 1.028, 1.029, 1.031, 1.035, 1.041, 1.047, 1.053, 1.063, 1.079, 1.101, 1.131, 1.168, 1.215, 1.282, 1.308, + 1.206, 1.172, 1.134, 1.106, 1.088, 1.076, 1.064, 1.051, 1.045, 1.041, 1.034, 1.029, 1.026, 1.023, 1.021, 1.021, 1.021, 1.021, 1.024, 1.027, 1.031, 1.035, 1.041, 1.048, 1.058, 1.072, 1.092, 1.121, 1.157, 1.201, 1.262, 1.288, + 1.196, 1.161, 1.125, 1.099, 1.081, 1.071, 1.059, 1.047, 1.041, 1.035, 1.029, 1.025, 1.022, 1.019, 1.018, 1.017, 1.017, 1.018, 1.021, 1.023, 1.027, 1.031, 1.036, 1.044, 1.053, 1.066, 1.085, 1.113, 1.148, 1.191, 1.247, 1.273, + 1.188, 1.152, 1.118, 1.093, 1.076, 1.064, 1.052, 1.044, 1.037, 1.031, 1.026, 1.022, 1.019, 1.017, 1.015, 1.015, 1.014, 1.015, 1.017, 1.019, 1.023, 1.027, 1.033, 1.041, 1.049, 1.061, 1.078, 1.105, 1.139, 1.181, 1.235, 1.261, + 1.182, 1.145, 1.112, 1.088, 1.072, 1.059, 1.049, 1.041, 1.035, 1.028, 1.023, 1.019, 1.016, 1.014, 1.012, 1.012, 1.011, 1.012, 1.015, 1.017, 1.021, 1.024, 1.029, 1.037, 1.045, 1.056, 1.074, 1.099, 1.133, 1.173, 1.224, 1.251, + 1.178, 1.139, 1.106, 1.083, 1.068, 1.056, 1.046, 1.039, 1.032, 1.026, 1.021, 1.017, 1.014, 1.011, 1.009, 1.009, 1.009, 1.009, 1.012, 1.015, 1.018, 1.022, 1.028, 1.035, 1.042, 1.054, 1.069, 1.095, 1.127, 1.168, 1.217, 1.244, + 1.174, 1.134, 1.102, 1.081, 1.065, 1.053, 1.044, 1.037, 1.029, 1.024, 1.019, 1.015, 1.012, 1.009, 1.007, 1.006, 1.006, 1.007, 1.009, 1.012, 1.016, 1.019, 1.027, 1.034, 1.041, 1.051, 1.067, 1.092, 1.123, 1.161, 1.211, 1.239, + 1.173, 1.131, 1.099, 1.078, 1.062, 1.051, 1.042, 1.035, 1.028, 1.023, 1.018, 1.013, 1.009, 1.007, 1.005, 1.004, 1.004, 1.005, 1.007, 1.011, 1.014, 1.018, 1.025, 1.032, 1.039, 1.049, 1.064, 1.089, 1.119, 1.158, 1.207, 1.236, + 1.171, 1.129, 1.097, 1.075, 1.061, 1.049, 1.041, 1.033, 1.027, 1.021, 1.017, 1.012, 1.008, 1.005, 1.004, 1.003, 1.003, 1.003, 1.005, 1.009, 1.013, 1.017, 1.025, 1.031, 1.037, 1.047, 1.062, 1.087, 1.117, 1.155, 1.205, 1.233, + 1.169, 1.128, 1.097, 1.074, 1.059, 1.049, 1.041, 1.033, 1.026, 1.021, 1.015, 1.011, 1.007, 1.005, 1.004, 1.002, 1.002, 1.002, 1.004, 1.008, 1.012, 1.017, 1.024, 1.031, 1.036, 1.046, 1.061, 1.085, 1.116, 1.155, 1.203, 1.233, + 1.169, 1.128, 1.097, 1.074, 1.059, 1.048, 1.039, 1.031, 1.025, 1.021, 1.015, 1.011, 1.006, 1.004, 1.003, 1.001, 1.001, 1.002, 1.004, 1.008, 1.012, 1.017, 1.024, 1.031, 1.036, 1.046, 1.061, 1.084, 1.115, 1.155, 1.203, 1.233, + 1.169, 1.129, 1.098, 1.075, 1.059, 1.048, 1.039, 1.031, 1.025, 1.019, 1.015, 1.011, 1.006, 1.004, 1.002, 1.001, 1.001, 1.002, 1.004, 1.008, 1.012, 1.017, 1.025, 1.031, 1.036, 1.046, 1.061, 1.084, 1.116, 1.155, 1.204, 1.234, + 1.169, 1.131, 1.099, 1.076, 1.061, 1.049, 1.039, 1.032, 1.025, 1.021, 1.015, 1.011, 1.007, 1.005, 1.003, 1.001, 1.001, 1.002, 1.005, 1.009, 1.013, 1.017, 1.024, 1.031, 1.037, 1.047, 1.061, 1.085, 1.118, 1.158, 1.208, 1.237, + 1.171, 1.134, 1.102, 1.078, 1.062, 1.051, 1.041, 1.033, 1.026, 1.021, 1.016, 1.011, 1.009, 1.006, 1.004, 1.002, 1.002, 1.003, 1.006, 1.012, 1.015, 1.018, 1.023, 1.031, 1.038, 1.048, 1.064, 1.088, 1.121, 1.162, 1.212, 1.241, + 1.174, 1.139, 1.106, 1.081, 1.064, 1.052, 1.043, 1.034, 1.027, 1.022, 1.017, 1.013, 1.009, 1.008, 1.005, 1.004, 1.004, 1.005, 1.008, 1.013, 1.017, 1.019, 1.024, 1.031, 1.039, 1.051, 1.067, 1.092, 1.125, 1.167, 1.219, 1.246, + 1.179, 1.145, 1.112, 1.085, 1.067, 1.054, 1.045, 1.036, 1.029, 1.023, 1.018, 1.014, 1.011, 1.008, 1.007, 1.006, 1.007, 1.008, 1.009, 1.014, 1.018, 1.019, 1.025, 1.032, 1.041, 1.054, 1.071, 1.096, 1.131, 1.174, 1.228, 1.255, + 1.186, 1.152, 1.117, 1.089, 1.069, 1.057, 1.046, 1.038, 1.031, 1.025, 1.019, 1.016, 1.013, 1.009, 1.009, 1.008, 1.009, 1.009, 1.011, 1.014, 1.018, 1.021, 1.027, 1.034, 1.044, 1.056, 1.075, 1.102, 1.137, 1.182, 1.239, 1.268, + 1.194, 1.159, 1.123, 1.095, 1.075, 1.061, 1.049, 1.041, 1.033, 1.027, 1.022, 1.018, 1.015, 1.013, 1.011, 1.011, 1.011, 1.011, 1.013, 1.016, 1.019, 1.024, 1.031, 1.037, 1.047, 1.061, 1.082, 1.109, 1.147, 1.194, 1.256, 1.281, + 1.203, 1.169, 1.132, 1.102, 1.081, 1.065, 1.053, 1.044, 1.036, 1.029, 1.026, 1.021, 1.018, 1.015, 1.013, 1.013, 1.013, 1.014, 1.015, 1.018, 1.022, 1.028, 1.034, 1.041, 1.052, 1.067, 1.091, 1.121, 1.157, 1.207, 1.273, 1.299, + 1.214, 1.179, 1.142, 1.111, 1.088, 1.069, 1.057, 1.047, 1.039, 1.033, 1.028, 1.024, 1.021, 1.018, 1.016, 1.015, 1.015, 1.016, 1.018, 1.021, 1.026, 1.031, 1.038, 1.046, 1.058, 1.075, 1.101, 1.133, 1.171, 1.222, 1.293, 1.319, + 1.227, 1.194, 1.153, 1.121, 1.096, 1.076, 1.062, 1.052, 1.043, 1.036, 1.031, 1.027, 1.023, 1.021, 1.019, 1.019, 1.018, 1.019, 1.021, 1.025, 1.029, 1.035, 1.042, 1.051, 1.065, 1.085, 1.112, 1.145, 1.185, 1.239, 1.318, 1.346, + 1.244, 1.211, 1.167, 1.133, 1.107, 1.088, 1.069, 1.057, 1.047, 1.041, 1.034, 1.031, 1.026, 1.024, 1.022, 1.022, 1.022, 1.023, 1.025, 1.029, 1.034, 1.039, 1.047, 1.059, 1.075, 1.096, 1.124, 1.158, 1.202, 1.261, 1.346, 1.381, + 1.264, 1.229, 1.183, 1.147, 1.119, 1.098, 1.077, 1.063, 1.053, 1.045, 1.039, 1.034, 1.031, 1.027, 1.026, 1.026, 1.026, 1.027, 1.029, 1.034, 1.039, 1.045, 1.054, 1.068, 1.086, 1.109, 1.138, 1.175, 1.222, 1.288, 1.381, 1.423, + 1.287, 1.251, 1.201, 1.163, 1.134, 1.109, 1.086, 1.071, 1.059, 1.051, 1.044, 1.039, 1.034, 1.032, 1.031, 1.031, 1.031, 1.032, 1.036, 1.039, 1.045, 1.053, 1.064, 1.079, 1.101, 1.125, 1.156, 1.195, 1.246, 1.321, 1.423, 1.479, + 1.314, 1.275, 1.222, 1.182, 1.151, 1.123, 1.098, 1.082, 1.068, 1.058, 1.051, 1.045, 1.041, 1.037, 1.036, 1.036, 1.037, 1.039, 1.041, 1.047, 1.053, 1.063, 1.076, 1.093, 1.115, 1.141, 1.174, 1.216, 1.275, 1.361, 1.479, 1.556, + 1.349, 1.307, 1.246, 1.203, 1.169, 1.138, 1.114, 1.095, 1.079, 1.067, 1.058, 1.052, 1.048, 1.045, 1.043, 1.043, 1.044, 1.046, 1.049, 1.055, 1.063, 1.075, 1.091, 1.111, 1.134, 1.161, 1.196, 1.242, 1.311, 1.407, 1.556, 1.617, + 1.389, 1.333, 1.266, 1.219, 1.184, 1.152, 1.127, 1.106, 1.089, 1.076, 1.066, 1.058, 1.053, 1.049, 1.049, 1.049, 1.049, 1.051, 1.055, 1.062, 1.072, 1.085, 1.102, 1.127, 1.151, 1.177, 1.213, 1.264, 1.339, 1.445, 1.617, 1.667 + ], + "sigma": 0.00092, + "sigma_Cb": 0.00149 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.82604, -0.41219, -0.41384, + -0.51919, 1.83221, -0.31302, + 0.23201, -1.42044, 2.18842 + ] + }, + { + "ct": 2820, + "ccm": + [ + 1.80891, -0.47916, -0.32974, + -0.47311, 1.83395, -0.36084, + 0.21814, -1.22973, 2.01158 + ] + }, + { + "ct": 2830, + "ccm": + [ + 1.80397, -0.51779, -0.28617, + -0.64069, 2.16622, -0.52553, + 0.12013, -0.95702, 1.83689 + ] + }, + { + "ct": 2885, + "ccm": + [ + 1.78861, -0.50175, -0.28685, + -0.63703, 2.14176, -0.50473, + 0.08715, -0.86455, 1.77741 + ] + }, + { + "ct": 3601, + "ccm": + [ + 1.85135, -0.56992, -0.28143, + -0.56285, 2.08651, -0.52366, + 0.03737, -0.70813, 1.67076 + ] + }, + { + "ct": 3615, + "ccm": + [ + 1.87447, -0.60511, -0.26936, + -0.55592, 2.07733, -0.52141, + 0.04105, -0.70347, 1.66242 + ] + }, + { + "ct": 3622, + "ccm": + [ + 1.85269, -0.58224, -0.27044, + -0.55133, 2.06403, -0.51271, + 0.03952, -0.69055, 1.65103 + ] + }, + { + "ct": 4345, + "ccm": + [ + 1.81525, -0.56996, -0.24529, + -0.49203, 2.16996, -0.67793, + 0.02513, -0.67456, 1.64943 + ] + }, + { + "ct": 4410, + "ccm": + [ + 1.83312, -0.59611, -0.23701, + -0.48332, 2.15123, -0.66791, + 0.02629, -0.67203, 1.64574 + ] + }, + { + "ct": 4486, + "ccm": + [ + 1.84759, -0.60181, -0.24578, + -0.47792, 2.13471, -0.65679, + 0.02056, -0.65959, 1.63903 + ] + }, + { + "ct": 4576, + "ccm": + [ + 1.83733, -0.58859, -0.24874, + -0.47443, 2.13699, -0.66255, + 0.01842, -0.65402, 1.63561 + ] + }, + { + "ct": 5657, + "ccm": + [ + 1.84437, -0.57638, -0.26799, + -0.44569, 2.04163, -0.59593, + -0.01759, -0.52787, 1.54546 + ] + }, + { + "ct": 5672, + "ccm": + [ + 1.83986, -0.57025, -0.26962, + -0.44974, 2.04763, -0.59789, + -0.03246, -0.51626, 1.54872 + ] + }, + { + "ct": 5710, + "ccm": + [ + 1.83822, -0.57688, -0.26134, + -0.44263, 2.03779, -0.59516, + -0.02552, -0.52605, 1.55157 + ] + }, + { + "ct": 6850, + "ccm": + [ + 1.80507, -0.22489, -0.58017, + -0.48609, 2.48778, -1.00168, + -0.10995, -0.63701, 1.74696 + ] + } + ] + } + }, + { + "rpi.cac": { } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "night": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +} diff --git a/src/ipa/rpi/pisp/data/imx290.json b/src/ipa/rpi/pisp/data/imx290.json new file mode 100644 index 00000000..37421e85 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx290.json @@ -0,0 +1,341 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 6813, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 890, + "reference_Y": 12900 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.67 + } + }, + { + "rpi.geq": + { + "offset": 187, + "slope": 0.00842 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "speed": 0.2, + "metering_modes": + { + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 10, 30000, 60000 ], + "gain": [ 1.0, 2.0, 8.0 ] + }, + "short": + { + "shutter": [ 10, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.16 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.7, + "luminance_lut": + [ + 2.844, 2.604, 2.365, 2.2, 2.039, 1.916, 1.799, 1.707, 1.622, 1.552, 1.487, 1.435, 1.389, 1.356, 1.332, 1.317, 1.31, 1.308, 1.313, 1.324, 1.344, 1.37, 1.41, 1.454, 1.508, 1.567, 1.641, 1.719, 1.82, 1.925, 2.073, 2.221, + 2.749, 2.521, 2.294, 2.134, 1.979, 1.861, 1.749, 1.661, 1.578, 1.511, 1.448, 1.398, 1.354, 1.322, 1.3, 1.285, 1.278, 1.277, 1.281, 1.292, 1.311, 1.336, 1.374, 1.416, 1.469, 1.526, 1.596, 1.671, 1.77, 1.872, 2.019, 2.166, + 2.654, 2.438, 2.223, 2.069, 1.919, 1.807, 1.7, 1.614, 1.534, 1.469, 1.409, 1.361, 1.318, 1.288, 1.267, 1.254, 1.247, 1.245, 1.25, 1.259, 1.277, 1.302, 1.338, 1.379, 1.43, 1.485, 1.552, 1.623, 1.719, 1.819, 1.965, 2.112, + 2.563, 2.359, 2.155, 2.007, 1.863, 1.755, 1.653, 1.571, 1.493, 1.43, 1.372, 1.325, 1.284, 1.256, 1.236, 1.223, 1.217, 1.216, 1.219, 1.229, 1.246, 1.269, 1.305, 1.344, 1.393, 1.446, 1.51, 1.578, 1.672, 1.77, 1.914, 2.059, + 2.494, 2.299, 2.103, 1.961, 1.822, 1.718, 1.619, 1.538, 1.461, 1.399, 1.343, 1.298, 1.259, 1.232, 1.213, 1.2, 1.194, 1.193, 1.196, 1.205, 1.222, 1.245, 1.279, 1.318, 1.365, 1.416, 1.481, 1.549, 1.641, 1.735, 1.875, 2.015, + 2.425, 2.238, 2.05, 1.914, 1.782, 1.681, 1.585, 1.505, 1.429, 1.369, 1.314, 1.271, 1.234, 1.208, 1.189, 1.177, 1.171, 1.169, 1.173, 1.182, 1.198, 1.221, 1.254, 1.292, 1.338, 1.387, 1.452, 1.519, 1.609, 1.701, 1.836, 1.971, + 2.363, 2.183, 2.003, 1.873, 1.746, 1.648, 1.555, 1.477, 1.401, 1.342, 1.289, 1.247, 1.212, 1.187, 1.168, 1.156, 1.149, 1.148, 1.152, 1.16, 1.177, 1.198, 1.231, 1.267, 1.312, 1.36, 1.425, 1.492, 1.58, 1.671, 1.802, 1.932, + 2.314, 2.14, 1.965, 1.839, 1.716, 1.622, 1.532, 1.454, 1.38, 1.322, 1.27, 1.229, 1.195, 1.169, 1.149, 1.137, 1.129, 1.128, 1.132, 1.142, 1.158, 1.18, 1.21, 1.245, 1.289, 1.336, 1.401, 1.469, 1.557, 1.649, 1.776, 1.903, + 2.264, 2.096, 1.927, 1.805, 1.687, 1.596, 1.509, 1.432, 1.358, 1.301, 1.251, 1.211, 1.177, 1.151, 1.131, 1.117, 1.109, 1.108, 1.113, 1.123, 1.14, 1.161, 1.19, 1.222, 1.265, 1.313, 1.378, 1.445, 1.534, 1.626, 1.75, 1.874, + 2.225, 2.061, 1.897, 1.778, 1.663, 1.574, 1.489, 1.414, 1.341, 1.285, 1.235, 1.196, 1.163, 1.136, 1.115, 1.1, 1.091, 1.089, 1.095, 1.106, 1.124, 1.145, 1.174, 1.205, 1.248, 1.294, 1.359, 1.427, 1.516, 1.606, 1.728, 1.849, + 2.193, 2.033, 1.872, 1.756, 1.642, 1.556, 1.473, 1.399, 1.327, 1.272, 1.224, 1.185, 1.15, 1.123, 1.1, 1.084, 1.074, 1.072, 1.078, 1.09, 1.11, 1.133, 1.161, 1.193, 1.234, 1.28, 1.345, 1.413, 1.501, 1.59, 1.709, 1.828, + 2.161, 2.004, 1.848, 1.734, 1.622, 1.537, 1.457, 1.384, 1.313, 1.26, 1.212, 1.173, 1.138, 1.11, 1.085, 1.068, 1.057, 1.055, 1.062, 1.075, 1.096, 1.12, 1.148, 1.18, 1.221, 1.266, 1.331, 1.399, 1.486, 1.574, 1.69, 1.807, + 2.14, 1.986, 1.832, 1.719, 1.609, 1.525, 1.445, 1.373, 1.304, 1.251, 1.204, 1.165, 1.129, 1.1, 1.074, 1.055, 1.043, 1.041, 1.049, 1.063, 1.086, 1.11, 1.14, 1.172, 1.212, 1.258, 1.323, 1.39, 1.477, 1.566, 1.679, 1.792, + 2.123, 1.971, 1.819, 1.707, 1.598, 1.514, 1.434, 1.364, 1.296, 1.243, 1.197, 1.158, 1.122, 1.091, 1.064, 1.044, 1.031, 1.027, 1.036, 1.052, 1.076, 1.102, 1.132, 1.165, 1.206, 1.251, 1.316, 1.383, 1.471, 1.56, 1.67, 1.78, + 2.106, 1.956, 1.806, 1.695, 1.587, 1.504, 1.424, 1.354, 1.288, 1.236, 1.19, 1.15, 1.114, 1.083, 1.055, 1.033, 1.018, 1.014, 1.024, 1.04, 1.066, 1.094, 1.124, 1.158, 1.199, 1.245, 1.309, 1.376, 1.465, 1.555, 1.661, 1.767, + 2.104, 1.955, 1.805, 1.694, 1.586, 1.502, 1.422, 1.352, 1.285, 1.234, 1.188, 1.149, 1.113, 1.081, 1.053, 1.031, 1.014, 1.011, 1.021, 1.038, 1.064, 1.091, 1.122, 1.156, 1.198, 1.244, 1.308, 1.376, 1.465, 1.555, 1.66, 1.766, + 2.104, 1.955, 1.806, 1.695, 1.586, 1.502, 1.421, 1.351, 1.284, 1.232, 1.187, 1.148, 1.112, 1.08, 1.051, 1.029, 1.012, 1.008, 1.02, 1.036, 1.062, 1.089, 1.12, 1.155, 1.197, 1.244, 1.308, 1.375, 1.465, 1.555, 1.661, 1.766, + 2.105, 1.956, 1.807, 1.696, 1.587, 1.502, 1.42, 1.35, 1.282, 1.231, 1.186, 1.148, 1.112, 1.08, 1.051, 1.028, 1.011, 1.007, 1.019, 1.036, 1.061, 1.088, 1.119, 1.154, 1.197, 1.244, 1.308, 1.376, 1.466, 1.557, 1.662, 1.767, + 2.121, 1.97, 1.818, 1.705, 1.595, 1.508, 1.424, 1.353, 1.286, 1.236, 1.191, 1.153, 1.118, 1.087, 1.059, 1.038, 1.022, 1.018, 1.028, 1.044, 1.067, 1.093, 1.124, 1.158, 1.201, 1.248, 1.314, 1.383, 1.474, 1.567, 1.672, 1.777, + 2.137, 1.983, 1.829, 1.715, 1.603, 1.514, 1.428, 1.357, 1.291, 1.24, 1.196, 1.158, 1.123, 1.094, 1.068, 1.047, 1.033, 1.029, 1.038, 1.052, 1.074, 1.098, 1.128, 1.162, 1.205, 1.253, 1.32, 1.39, 1.483, 1.577, 1.682, 1.788, + 2.154, 1.998, 1.843, 1.726, 1.613, 1.522, 1.435, 1.364, 1.297, 1.246, 1.202, 1.164, 1.131, 1.102, 1.078, 1.059, 1.045, 1.041, 1.048, 1.061, 1.082, 1.105, 1.134, 1.167, 1.211, 1.259, 1.327, 1.399, 1.494, 1.588, 1.694, 1.8, + 2.176, 2.019, 1.862, 1.744, 1.628, 1.537, 1.449, 1.377, 1.309, 1.258, 1.213, 1.176, 1.143, 1.116, 1.092, 1.074, 1.061, 1.057, 1.063, 1.075, 1.094, 1.117, 1.146, 1.178, 1.222, 1.27, 1.34, 1.414, 1.509, 1.604, 1.711, 1.818, + 2.199, 2.04, 1.881, 1.761, 1.644, 1.552, 1.464, 1.391, 1.321, 1.269, 1.223, 1.187, 1.155, 1.129, 1.106, 1.09, 1.078, 1.074, 1.078, 1.088, 1.107, 1.128, 1.157, 1.189, 1.233, 1.281, 1.353, 1.428, 1.524, 1.62, 1.728, 1.836, + 2.228, 2.066, 1.904, 1.782, 1.662, 1.57, 1.482, 1.408, 1.337, 1.284, 1.237, 1.201, 1.17, 1.145, 1.123, 1.107, 1.096, 1.092, 1.095, 1.104, 1.121, 1.142, 1.17, 1.203, 1.247, 1.297, 1.37, 1.446, 1.542, 1.639, 1.75, 1.86, + 2.267, 2.099, 1.932, 1.807, 1.684, 1.592, 1.504, 1.428, 1.356, 1.302, 1.255, 1.219, 1.189, 1.164, 1.141, 1.125, 1.115, 1.111, 1.114, 1.123, 1.138, 1.158, 1.186, 1.22, 1.266, 1.318, 1.391, 1.467, 1.563, 1.661, 1.776, 1.891, + 2.305, 2.132, 1.96, 1.832, 1.707, 1.614, 1.526, 1.449, 1.375, 1.32, 1.272, 1.237, 1.208, 1.182, 1.16, 1.144, 1.135, 1.131, 1.134, 1.141, 1.155, 1.174, 1.203, 1.236, 1.285, 1.338, 1.412, 1.489, 1.585, 1.682, 1.802, 1.922, + 2.351, 2.173, 1.996, 1.864, 1.736, 1.641, 1.552, 1.474, 1.4, 1.344, 1.294, 1.258, 1.228, 1.203, 1.181, 1.165, 1.156, 1.152, 1.155, 1.162, 1.176, 1.195, 1.224, 1.259, 1.309, 1.365, 1.439, 1.516, 1.613, 1.711, 1.835, 1.96, + 2.4, 2.218, 2.036, 1.901, 1.768, 1.671, 1.58, 1.502, 1.428, 1.37, 1.319, 1.281, 1.249, 1.224, 1.203, 1.188, 1.178, 1.174, 1.177, 1.184, 1.197, 1.217, 1.248, 1.285, 1.337, 1.394, 1.469, 1.547, 1.644, 1.743, 1.873, 2.002, + 2.45, 2.264, 2.077, 1.938, 1.801, 1.702, 1.608, 1.53, 1.456, 1.397, 1.344, 1.304, 1.271, 1.245, 1.224, 1.21, 1.2, 1.196, 1.199, 1.206, 1.219, 1.239, 1.272, 1.311, 1.365, 1.424, 1.5, 1.578, 1.676, 1.776, 1.91, 2.044, + 2.513, 2.318, 2.124, 1.984, 1.848, 1.747, 1.652, 1.572, 1.496, 1.436, 1.383, 1.341, 1.303, 1.274, 1.253, 1.238, 1.228, 1.225, 1.228, 1.235, 1.248, 1.269, 1.303, 1.343, 1.4, 1.46, 1.537, 1.617, 1.718, 1.82, 1.962, 2.103, + 2.579, 2.376, 2.172, 2.032, 1.897, 1.796, 1.7, 1.617, 1.538, 1.479, 1.426, 1.38, 1.337, 1.306, 1.283, 1.267, 1.258, 1.254, 1.257, 1.265, 1.279, 1.3, 1.336, 1.377, 1.435, 1.497, 1.576, 1.658, 1.761, 1.867, 2.016, 2.165, + 2.645, 2.433, 2.22, 2.08, 1.946, 1.844, 1.747, 1.663, 1.581, 1.521, 1.468, 1.419, 1.371, 1.337, 1.313, 1.296, 1.287, 1.284, 1.287, 1.295, 1.309, 1.331, 1.368, 1.411, 1.471, 1.535, 1.615, 1.699, 1.805, 1.914, 2.071, 2.227 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.sharpen": { } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 3900, + "ccm": + [ + 1.54659, -0.17707, -0.36953, + -0.51471, 1.72733, -0.21262, + 0.06667, -0.92279, 1.85612 + ] + } + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx296.json b/src/ipa/rpi/pisp/data/imx296.json new file mode 100644 index 00000000..d9dde898 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx296.json @@ -0,0 +1,1194 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 4724, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 860, + "reference_Y": 14551 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.751 + } + }, + { + "rpi.geq": + { + "offset": 226, + "slope": 0.01032 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2875.0, 0.4699, 0.3209, + 3610.0, 0.4089, 0.4265, + 4640.0, 0.3281, 0.5417, + 5912.0, 0.2992, 0.5771, + 7630.0, 0.2285, 0.6524 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01783, + "transverse_neg": 0.02154 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.084, 2.084, 2.085, 2.085, 2.085, 2.087, 2.088, 2.087, 2.086, 2.082, 2.082, 2.084, 2.086, 2.088, 2.088, 2.088, 2.087, 2.088, 2.088, 2.091, 2.092, 2.093, 2.093, 2.093, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.091, 2.088, + 2.086, 2.086, 2.087, 2.088, 2.089, 2.089, 2.091, 2.089, 2.087, 2.086, 2.087, 2.088, 2.091, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.093, 2.093, 2.094, 2.095, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.093, + 2.087, 2.087, 2.088, 2.091, 2.091, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.096, + 2.089, 2.088, 2.089, 2.091, 2.091, 2.092, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.092, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.095, 2.096, 2.096, 2.097, 2.099, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.099, 2.098, 2.097, + 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.094, 2.095, 2.096, 2.097, 2.098, 2.098, 2.098, 2.101, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.089, 2.091, 2.092, 2.092, 2.092, 2.092, 2.094, 2.096, 2.096, 2.097, 2.098, 2.099, 2.099, 2.099, 2.099, 2.099, 2.097, + 2.093, 2.094, 2.094, 2.094, 2.095, 2.093, 2.092, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.096, 2.096, 2.097, 2.098, 2.098, 2.101, 2.101, 2.099, 2.099, 2.099, + 2.094, 2.094, 2.094, 2.095, 2.095, 2.095, 2.091, 2.089, 2.091, 2.089, 2.089, 2.089, 2.091, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.093, 2.095, 2.096, 2.097, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.099, + 2.095, 2.094, 2.094, 2.095, 2.096, 2.095, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.093, 2.094, 2.096, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.101, 2.099, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.095, 2.092, 2.089, 2.089, 2.088, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.093, 2.093, 2.095, 2.096, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.101, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.092, 2.092, 2.094, 2.094, 2.094, 2.096, 2.096, 2.098, 2.099, 2.102, 2.103, 2.103, 2.102, 2.102, + 2.095, 2.095, 2.095, 2.096, 2.096, 2.094, 2.093, 2.091, 2.091, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.093, 2.094, 2.094, 2.095, 2.096, 2.097, 2.098, 2.099, 2.103, 2.103, 2.103, 2.101, 2.101, + 2.095, 2.096, 2.096, 2.097, 2.096, 2.095, 2.093, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.096, 2.097, 2.099, 2.101, 2.103, 2.103, 2.103, 2.101, 2.099, + 2.096, 2.096, 2.097, 2.096, 2.097, 2.096, 2.094, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.099, 2.101, 2.103, 2.103, 2.102, 2.101, 2.101, + 2.096, 2.096, 2.097, 2.097, 2.097, 2.096, 2.094, 2.093, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.097, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, + 2.097, 2.096, 2.097, 2.097, 2.097, 2.097, 2.095, 2.093, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.092, 2.092, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.101, + 2.098, 2.097, 2.096, 2.097, 2.097, 2.097, 2.095, 2.094, 2.094, 2.094, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.095, 2.095, 2.094, 2.093, 2.095, 2.096, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.102, + 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.095, 2.094, 2.095, 2.093, 2.093, 2.092, 2.092, 2.092, 2.094, 2.094, 2.096, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.102, 2.101, 2.102, + 2.098, 2.097, 2.097, 2.098, 2.097, 2.096, 2.095, 2.095, 2.095, 2.094, 2.093, 2.093, 2.094, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.095, 2.097, 2.097, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.104, 2.103, 2.102, 2.101, + 2.099, 2.098, 2.098, 2.098, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.094, 2.094, 2.094, 2.096, 2.097, 2.097, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.101, 2.101, 2.104, 2.105, 2.105, 2.103, 2.102, + 2.101, 2.099, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.098, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.103, 2.104, 2.105, 2.105, 2.104, 2.103, + 2.102, 2.102, 2.099, 2.098, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.104, 2.105, 2.106, 2.106, 2.106, 2.104, 2.104, 2.104, + 2.102, 2.101, 2.099, 2.099, 2.099, 2.101, 2.101, 2.101, 2.099, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.099, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.103, 2.105, 2.106, 2.106, 2.106, 2.106, 2.105, 2.104, 2.104, + 2.099, 2.099, 2.099, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.098, 2.097, 2.098, 2.098, 2.099, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.105, 2.105, 2.106, 2.106, 2.104, 2.104, 2.103, + 2.096, 2.097, 2.097, 2.097, 2.097, 2.099, 2.099, 2.099, 2.099, 2.097, 2.097, 2.098, 2.098, 2.099, 2.098, 2.097, 2.097, 2.099, 2.101, 2.101, 2.101, 2.101, 2.101, 2.103, 2.105, 2.105, 2.105, 2.104, 2.104, 2.103, 2.101, 2.101, + 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.098, 2.099, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.097, 2.097, 2.096, 2.098, 2.098, 2.099, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.104, 2.104, 2.103, 2.101, 2.099, 2.098, + 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.097, 2.098, 2.097, 2.097, 2.096, 2.096, 2.098, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.103, 2.104, 2.104, 2.102, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.096, 2.098, 2.098, 2.098, 2.098, 2.097, 2.098, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.099, 2.099, 2.099, 2.101, 2.102, 2.103, 2.104, 2.104, 2.104, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.097, 2.099, 2.099, 2.099, 2.099, 2.099, 2.099, 2.098, 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.098, 2.097, 2.099, 2.101, 2.099, 2.099, 2.099, 2.102, 2.102, 2.104, 2.105, 2.105, 2.102, 2.099, 2.098 + ] + }, + { + "ct": 5000, + "table": + [ + 3.431, 3.437, 3.439, 3.439, 3.436, 3.438, 3.441, 3.441, 3.441, 3.441, 3.442, 3.443, 3.443, 3.444, 3.446, 3.448, 3.451, 3.451, 3.452, 3.451, 3.449, 3.449, 3.452, 3.453, 3.454, 3.454, 3.453, 3.456, 3.456, 3.456, 3.451, 3.448, + 3.445, 3.446, 3.445, 3.449, 3.453, 3.451, 3.451, 3.446, 3.447, 3.446, 3.447, 3.451, 3.453, 3.455, 3.454, 3.453, 3.453, 3.454, 3.455, 3.456, 3.457, 3.459, 3.461, 3.462, 3.463, 3.463, 3.465, 3.466, 3.467, 3.465, 3.459, 3.457, + 3.449, 3.449, 3.449, 3.454, 3.455, 3.454, 3.453, 3.451, 3.451, 3.448, 3.451, 3.451, 3.455, 3.456, 3.457, 3.456, 3.456, 3.458, 3.457, 3.459, 3.459, 3.461, 3.464, 3.467, 3.467, 3.466, 3.468, 3.469, 3.471, 3.468, 3.465, 3.462, + 3.451, 3.448, 3.451, 3.453, 3.457, 3.455, 3.454, 3.449, 3.449, 3.448, 3.449, 3.449, 3.455, 3.455, 3.456, 3.455, 3.454, 3.455, 3.455, 3.457, 3.458, 3.458, 3.461, 3.464, 3.466, 3.468, 3.469, 3.469, 3.469, 3.468, 3.465, 3.463, + 3.449, 3.449, 3.451, 3.453, 3.456, 3.455, 3.452, 3.449, 3.448, 3.447, 3.446, 3.448, 3.451, 3.452, 3.454, 3.455, 3.455, 3.454, 3.457, 3.458, 3.458, 3.459, 3.461, 3.464, 3.464, 3.466, 3.467, 3.469, 3.469, 3.467, 3.463, 3.459, + 3.449, 3.451, 3.452, 3.454, 3.455, 3.454, 3.452, 3.449, 3.447, 3.447, 3.446, 3.449, 3.449, 3.451, 3.452, 3.452, 3.452, 3.452, 3.454, 3.455, 3.457, 3.459, 3.461, 3.464, 3.464, 3.466, 3.465, 3.468, 3.468, 3.469, 3.465, 3.462, + 3.451, 3.451, 3.452, 3.453, 3.453, 3.453, 3.451, 3.449, 3.449, 3.447, 3.446, 3.447, 3.448, 3.451, 3.451, 3.451, 3.453, 3.452, 3.452, 3.452, 3.457, 3.458, 3.461, 3.463, 3.464, 3.465, 3.464, 3.466, 3.468, 3.469, 3.466, 3.463, + 3.451, 3.451, 3.451, 3.454, 3.453, 3.453, 3.451, 3.448, 3.448, 3.444, 3.444, 3.444, 3.448, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.452, 3.454, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.467, 3.468, 3.469, 3.466, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.454, 3.453, 3.449, 3.448, 3.447, 3.447, 3.444, 3.446, 3.446, 3.446, 3.446, 3.447, 3.449, 3.449, 3.451, 3.452, 3.455, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.468, 3.469, 3.468, 3.465, 3.462, + 3.453, 3.452, 3.454, 3.456, 3.455, 3.453, 3.449, 3.447, 3.446, 3.446, 3.445, 3.448, 3.447, 3.446, 3.445, 3.446, 3.448, 3.448, 3.449, 3.453, 3.455, 3.457, 3.459, 3.461, 3.464, 3.466, 3.467, 3.468, 3.468, 3.467, 3.465, 3.463, + 3.453, 3.453, 3.454, 3.456, 3.456, 3.451, 3.448, 3.447, 3.447, 3.446, 3.445, 3.446, 3.446, 3.446, 3.446, 3.446, 3.448, 3.448, 3.449, 3.452, 3.454, 3.456, 3.459, 3.459, 3.461, 3.465, 3.466, 3.468, 3.468, 3.468, 3.467, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.456, 3.452, 3.448, 3.446, 3.446, 3.444, 3.446, 3.445, 3.446, 3.446, 3.447, 3.448, 3.449, 3.449, 3.449, 3.452, 3.453, 3.454, 3.458, 3.458, 3.461, 3.461, 3.464, 3.469, 3.469, 3.468, 3.466, 3.466, + 3.452, 3.452, 3.453, 3.454, 3.454, 3.453, 3.447, 3.446, 3.444, 3.444, 3.444, 3.444, 3.445, 3.446, 3.448, 3.451, 3.452, 3.453, 3.451, 3.453, 3.453, 3.455, 3.458, 3.459, 3.461, 3.462, 3.463, 3.468, 3.471, 3.469, 3.467, 3.467, + 3.454, 3.455, 3.457, 3.458, 3.458, 3.455, 3.449, 3.446, 3.445, 3.445, 3.445, 3.445, 3.447, 3.447, 3.448, 3.451, 3.452, 3.453, 3.452, 3.452, 3.452, 3.454, 3.457, 3.459, 3.459, 3.462, 3.464, 3.468, 3.469, 3.467, 3.465, 3.465, + 3.457, 3.455, 3.455, 3.459, 3.458, 3.454, 3.451, 3.448, 3.445, 3.445, 3.445, 3.446, 3.448, 3.449, 3.451, 3.452, 3.451, 3.453, 3.452, 3.452, 3.453, 3.457, 3.457, 3.461, 3.461, 3.463, 3.465, 3.468, 3.471, 3.468, 3.465, 3.463, + 3.458, 3.456, 3.456, 3.459, 3.457, 3.454, 3.452, 3.449, 3.447, 3.445, 3.446, 3.447, 3.447, 3.448, 3.449, 3.448, 3.449, 3.451, 3.451, 3.451, 3.451, 3.455, 3.456, 3.458, 3.462, 3.463, 3.464, 3.465, 3.467, 3.466, 3.464, 3.462, + 3.457, 3.456, 3.455, 3.457, 3.457, 3.454, 3.449, 3.447, 3.445, 3.445, 3.446, 3.446, 3.448, 3.446, 3.448, 3.449, 3.449, 3.451, 3.451, 3.451, 3.453, 3.455, 3.457, 3.459, 3.462, 3.464, 3.464, 3.465, 3.467, 3.464, 3.464, 3.463, + 3.458, 3.457, 3.455, 3.456, 3.456, 3.456, 3.453, 3.449, 3.447, 3.448, 3.447, 3.447, 3.447, 3.447, 3.447, 3.448, 3.449, 3.451, 3.451, 3.452, 3.453, 3.455, 3.458, 3.459, 3.459, 3.463, 3.464, 3.463, 3.464, 3.463, 3.464, 3.464, + 3.457, 3.456, 3.456, 3.456, 3.456, 3.456, 3.455, 3.449, 3.447, 3.448, 3.451, 3.449, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.451, 3.452, 3.453, 3.456, 3.458, 3.459, 3.461, 3.462, 3.464, 3.464, 3.465, 3.464, 3.464, 3.463, + 3.457, 3.456, 3.455, 3.455, 3.455, 3.455, 3.453, 3.451, 3.449, 3.448, 3.448, 3.449, 3.449, 3.449, 3.448, 3.449, 3.451, 3.452, 3.452, 3.453, 3.454, 3.457, 3.458, 3.459, 3.462, 3.464, 3.465, 3.464, 3.465, 3.464, 3.463, 3.463, + 3.456, 3.456, 3.454, 3.453, 3.454, 3.453, 3.452, 3.451, 3.449, 3.448, 3.448, 3.449, 3.451, 3.451, 3.448, 3.449, 3.451, 3.454, 3.454, 3.454, 3.455, 3.457, 3.458, 3.461, 3.461, 3.462, 3.464, 3.464, 3.466, 3.465, 3.464, 3.464, + 3.459, 3.457, 3.456, 3.455, 3.454, 3.453, 3.453, 3.452, 3.452, 3.451, 3.449, 3.449, 3.449, 3.448, 3.447, 3.449, 3.451, 3.454, 3.455, 3.455, 3.456, 3.458, 3.459, 3.461, 3.461, 3.462, 3.463, 3.466, 3.469, 3.465, 3.465, 3.464, + 3.463, 3.461, 3.458, 3.458, 3.457, 3.456, 3.456, 3.454, 3.454, 3.452, 3.452, 3.451, 3.451, 3.449, 3.448, 3.448, 3.452, 3.454, 3.456, 3.455, 3.457, 3.458, 3.461, 3.464, 3.462, 3.461, 3.463, 3.466, 3.469, 3.469, 3.467, 3.467, + 3.466, 3.462, 3.461, 3.461, 3.459, 3.457, 3.457, 3.457, 3.456, 3.454, 3.455, 3.455, 3.455, 3.451, 3.452, 3.453, 3.454, 3.455, 3.456, 3.456, 3.459, 3.462, 3.463, 3.466, 3.466, 3.467, 3.466, 3.469, 3.471, 3.469, 3.468, 3.466, + 3.467, 3.463, 3.463, 3.459, 3.461, 3.459, 3.461, 3.459, 3.458, 3.456, 3.457, 3.456, 3.457, 3.455, 3.456, 3.455, 3.456, 3.457, 3.459, 3.461, 3.461, 3.464, 3.465, 3.468, 3.469, 3.469, 3.469, 3.469, 3.471, 3.468, 3.467, 3.468, + 3.467, 3.464, 3.459, 3.459, 3.462, 3.462, 3.462, 3.461, 3.461, 3.462, 3.461, 3.459, 3.461, 3.459, 3.458, 3.457, 3.459, 3.461, 3.462, 3.463, 3.464, 3.466, 3.468, 3.469, 3.471, 3.469, 3.471, 3.472, 3.471, 3.467, 3.466, 3.464, + 3.464, 3.462, 3.458, 3.457, 3.458, 3.461, 3.461, 3.461, 3.461, 3.462, 3.462, 3.461, 3.461, 3.459, 3.459, 3.459, 3.461, 3.461, 3.464, 3.465, 3.465, 3.468, 3.468, 3.469, 3.471, 3.469, 3.469, 3.469, 3.469, 3.464, 3.462, 3.459, + 3.457, 3.458, 3.455, 3.456, 3.456, 3.457, 3.459, 3.459, 3.459, 3.459, 3.458, 3.456, 3.458, 3.457, 3.458, 3.458, 3.458, 3.459, 3.461, 3.463, 3.465, 3.466, 3.468, 3.469, 3.471, 3.468, 3.466, 3.466, 3.465, 3.461, 3.459, 3.457, + 3.456, 3.455, 3.454, 3.454, 3.455, 3.456, 3.458, 3.459, 3.459, 3.456, 3.456, 3.456, 3.455, 3.456, 3.455, 3.455, 3.455, 3.454, 3.457, 3.461, 3.462, 3.464, 3.465, 3.467, 3.467, 3.466, 3.464, 3.464, 3.463, 3.461, 3.457, 3.456, + 3.456, 3.454, 3.453, 3.454, 3.454, 3.455, 3.458, 3.459, 3.459, 3.456, 3.455, 3.455, 3.455, 3.451, 3.453, 3.454, 3.454, 3.455, 3.455, 3.458, 3.461, 3.462, 3.461, 3.463, 3.465, 3.464, 3.463, 3.463, 3.462, 3.459, 3.456, 3.451, + 3.455, 3.452, 3.452, 3.452, 3.455, 3.457, 3.459, 3.459, 3.459, 3.458, 3.456, 3.456, 3.455, 3.453, 3.453, 3.455, 3.457, 3.457, 3.457, 3.461, 3.461, 3.461, 3.459, 3.462, 3.464, 3.464, 3.464, 3.463, 3.463, 3.459, 3.454, 3.451, + 3.452, 3.452, 3.452, 3.453, 3.457, 3.458, 3.458, 3.459, 3.459, 3.458, 3.457, 3.457, 3.455, 3.455, 3.458, 3.459, 3.458, 3.459, 3.459, 3.461, 3.461, 3.461, 3.459, 3.461, 3.463, 3.464, 3.466, 3.463, 3.461, 3.458, 3.453, 3.449 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.403, 3.399, 3.395, 3.391, 3.392, 3.394, 3.401, 3.403, 3.404, 3.404, 3.403, 3.399, 3.398, 3.396, 3.395, 3.396, 3.399, 3.403, 3.404, 3.401, 3.399, 3.398, 3.397, 3.401, 3.401, 3.401, 3.396, 3.394, 3.397, 3.396, 3.388, 3.364, + 3.403, 3.399, 3.393, 3.389, 3.391, 3.395, 3.401, 3.404, 3.406, 3.404, 3.403, 3.399, 3.399, 3.397, 3.397, 3.397, 3.401, 3.404, 3.404, 3.402, 3.398, 3.396, 3.397, 3.401, 3.401, 3.401, 3.395, 3.394, 3.396, 3.393, 3.387, 3.364, + 3.399, 3.398, 3.391, 3.385, 3.386, 3.395, 3.402, 3.405, 3.405, 3.404, 3.402, 3.399, 3.399, 3.398, 3.398, 3.398, 3.401, 3.404, 3.405, 3.403, 3.399, 3.396, 3.396, 3.398, 3.401, 3.401, 3.398, 3.394, 3.392, 3.389, 3.386, 3.364, + 3.398, 3.393, 3.386, 3.382, 3.385, 3.392, 3.399, 3.403, 3.405, 3.404, 3.402, 3.398, 3.398, 3.397, 3.397, 3.398, 3.401, 3.404, 3.405, 3.403, 3.398, 3.394, 3.394, 3.398, 3.401, 3.401, 3.396, 3.392, 3.391, 3.388, 3.383, 3.362, + 3.396, 3.391, 3.384, 3.381, 3.384, 3.389, 3.398, 3.402, 3.402, 3.401, 3.399, 3.395, 3.395, 3.395, 3.397, 3.397, 3.401, 3.402, 3.404, 3.403, 3.399, 3.394, 3.393, 3.395, 3.399, 3.399, 3.397, 3.391, 3.388, 3.384, 3.381, 3.363, + 3.391, 3.386, 3.382, 3.381, 3.385, 3.389, 3.396, 3.398, 3.399, 3.399, 3.398, 3.395, 3.394, 3.394, 3.395, 3.397, 3.399, 3.401, 3.403, 3.401, 3.398, 3.394, 3.393, 3.393, 3.394, 3.396, 3.395, 3.392, 3.387, 3.382, 3.378, 3.361, + 3.389, 3.386, 3.379, 3.379, 3.383, 3.388, 3.394, 3.397, 3.397, 3.397, 3.395, 3.393, 3.393, 3.393, 3.395, 3.395, 3.397, 3.398, 3.401, 3.399, 3.397, 3.395, 3.394, 3.391, 3.393, 3.393, 3.393, 3.389, 3.387, 3.381, 3.374, 3.357, + 3.386, 3.383, 3.376, 3.375, 3.381, 3.386, 3.394, 3.396, 3.396, 3.394, 3.392, 3.392, 3.394, 3.394, 3.395, 3.394, 3.396, 3.398, 3.399, 3.397, 3.397, 3.394, 3.393, 3.391, 3.389, 3.391, 3.392, 3.388, 3.386, 3.379, 3.372, 3.355, + 3.386, 3.379, 3.373, 3.373, 3.378, 3.384, 3.391, 3.396, 3.395, 3.393, 3.389, 3.391, 3.391, 3.393, 3.394, 3.393, 3.394, 3.396, 3.397, 3.396, 3.393, 3.394, 3.393, 3.392, 3.389, 3.389, 3.389, 3.389, 3.386, 3.378, 3.371, 3.351, + 3.379, 3.375, 3.371, 3.371, 3.376, 3.381, 3.388, 3.393, 3.394, 3.391, 3.386, 3.386, 3.388, 3.393, 3.392, 3.392, 3.393, 3.395, 3.394, 3.392, 3.389, 3.391, 3.391, 3.392, 3.389, 3.388, 3.389, 3.389, 3.383, 3.377, 3.369, 3.351, + 3.373, 3.371, 3.367, 3.368, 3.373, 3.381, 3.387, 3.389, 3.391, 3.389, 3.385, 3.386, 3.383, 3.389, 3.389, 3.392, 3.392, 3.394, 3.393, 3.389, 3.387, 3.387, 3.388, 3.389, 3.389, 3.388, 3.386, 3.386, 3.382, 3.374, 3.367, 3.345, + 3.371, 3.369, 3.365, 3.366, 3.373, 3.379, 3.386, 3.389, 3.391, 3.389, 3.385, 3.384, 3.382, 3.386, 3.387, 3.389, 3.391, 3.392, 3.391, 3.387, 3.385, 3.385, 3.386, 3.388, 3.388, 3.388, 3.386, 3.385, 3.381, 3.373, 3.367, 3.345, + 3.367, 3.365, 3.365, 3.366, 3.374, 3.379, 3.384, 3.388, 3.389, 3.387, 3.384, 3.383, 3.383, 3.385, 3.385, 3.386, 3.388, 3.389, 3.388, 3.386, 3.383, 3.382, 3.384, 3.386, 3.387, 3.386, 3.381, 3.381, 3.379, 3.372, 3.364, 3.344, + 3.365, 3.363, 3.362, 3.367, 3.375, 3.379, 3.383, 3.384, 3.386, 3.384, 3.381, 3.379, 3.379, 3.383, 3.383, 3.384, 3.385, 3.387, 3.387, 3.385, 3.381, 3.381, 3.382, 3.384, 3.384, 3.385, 3.382, 3.379, 3.374, 3.369, 3.359, 3.343, + 3.359, 3.358, 3.361, 3.364, 3.373, 3.381, 3.384, 3.384, 3.385, 3.384, 3.381, 3.377, 3.379, 3.379, 3.382, 3.383, 3.384, 3.386, 3.386, 3.385, 3.381, 3.379, 3.381, 3.382, 3.382, 3.383, 3.379, 3.377, 3.371, 3.364, 3.357, 3.339, + 3.357, 3.356, 3.356, 3.362, 3.372, 3.379, 3.384, 3.384, 3.383, 3.381, 3.378, 3.376, 3.377, 3.379, 3.381, 3.382, 3.383, 3.385, 3.385, 3.383, 3.379, 3.379, 3.379, 3.381, 3.381, 3.382, 3.379, 3.372, 3.367, 3.362, 3.354, 3.334, + 3.357, 3.354, 3.357, 3.361, 3.372, 3.381, 3.385, 3.385, 3.384, 3.379, 3.376, 3.376, 3.376, 3.379, 3.381, 3.383, 3.383, 3.384, 3.383, 3.379, 3.378, 3.381, 3.379, 3.379, 3.379, 3.379, 3.378, 3.371, 3.363, 3.358, 3.354, 3.332, + 3.354, 3.351, 3.354, 3.359, 3.371, 3.379, 3.382, 3.384, 3.381, 3.378, 3.375, 3.374, 3.376, 3.378, 3.381, 3.383, 3.384, 3.382, 3.377, 3.377, 3.376, 3.377, 3.378, 3.378, 3.379, 3.379, 3.376, 3.367, 3.361, 3.357, 3.352, 3.333, + 3.352, 3.349, 3.351, 3.357, 3.372, 3.381, 3.383, 3.383, 3.381, 3.376, 3.372, 3.373, 3.375, 3.377, 3.382, 3.384, 3.384, 3.379, 3.376, 3.374, 3.374, 3.375, 3.375, 3.376, 3.377, 3.376, 3.373, 3.366, 3.361, 3.356, 3.347, 3.332, + 3.347, 3.346, 3.346, 3.355, 3.371, 3.377, 3.382, 3.381, 3.379, 3.372, 3.371, 3.371, 3.372, 3.375, 3.379, 3.383, 3.384, 3.379, 3.374, 3.373, 3.371, 3.373, 3.374, 3.375, 3.374, 3.374, 3.371, 3.365, 3.359, 3.352, 3.343, 3.331, + 3.345, 3.344, 3.345, 3.353, 3.367, 3.374, 3.382, 3.382, 3.376, 3.371, 3.369, 3.368, 3.369, 3.373, 3.377, 3.381, 3.379, 3.376, 3.373, 3.369, 3.368, 3.371, 3.372, 3.373, 3.371, 3.371, 3.369, 3.363, 3.357, 3.349, 3.341, 3.326, + 3.343, 3.341, 3.344, 3.351, 3.362, 3.371, 3.376, 3.376, 3.372, 3.369, 3.367, 3.366, 3.367, 3.369, 3.376, 3.378, 3.378, 3.375, 3.371, 3.367, 3.367, 3.368, 3.369, 3.369, 3.369, 3.368, 3.365, 3.361, 3.354, 3.347, 3.338, 3.321, + 3.341, 3.339, 3.342, 3.349, 3.359, 3.367, 3.371, 3.372, 3.371, 3.368, 3.366, 3.363, 3.365, 3.368, 3.371, 3.374, 3.376, 3.374, 3.368, 3.365, 3.365, 3.366, 3.368, 3.367, 3.367, 3.363, 3.361, 3.356, 3.352, 3.346, 3.336, 3.317, + 3.338, 3.336, 3.338, 3.346, 3.359, 3.364, 3.368, 3.369, 3.367, 3.366, 3.363, 3.362, 3.364, 3.364, 3.367, 3.371, 3.372, 3.369, 3.365, 3.362, 3.362, 3.365, 3.367, 3.367, 3.366, 3.362, 3.357, 3.353, 3.349, 3.342, 3.335, 3.317, + 3.334, 3.334, 3.336, 3.346, 3.354, 3.361, 3.365, 3.365, 3.365, 3.362, 3.361, 3.361, 3.362, 3.362, 3.364, 3.366, 3.368, 3.366, 3.361, 3.357, 3.357, 3.359, 3.363, 3.365, 3.363, 3.361, 3.355, 3.351, 3.346, 3.339, 3.336, 3.317, + 3.332, 3.332, 3.334, 3.344, 3.354, 3.359, 3.363, 3.365, 3.363, 3.361, 3.359, 3.359, 3.363, 3.363, 3.365, 3.365, 3.367, 3.366, 3.358, 3.356, 3.356, 3.358, 3.362, 3.364, 3.363, 3.359, 3.353, 3.348, 3.345, 3.339, 3.336, 3.315, + 3.332, 3.328, 3.331, 3.343, 3.351, 3.357, 3.358, 3.362, 3.361, 3.359, 3.357, 3.357, 3.361, 3.362, 3.364, 3.363, 3.363, 3.359, 3.356, 3.354, 3.354, 3.355, 3.358, 3.359, 3.361, 3.359, 3.351, 3.346, 3.344, 3.339, 3.336, 3.313, + 3.324, 3.324, 3.327, 3.334, 3.345, 3.351, 3.354, 3.356, 3.356, 3.354, 3.353, 3.354, 3.357, 3.358, 3.361, 3.358, 3.359, 3.355, 3.352, 3.348, 3.347, 3.351, 3.354, 3.358, 3.359, 3.355, 3.346, 3.343, 3.341, 3.336, 3.331, 3.312, + 3.318, 3.319, 3.321, 3.328, 3.337, 3.339, 3.345, 3.348, 3.346, 3.345, 3.347, 3.348, 3.351, 3.354, 3.356, 3.353, 3.354, 3.344, 3.343, 3.343, 3.343, 3.344, 3.347, 3.349, 3.353, 3.346, 3.341, 3.339, 3.331, 3.329, 3.325, 3.311, + 3.309, 3.313, 3.317, 3.325, 3.329, 3.332, 3.338, 3.339, 3.341, 3.339, 3.339, 3.342, 3.346, 3.346, 3.351, 3.351, 3.343, 3.338, 3.338, 3.339, 3.339, 3.339, 3.341, 3.341, 3.346, 3.343, 3.339, 3.332, 3.327, 3.326, 3.322, 3.309, + 3.305, 3.309, 3.317, 3.325, 3.328, 3.331, 3.334, 3.336, 3.337, 3.336, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.341, 3.336, 3.335, 3.337, 3.339, 3.341, 3.339, 3.339, 3.342, 3.341, 3.337, 3.329, 3.326, 3.325, 3.321, 3.314, + 3.302, 3.306, 3.319, 3.325, 3.329, 3.331, 3.334, 3.335, 3.337, 3.337, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.342, 3.336, 3.336, 3.338, 3.339, 3.341, 3.341, 3.341, 3.339, 3.338, 3.336, 3.331, 3.327, 3.324, 3.321, 3.314 + ] + }, + { + "ct": 5000, + "table": + [ + 1.726, 1.725, 1.723, 1.721, 1.723, 1.724, 1.724, 1.726, 1.727, 1.728, 1.729, 1.728, 1.725, 1.724, 1.726, 1.726, 1.727, 1.729, 1.727, 1.727, 1.724, 1.725, 1.724, 1.726, 1.725, 1.725, 1.724, 1.724, 1.722, 1.721, 1.719, 1.714, + 1.726, 1.724, 1.722, 1.721, 1.722, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.727, 1.725, 1.724, 1.724, 1.725, 1.726, 1.725, 1.724, 1.723, 1.722, 1.721, 1.719, 1.714, + 1.724, 1.722, 1.719, 1.719, 1.721, 1.723, 1.726, 1.726, 1.727, 1.727, 1.727, 1.725, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.725, 1.724, 1.724, 1.724, 1.726, 1.725, 1.724, 1.722, 1.722, 1.721, 1.719, 1.712, + 1.723, 1.721, 1.719, 1.719, 1.719, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.726, 1.728, 1.729, 1.728, 1.725, 1.723, 1.723, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.718, 1.711, + 1.722, 1.719, 1.719, 1.718, 1.719, 1.722, 1.725, 1.726, 1.726, 1.727, 1.727, 1.726, 1.725, 1.726, 1.726, 1.726, 1.727, 1.727, 1.728, 1.727, 1.726, 1.725, 1.724, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.715, 1.711, + 1.721, 1.717, 1.717, 1.716, 1.719, 1.722, 1.724, 1.726, 1.726, 1.727, 1.726, 1.726, 1.726, 1.726, 1.726, 1.727, 1.727, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.722, 1.721, 1.718, 1.715, 1.707, + 1.718, 1.717, 1.716, 1.716, 1.718, 1.721, 1.725, 1.726, 1.726, 1.726, 1.725, 1.725, 1.725, 1.725, 1.726, 1.727, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.718, 1.715, 1.709, + 1.718, 1.716, 1.716, 1.715, 1.717, 1.721, 1.724, 1.725, 1.726, 1.725, 1.725, 1.724, 1.724, 1.725, 1.726, 1.726, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.723, 1.723, 1.723, 1.722, 1.722, 1.719, 1.718, 1.714, 1.709, + 1.718, 1.716, 1.715, 1.715, 1.717, 1.721, 1.723, 1.725, 1.726, 1.725, 1.724, 1.723, 1.724, 1.725, 1.725, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.723, 1.722, 1.722, 1.721, 1.717, 1.714, 1.707, + 1.717, 1.716, 1.714, 1.714, 1.716, 1.721, 1.723, 1.725, 1.725, 1.725, 1.723, 1.723, 1.724, 1.726, 1.726, 1.726, 1.726, 1.725, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.706, + 1.715, 1.714, 1.714, 1.714, 1.716, 1.719, 1.722, 1.724, 1.725, 1.725, 1.723, 1.723, 1.724, 1.725, 1.725, 1.725, 1.726, 1.725, 1.725, 1.725, 1.724, 1.724, 1.724, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.713, 1.705, + 1.714, 1.714, 1.713, 1.714, 1.717, 1.719, 1.722, 1.724, 1.724, 1.724, 1.723, 1.722, 1.723, 1.724, 1.724, 1.724, 1.726, 1.725, 1.726, 1.725, 1.723, 1.723, 1.724, 1.724, 1.724, 1.723, 1.721, 1.719, 1.717, 1.715, 1.713, 1.706, + 1.712, 1.712, 1.712, 1.713, 1.718, 1.719, 1.721, 1.723, 1.724, 1.724, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.725, 1.725, 1.725, 1.725, 1.723, 1.722, 1.724, 1.723, 1.723, 1.722, 1.721, 1.719, 1.717, 1.714, 1.711, 1.706, + 1.712, 1.711, 1.711, 1.713, 1.717, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.724, 1.725, 1.725, 1.724, 1.723, 1.722, 1.722, 1.722, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.711, 1.706, + 1.711, 1.709, 1.711, 1.713, 1.716, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.721, 1.722, 1.724, 1.724, 1.724, 1.723, 1.724, 1.724, 1.724, 1.722, 1.722, 1.722, 1.722, 1.722, 1.721, 1.719, 1.718, 1.714, 1.712, 1.709, 1.702, + 1.709, 1.709, 1.709, 1.712, 1.717, 1.719, 1.721, 1.723, 1.723, 1.723, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.724, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.716, 1.713, 1.711, 1.709, 1.701, + 1.708, 1.707, 1.709, 1.712, 1.716, 1.719, 1.722, 1.723, 1.723, 1.723, 1.721, 1.721, 1.721, 1.722, 1.723, 1.723, 1.723, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.714, 1.712, 1.709, 1.708, 1.702, + 1.707, 1.707, 1.708, 1.711, 1.716, 1.721, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.722, 1.722, 1.723, 1.723, 1.723, 1.722, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.721, 1.717, 1.714, 1.711, 1.709, 1.707, 1.702, + 1.706, 1.706, 1.707, 1.711, 1.714, 1.719, 1.722, 1.722, 1.722, 1.721, 1.719, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.719, 1.719, 1.721, 1.721, 1.719, 1.719, 1.716, 1.713, 1.711, 1.709, 1.706, 1.701, + 1.705, 1.704, 1.706, 1.709, 1.713, 1.718, 1.721, 1.722, 1.721, 1.719, 1.718, 1.719, 1.721, 1.722, 1.723, 1.724, 1.724, 1.721, 1.721, 1.721, 1.719, 1.719, 1.719, 1.719, 1.719, 1.717, 1.715, 1.713, 1.711, 1.707, 1.704, 1.699, + 1.703, 1.703, 1.704, 1.709, 1.712, 1.717, 1.719, 1.721, 1.719, 1.718, 1.717, 1.718, 1.719, 1.721, 1.722, 1.723, 1.723, 1.722, 1.719, 1.719, 1.718, 1.719, 1.719, 1.718, 1.717, 1.716, 1.714, 1.712, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.703, 1.704, 1.708, 1.712, 1.715, 1.718, 1.719, 1.719, 1.717, 1.717, 1.717, 1.717, 1.718, 1.721, 1.722, 1.722, 1.721, 1.719, 1.718, 1.717, 1.718, 1.718, 1.717, 1.716, 1.714, 1.714, 1.711, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.702, 1.703, 1.706, 1.709, 1.715, 1.717, 1.718, 1.717, 1.717, 1.716, 1.716, 1.717, 1.717, 1.719, 1.721, 1.721, 1.721, 1.719, 1.717, 1.716, 1.717, 1.717, 1.716, 1.714, 1.713, 1.712, 1.711, 1.708, 1.706, 1.702, 1.696, + 1.701, 1.701, 1.702, 1.706, 1.709, 1.714, 1.716, 1.717, 1.716, 1.716, 1.716, 1.715, 1.716, 1.716, 1.717, 1.718, 1.719, 1.719, 1.716, 1.715, 1.715, 1.715, 1.715, 1.715, 1.714, 1.713, 1.711, 1.709, 1.708, 1.704, 1.701, 1.695, + 1.699, 1.699, 1.702, 1.706, 1.708, 1.712, 1.714, 1.715, 1.715, 1.715, 1.714, 1.715, 1.714, 1.715, 1.716, 1.716, 1.716, 1.716, 1.714, 1.713, 1.713, 1.714, 1.715, 1.714, 1.714, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.695, + 1.698, 1.699, 1.701, 1.705, 1.708, 1.711, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.715, 1.715, 1.716, 1.716, 1.715, 1.713, 1.713, 1.713, 1.714, 1.714, 1.714, 1.713, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.696, + 1.698, 1.699, 1.701, 1.705, 1.707, 1.711, 1.712, 1.713, 1.713, 1.713, 1.713, 1.714, 1.714, 1.715, 1.715, 1.716, 1.715, 1.714, 1.713, 1.712, 1.712, 1.712, 1.713, 1.713, 1.713, 1.711, 1.709, 1.707, 1.705, 1.703, 1.701, 1.696, + 1.698, 1.697, 1.699, 1.702, 1.705, 1.707, 1.711, 1.711, 1.711, 1.711, 1.711, 1.712, 1.712, 1.713, 1.714, 1.714, 1.713, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.708, 1.706, 1.704, 1.703, 1.699, 1.696, + 1.694, 1.695, 1.697, 1.699, 1.702, 1.705, 1.706, 1.707, 1.707, 1.708, 1.708, 1.708, 1.709, 1.711, 1.711, 1.711, 1.708, 1.708, 1.708, 1.707, 1.707, 1.707, 1.708, 1.708, 1.709, 1.708, 1.706, 1.703, 1.702, 1.701, 1.698, 1.696, + 1.692, 1.692, 1.695, 1.698, 1.699, 1.701, 1.704, 1.704, 1.704, 1.704, 1.705, 1.706, 1.707, 1.709, 1.709, 1.707, 1.706, 1.704, 1.704, 1.705, 1.705, 1.706, 1.706, 1.706, 1.706, 1.706, 1.703, 1.702, 1.701, 1.699, 1.696, 1.694, + 1.691, 1.692, 1.695, 1.697, 1.699, 1.699, 1.702, 1.703, 1.703, 1.702, 1.703, 1.704, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.704, 1.705, 1.705, 1.705, 1.705, 1.704, 1.703, 1.701, 1.699, 1.698, 1.696, 1.695, + 1.689, 1.691, 1.696, 1.698, 1.699, 1.699, 1.701, 1.702, 1.702, 1.702, 1.703, 1.703, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.703, 1.704, 1.704, 1.705, 1.704, 1.704, 1.702, 1.701, 1.698, 1.698, 1.696, 1.696 + ] + } + ], + "luminance_lut": + [ + 1.425, 1.393, 1.341, 1.295, 1.258, 1.226, 1.201, 1.181, 1.162, 1.146, 1.133, 1.123, 1.115, 1.111, 1.107, 1.106, 1.106, 1.107, 1.108, 1.111, 1.114, 1.122, 1.133, 1.148, 1.164, 1.184, 1.208, 1.236, 1.271, 1.309, 1.359, 1.381, + 1.397, 1.367, 1.317, 1.274, 1.237, 1.207, 1.183, 1.163, 1.146, 1.133, 1.123, 1.114, 1.107, 1.101, 1.098, 1.096, 1.096, 1.096, 1.097, 1.102, 1.106, 1.112, 1.122, 1.133, 1.148, 1.166, 1.187, 1.215, 1.249, 1.288, 1.335, 1.359, + 1.374, 1.341, 1.292, 1.251, 1.215, 1.186, 1.166, 1.146, 1.131, 1.117, 1.108, 1.099, 1.091, 1.088, 1.084, 1.082, 1.081, 1.082, 1.084, 1.088, 1.093, 1.098, 1.107, 1.118, 1.133, 1.149, 1.169, 1.195, 1.228, 1.267, 1.313, 1.335, + 1.352, 1.318, 1.271, 1.231, 1.196, 1.169, 1.149, 1.131, 1.115, 1.103, 1.093, 1.086, 1.079, 1.074, 1.071, 1.069, 1.069, 1.069, 1.071, 1.076, 1.079, 1.085, 1.094, 1.102, 1.117, 1.133, 1.152, 1.176, 1.208, 1.246, 1.289, 1.313, + 1.333, 1.298, 1.253, 1.212, 1.179, 1.153, 1.134, 1.116, 1.102, 1.089, 1.079, 1.072, 1.066, 1.062, 1.059, 1.058, 1.057, 1.057, 1.059, 1.064, 1.068, 1.072, 1.081, 1.091, 1.102, 1.119, 1.137, 1.161, 1.191, 1.227, 1.271, 1.293, + 1.317, 1.281, 1.235, 1.196, 1.165, 1.139, 1.119, 1.104, 1.089, 1.078, 1.068, 1.062, 1.055, 1.051, 1.048, 1.047, 1.047, 1.047, 1.048, 1.053, 1.056, 1.061, 1.069, 1.079, 1.091, 1.105, 1.126, 1.147, 1.177, 1.212, 1.253, 1.278, + 1.301, 1.265, 1.221, 1.181, 1.151, 1.127, 1.108, 1.091, 1.078, 1.068, 1.059, 1.051, 1.045, 1.041, 1.038, 1.037, 1.036, 1.037, 1.038, 1.042, 1.046, 1.051, 1.059, 1.069, 1.081, 1.096, 1.113, 1.135, 1.164, 1.198, 1.238, 1.264, + 1.286, 1.251, 1.207, 1.169, 1.141, 1.116, 1.098, 1.081, 1.068, 1.058, 1.049, 1.042, 1.037, 1.033, 1.031, 1.029, 1.028, 1.028, 1.029, 1.033, 1.037, 1.043, 1.051, 1.059, 1.071, 1.086, 1.104, 1.124, 1.152, 1.185, 1.225, 1.252, + 1.275, 1.239, 1.196, 1.161, 1.132, 1.107, 1.089, 1.073, 1.059, 1.049, 1.041, 1.035, 1.028, 1.024, 1.023, 1.021, 1.021, 1.021, 1.022, 1.024, 1.029, 1.036, 1.043, 1.051, 1.063, 1.078, 1.095, 1.115, 1.143, 1.175, 1.214, 1.243, + 1.267, 1.227, 1.187, 1.152, 1.122, 1.101, 1.081, 1.067, 1.054, 1.042, 1.035, 1.028, 1.023, 1.018, 1.015, 1.014, 1.014, 1.014, 1.016, 1.019, 1.024, 1.029, 1.036, 1.045, 1.056, 1.071, 1.088, 1.107, 1.134, 1.167, 1.204, 1.234, + 1.261, 1.219, 1.179, 1.145, 1.116, 1.095, 1.076, 1.061, 1.047, 1.037, 1.031, 1.023, 1.018, 1.014, 1.011, 1.009, 1.009, 1.009, 1.011, 1.013, 1.018, 1.024, 1.031, 1.039, 1.049, 1.065, 1.083, 1.102, 1.128, 1.161, 1.196, 1.228, + 1.256, 1.213, 1.173, 1.139, 1.111, 1.091, 1.071, 1.056, 1.043, 1.033, 1.026, 1.019, 1.014, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.009, 1.013, 1.018, 1.026, 1.035, 1.046, 1.061, 1.078, 1.097, 1.123, 1.154, 1.191, 1.222, + 1.251, 1.208, 1.169, 1.137, 1.108, 1.088, 1.069, 1.053, 1.039, 1.029, 1.023, 1.015, 1.011, 1.006, 1.004, 1.003, 1.001, 1.002, 1.003, 1.006, 1.009, 1.015, 1.022, 1.032, 1.044, 1.057, 1.076, 1.094, 1.119, 1.149, 1.186, 1.218, + 1.249, 1.205, 1.167, 1.133, 1.107, 1.085, 1.067, 1.052, 1.038, 1.029, 1.021, 1.013, 1.008, 1.004, 1.003, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.013, 1.021, 1.031, 1.042, 1.055, 1.073, 1.093, 1.116, 1.147, 1.182, 1.218, + 1.249, 1.204, 1.165, 1.132, 1.106, 1.085, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.007, 1.003, 1.002, 1.001, 1.001, 1.001, 1.001, 1.004, 1.007, 1.013, 1.021, 1.029, 1.042, 1.055, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.249, 1.204, 1.165, 1.132, 1.107, 1.086, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.008, 1.004, 1.002, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.014, 1.021, 1.029, 1.042, 1.056, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.251, 1.205, 1.166, 1.133, 1.108, 1.087, 1.068, 1.052, 1.039, 1.031, 1.021, 1.014, 1.009, 1.006, 1.003, 1.002, 1.001, 1.001, 1.003, 1.006, 1.009, 1.014, 1.022, 1.031, 1.043, 1.056, 1.073, 1.093, 1.116, 1.145, 1.182, 1.218, + 1.252, 1.208, 1.168, 1.137, 1.111, 1.089, 1.071, 1.055, 1.043, 1.033, 1.023, 1.016, 1.012, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.008, 1.012, 1.017, 1.024, 1.034, 1.045, 1.059, 1.075, 1.095, 1.119, 1.149, 1.185, 1.218, + 1.256, 1.213, 1.173, 1.142, 1.115, 1.093, 1.075, 1.059, 1.047, 1.036, 1.027, 1.021, 1.016, 1.012, 1.011, 1.009, 1.008, 1.008, 1.009, 1.012, 1.016, 1.021, 1.028, 1.038, 1.049, 1.064, 1.081, 1.099, 1.126, 1.155, 1.192, 1.223, + 1.261, 1.221, 1.179, 1.148, 1.121, 1.099, 1.081, 1.065, 1.052, 1.042, 1.032, 1.026, 1.021, 1.017, 1.015, 1.014, 1.014, 1.013, 1.013, 1.016, 1.021, 1.026, 1.033, 1.043, 1.054, 1.068, 1.085, 1.106, 1.132, 1.161, 1.199, 1.228, + 1.267, 1.228, 1.188, 1.155, 1.128, 1.105, 1.086, 1.071, 1.059, 1.047, 1.038, 1.031, 1.027, 1.022, 1.021, 1.019, 1.019, 1.019, 1.019, 1.022, 1.026, 1.032, 1.038, 1.049, 1.061, 1.075, 1.092, 1.112, 1.138, 1.169, 1.207, 1.236, + 1.278, 1.241, 1.199, 1.164, 1.137, 1.114, 1.094, 1.078, 1.066, 1.055, 1.046, 1.038, 1.032, 1.029, 1.027, 1.027, 1.027, 1.027, 1.027, 1.029, 1.032, 1.038, 1.047, 1.056, 1.067, 1.083, 1.099, 1.121, 1.146, 1.178, 1.217, 1.244, + 1.291, 1.252, 1.211, 1.175, 1.147, 1.124, 1.103, 1.088, 1.075, 1.063, 1.054, 1.046, 1.041, 1.036, 1.035, 1.035, 1.035, 1.035, 1.036, 1.038, 1.041, 1.047, 1.055, 1.065, 1.075, 1.092, 1.111, 1.132, 1.157, 1.189, 1.231, 1.255, + 1.303, 1.265, 1.222, 1.187, 1.158, 1.133, 1.112, 1.097, 1.083, 1.072, 1.063, 1.054, 1.048, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.046, 1.049, 1.055, 1.065, 1.074, 1.086, 1.102, 1.119, 1.144, 1.171, 1.203, 1.243, 1.268, + 1.317, 1.282, 1.236, 1.201, 1.171, 1.146, 1.125, 1.109, 1.095, 1.083, 1.072, 1.064, 1.058, 1.054, 1.052, 1.051, 1.051, 1.053, 1.054, 1.057, 1.061, 1.065, 1.074, 1.086, 1.099, 1.113, 1.133, 1.156, 1.183, 1.217, 1.259, 1.282, + 1.335, 1.301, 1.254, 1.218, 1.186, 1.159, 1.138, 1.121, 1.108, 1.095, 1.085, 1.076, 1.069, 1.066, 1.065, 1.063, 1.062, 1.063, 1.065, 1.068, 1.073, 1.078, 1.087, 1.098, 1.113, 1.126, 1.146, 1.171, 1.199, 1.235, 1.277, 1.299, + 1.356, 1.321, 1.274, 1.235, 1.202, 1.175, 1.153, 1.137, 1.121, 1.108, 1.097, 1.089, 1.084, 1.081, 1.077, 1.075, 1.075, 1.075, 1.077, 1.081, 1.086, 1.091, 1.099, 1.113, 1.126, 1.144, 1.162, 1.187, 1.218, 1.255, 1.297, 1.321, + 1.376, 1.344, 1.296, 1.257, 1.223, 1.194, 1.171, 1.153, 1.137, 1.124, 1.112, 1.104, 1.099, 1.095, 1.093, 1.091, 1.089, 1.091, 1.092, 1.095, 1.101, 1.108, 1.116, 1.128, 1.144, 1.161, 1.181, 1.206, 1.237, 1.275, 1.321, 1.347, + 1.403, 1.369, 1.319, 1.279, 1.244, 1.214, 1.191, 1.171, 1.154, 1.139, 1.129, 1.121, 1.115, 1.111, 1.109, 1.106, 1.105, 1.105, 1.108, 1.112, 1.117, 1.124, 1.135, 1.147, 1.162, 1.181, 1.203, 1.228, 1.262, 1.301, 1.347, 1.377, + 1.429, 1.398, 1.348, 1.306, 1.269, 1.237, 1.214, 1.191, 1.173, 1.158, 1.146, 1.138, 1.132, 1.128, 1.125, 1.123, 1.122, 1.123, 1.125, 1.129, 1.136, 1.142, 1.154, 1.166, 1.182, 1.203, 1.226, 1.253, 1.288, 1.329, 1.377, 1.406, + 1.465, 1.429, 1.377, 1.335, 1.295, 1.262, 1.236, 1.214, 1.194, 1.179, 1.167, 1.157, 1.151, 1.146, 1.144, 1.142, 1.142, 1.142, 1.144, 1.149, 1.154, 1.163, 1.174, 1.187, 1.205, 1.226, 1.251, 1.279, 1.315, 1.357, 1.406, 1.437, + 1.493, 1.465, 1.409, 1.364, 1.323, 1.289, 1.261, 1.235, 1.214, 1.194, 1.179, 1.171, 1.166, 1.163, 1.161, 1.161, 1.161, 1.161, 1.162, 1.164, 1.168, 1.175, 1.187, 1.205, 1.225, 1.251, 1.276, 1.306, 1.344, 1.387, 1.437, 1.455 + ], + "sigma": 0.0007, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.95054, -0.57435, -0.37619, + -0.46945, 1.86661, -0.39716, + 0.07977, -1.14072, 2.06095 + ] + }, + { + "ct": 2800, + "ccm": + [ + 1.94104, -0.60261, -0.33844, + -0.43162, 1.85422, -0.42261, + 0.03799, -0.95022, 1.91222 + ] + }, + { + "ct": 2900, + "ccm": + [ + 1.91828, -0.59569, -0.32258, + -0.51902, 2.09091, -0.57189, + -0.03324, -0.73462, 1.76785 + ] + }, + { + "ct": 3620, + "ccm": + [ + 1.97199, -0.66403, -0.30797, + -0.46411, 2.02612, -0.56201, + -0.07764, -0.61178, 1.68942 + ] + }, + { + "ct": 4560, + "ccm": + [ + 2.15256, -0.84787, -0.30469, + -0.48422, 2.28962, -0.80541, + -0.15113, -0.53014, 1.68127 + ] + }, + { + "ct": 5600, + "ccm": + [ + 2.04576, -0.74771, -0.29805, + -0.36332, 1.98993, -0.62662, + -0.09328, -0.46543, 1.55871 + ] + }, + { + "ct": 7400, + "ccm": + [ + 2.37532, -0.83069, -0.54462, + -0.48279, 2.84309, -1.36031, + -0.21178, -0.66532, 1.87709 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.06, + "strength": 0.5, + "limit": 0.5 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx296_16mm.json b/src/ipa/rpi/pisp/data/imx296_16mm.json new file mode 100644 index 00000000..87443745 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx296_16mm.json @@ -0,0 +1,1247 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 4724, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 860, + "reference_Y": 14551 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.751 + } + }, + { + "rpi.geq": + { + "offset": 226, + "slope": 0.01032 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2875.0, 0.4699, 0.3209, + 3610.0, 0.4089, 0.4265, + 4640.0, 0.3281, 0.5417, + 5912.0, 0.2992, 0.5771, + 7630.0, 0.2285, 0.6524 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01783, + "transverse_neg": 0.02154 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.084, 2.084, 2.085, 2.085, 2.085, 2.087, 2.088, 2.087, 2.086, 2.082, 2.082, 2.084, 2.086, 2.088, 2.088, 2.088, 2.087, 2.088, 2.088, 2.091, 2.092, 2.093, 2.093, 2.093, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.091, 2.088, + 2.086, 2.086, 2.087, 2.088, 2.089, 2.089, 2.091, 2.089, 2.087, 2.086, 2.087, 2.088, 2.091, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.093, 2.093, 2.094, 2.095, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.093, + 2.087, 2.087, 2.088, 2.091, 2.091, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.096, + 2.089, 2.088, 2.089, 2.091, 2.091, 2.092, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.092, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.095, 2.096, 2.096, 2.097, 2.099, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.099, 2.098, 2.097, + 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.094, 2.095, 2.096, 2.097, 2.098, 2.098, 2.098, 2.101, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.089, 2.091, 2.092, 2.092, 2.092, 2.092, 2.094, 2.096, 2.096, 2.097, 2.098, 2.099, 2.099, 2.099, 2.099, 2.099, 2.097, + 2.093, 2.094, 2.094, 2.094, 2.095, 2.093, 2.092, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.096, 2.096, 2.097, 2.098, 2.098, 2.101, 2.101, 2.099, 2.099, 2.099, + 2.094, 2.094, 2.094, 2.095, 2.095, 2.095, 2.091, 2.089, 2.091, 2.089, 2.089, 2.089, 2.091, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.093, 2.095, 2.096, 2.097, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.099, + 2.095, 2.094, 2.094, 2.095, 2.096, 2.095, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.093, 2.094, 2.096, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.101, 2.099, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.095, 2.092, 2.089, 2.089, 2.088, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.093, 2.093, 2.095, 2.096, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.101, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.092, 2.092, 2.094, 2.094, 2.094, 2.096, 2.096, 2.098, 2.099, 2.102, 2.103, 2.103, 2.102, 2.102, + 2.095, 2.095, 2.095, 2.096, 2.096, 2.094, 2.093, 2.091, 2.091, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.093, 2.094, 2.094, 2.095, 2.096, 2.097, 2.098, 2.099, 2.103, 2.103, 2.103, 2.101, 2.101, + 2.095, 2.096, 2.096, 2.097, 2.096, 2.095, 2.093, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.096, 2.097, 2.099, 2.101, 2.103, 2.103, 2.103, 2.101, 2.099, + 2.096, 2.096, 2.097, 2.096, 2.097, 2.096, 2.094, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.099, 2.101, 2.103, 2.103, 2.102, 2.101, 2.101, + 2.096, 2.096, 2.097, 2.097, 2.097, 2.096, 2.094, 2.093, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.097, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, + 2.097, 2.096, 2.097, 2.097, 2.097, 2.097, 2.095, 2.093, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.092, 2.092, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.101, + 2.098, 2.097, 2.096, 2.097, 2.097, 2.097, 2.095, 2.094, 2.094, 2.094, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.095, 2.095, 2.094, 2.093, 2.095, 2.096, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.102, + 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.095, 2.094, 2.095, 2.093, 2.093, 2.092, 2.092, 2.092, 2.094, 2.094, 2.096, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.102, 2.101, 2.102, + 2.098, 2.097, 2.097, 2.098, 2.097, 2.096, 2.095, 2.095, 2.095, 2.094, 2.093, 2.093, 2.094, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.095, 2.097, 2.097, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.104, 2.103, 2.102, 2.101, + 2.099, 2.098, 2.098, 2.098, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.094, 2.094, 2.094, 2.096, 2.097, 2.097, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.101, 2.101, 2.104, 2.105, 2.105, 2.103, 2.102, + 2.101, 2.099, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.098, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.103, 2.104, 2.105, 2.105, 2.104, 2.103, + 2.102, 2.102, 2.099, 2.098, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.104, 2.105, 2.106, 2.106, 2.106, 2.104, 2.104, 2.104, + 2.102, 2.101, 2.099, 2.099, 2.099, 2.101, 2.101, 2.101, 2.099, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.099, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.103, 2.105, 2.106, 2.106, 2.106, 2.106, 2.105, 2.104, 2.104, + 2.099, 2.099, 2.099, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.098, 2.097, 2.098, 2.098, 2.099, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.105, 2.105, 2.106, 2.106, 2.104, 2.104, 2.103, + 2.096, 2.097, 2.097, 2.097, 2.097, 2.099, 2.099, 2.099, 2.099, 2.097, 2.097, 2.098, 2.098, 2.099, 2.098, 2.097, 2.097, 2.099, 2.101, 2.101, 2.101, 2.101, 2.101, 2.103, 2.105, 2.105, 2.105, 2.104, 2.104, 2.103, 2.101, 2.101, + 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.098, 2.099, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.097, 2.097, 2.096, 2.098, 2.098, 2.099, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.104, 2.104, 2.103, 2.101, 2.099, 2.098, + 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.097, 2.098, 2.097, 2.097, 2.096, 2.096, 2.098, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.103, 2.104, 2.104, 2.102, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.096, 2.098, 2.098, 2.098, 2.098, 2.097, 2.098, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.099, 2.099, 2.099, 2.101, 2.102, 2.103, 2.104, 2.104, 2.104, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.097, 2.099, 2.099, 2.099, 2.099, 2.099, 2.099, 2.098, 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.098, 2.097, 2.099, 2.101, 2.099, 2.099, 2.099, 2.102, 2.102, 2.104, 2.105, 2.105, 2.102, 2.099, 2.098 + ] + }, + { + "ct": 5000, + "table": + [ + 3.431, 3.437, 3.439, 3.439, 3.436, 3.438, 3.441, 3.441, 3.441, 3.441, 3.442, 3.443, 3.443, 3.444, 3.446, 3.448, 3.451, 3.451, 3.452, 3.451, 3.449, 3.449, 3.452, 3.453, 3.454, 3.454, 3.453, 3.456, 3.456, 3.456, 3.451, 3.448, + 3.445, 3.446, 3.445, 3.449, 3.453, 3.451, 3.451, 3.446, 3.447, 3.446, 3.447, 3.451, 3.453, 3.455, 3.454, 3.453, 3.453, 3.454, 3.455, 3.456, 3.457, 3.459, 3.461, 3.462, 3.463, 3.463, 3.465, 3.466, 3.467, 3.465, 3.459, 3.457, + 3.449, 3.449, 3.449, 3.454, 3.455, 3.454, 3.453, 3.451, 3.451, 3.448, 3.451, 3.451, 3.455, 3.456, 3.457, 3.456, 3.456, 3.458, 3.457, 3.459, 3.459, 3.461, 3.464, 3.467, 3.467, 3.466, 3.468, 3.469, 3.471, 3.468, 3.465, 3.462, + 3.451, 3.448, 3.451, 3.453, 3.457, 3.455, 3.454, 3.449, 3.449, 3.448, 3.449, 3.449, 3.455, 3.455, 3.456, 3.455, 3.454, 3.455, 3.455, 3.457, 3.458, 3.458, 3.461, 3.464, 3.466, 3.468, 3.469, 3.469, 3.469, 3.468, 3.465, 3.463, + 3.449, 3.449, 3.451, 3.453, 3.456, 3.455, 3.452, 3.449, 3.448, 3.447, 3.446, 3.448, 3.451, 3.452, 3.454, 3.455, 3.455, 3.454, 3.457, 3.458, 3.458, 3.459, 3.461, 3.464, 3.464, 3.466, 3.467, 3.469, 3.469, 3.467, 3.463, 3.459, + 3.449, 3.451, 3.452, 3.454, 3.455, 3.454, 3.452, 3.449, 3.447, 3.447, 3.446, 3.449, 3.449, 3.451, 3.452, 3.452, 3.452, 3.452, 3.454, 3.455, 3.457, 3.459, 3.461, 3.464, 3.464, 3.466, 3.465, 3.468, 3.468, 3.469, 3.465, 3.462, + 3.451, 3.451, 3.452, 3.453, 3.453, 3.453, 3.451, 3.449, 3.449, 3.447, 3.446, 3.447, 3.448, 3.451, 3.451, 3.451, 3.453, 3.452, 3.452, 3.452, 3.457, 3.458, 3.461, 3.463, 3.464, 3.465, 3.464, 3.466, 3.468, 3.469, 3.466, 3.463, + 3.451, 3.451, 3.451, 3.454, 3.453, 3.453, 3.451, 3.448, 3.448, 3.444, 3.444, 3.444, 3.448, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.452, 3.454, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.467, 3.468, 3.469, 3.466, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.454, 3.453, 3.449, 3.448, 3.447, 3.447, 3.444, 3.446, 3.446, 3.446, 3.446, 3.447, 3.449, 3.449, 3.451, 3.452, 3.455, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.468, 3.469, 3.468, 3.465, 3.462, + 3.453, 3.452, 3.454, 3.456, 3.455, 3.453, 3.449, 3.447, 3.446, 3.446, 3.445, 3.448, 3.447, 3.446, 3.445, 3.446, 3.448, 3.448, 3.449, 3.453, 3.455, 3.457, 3.459, 3.461, 3.464, 3.466, 3.467, 3.468, 3.468, 3.467, 3.465, 3.463, + 3.453, 3.453, 3.454, 3.456, 3.456, 3.451, 3.448, 3.447, 3.447, 3.446, 3.445, 3.446, 3.446, 3.446, 3.446, 3.446, 3.448, 3.448, 3.449, 3.452, 3.454, 3.456, 3.459, 3.459, 3.461, 3.465, 3.466, 3.468, 3.468, 3.468, 3.467, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.456, 3.452, 3.448, 3.446, 3.446, 3.444, 3.446, 3.445, 3.446, 3.446, 3.447, 3.448, 3.449, 3.449, 3.449, 3.452, 3.453, 3.454, 3.458, 3.458, 3.461, 3.461, 3.464, 3.469, 3.469, 3.468, 3.466, 3.466, + 3.452, 3.452, 3.453, 3.454, 3.454, 3.453, 3.447, 3.446, 3.444, 3.444, 3.444, 3.444, 3.445, 3.446, 3.448, 3.451, 3.452, 3.453, 3.451, 3.453, 3.453, 3.455, 3.458, 3.459, 3.461, 3.462, 3.463, 3.468, 3.471, 3.469, 3.467, 3.467, + 3.454, 3.455, 3.457, 3.458, 3.458, 3.455, 3.449, 3.446, 3.445, 3.445, 3.445, 3.445, 3.447, 3.447, 3.448, 3.451, 3.452, 3.453, 3.452, 3.452, 3.452, 3.454, 3.457, 3.459, 3.459, 3.462, 3.464, 3.468, 3.469, 3.467, 3.465, 3.465, + 3.457, 3.455, 3.455, 3.459, 3.458, 3.454, 3.451, 3.448, 3.445, 3.445, 3.445, 3.446, 3.448, 3.449, 3.451, 3.452, 3.451, 3.453, 3.452, 3.452, 3.453, 3.457, 3.457, 3.461, 3.461, 3.463, 3.465, 3.468, 3.471, 3.468, 3.465, 3.463, + 3.458, 3.456, 3.456, 3.459, 3.457, 3.454, 3.452, 3.449, 3.447, 3.445, 3.446, 3.447, 3.447, 3.448, 3.449, 3.448, 3.449, 3.451, 3.451, 3.451, 3.451, 3.455, 3.456, 3.458, 3.462, 3.463, 3.464, 3.465, 3.467, 3.466, 3.464, 3.462, + 3.457, 3.456, 3.455, 3.457, 3.457, 3.454, 3.449, 3.447, 3.445, 3.445, 3.446, 3.446, 3.448, 3.446, 3.448, 3.449, 3.449, 3.451, 3.451, 3.451, 3.453, 3.455, 3.457, 3.459, 3.462, 3.464, 3.464, 3.465, 3.467, 3.464, 3.464, 3.463, + 3.458, 3.457, 3.455, 3.456, 3.456, 3.456, 3.453, 3.449, 3.447, 3.448, 3.447, 3.447, 3.447, 3.447, 3.447, 3.448, 3.449, 3.451, 3.451, 3.452, 3.453, 3.455, 3.458, 3.459, 3.459, 3.463, 3.464, 3.463, 3.464, 3.463, 3.464, 3.464, + 3.457, 3.456, 3.456, 3.456, 3.456, 3.456, 3.455, 3.449, 3.447, 3.448, 3.451, 3.449, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.451, 3.452, 3.453, 3.456, 3.458, 3.459, 3.461, 3.462, 3.464, 3.464, 3.465, 3.464, 3.464, 3.463, + 3.457, 3.456, 3.455, 3.455, 3.455, 3.455, 3.453, 3.451, 3.449, 3.448, 3.448, 3.449, 3.449, 3.449, 3.448, 3.449, 3.451, 3.452, 3.452, 3.453, 3.454, 3.457, 3.458, 3.459, 3.462, 3.464, 3.465, 3.464, 3.465, 3.464, 3.463, 3.463, + 3.456, 3.456, 3.454, 3.453, 3.454, 3.453, 3.452, 3.451, 3.449, 3.448, 3.448, 3.449, 3.451, 3.451, 3.448, 3.449, 3.451, 3.454, 3.454, 3.454, 3.455, 3.457, 3.458, 3.461, 3.461, 3.462, 3.464, 3.464, 3.466, 3.465, 3.464, 3.464, + 3.459, 3.457, 3.456, 3.455, 3.454, 3.453, 3.453, 3.452, 3.452, 3.451, 3.449, 3.449, 3.449, 3.448, 3.447, 3.449, 3.451, 3.454, 3.455, 3.455, 3.456, 3.458, 3.459, 3.461, 3.461, 3.462, 3.463, 3.466, 3.469, 3.465, 3.465, 3.464, + 3.463, 3.461, 3.458, 3.458, 3.457, 3.456, 3.456, 3.454, 3.454, 3.452, 3.452, 3.451, 3.451, 3.449, 3.448, 3.448, 3.452, 3.454, 3.456, 3.455, 3.457, 3.458, 3.461, 3.464, 3.462, 3.461, 3.463, 3.466, 3.469, 3.469, 3.467, 3.467, + 3.466, 3.462, 3.461, 3.461, 3.459, 3.457, 3.457, 3.457, 3.456, 3.454, 3.455, 3.455, 3.455, 3.451, 3.452, 3.453, 3.454, 3.455, 3.456, 3.456, 3.459, 3.462, 3.463, 3.466, 3.466, 3.467, 3.466, 3.469, 3.471, 3.469, 3.468, 3.466, + 3.467, 3.463, 3.463, 3.459, 3.461, 3.459, 3.461, 3.459, 3.458, 3.456, 3.457, 3.456, 3.457, 3.455, 3.456, 3.455, 3.456, 3.457, 3.459, 3.461, 3.461, 3.464, 3.465, 3.468, 3.469, 3.469, 3.469, 3.469, 3.471, 3.468, 3.467, 3.468, + 3.467, 3.464, 3.459, 3.459, 3.462, 3.462, 3.462, 3.461, 3.461, 3.462, 3.461, 3.459, 3.461, 3.459, 3.458, 3.457, 3.459, 3.461, 3.462, 3.463, 3.464, 3.466, 3.468, 3.469, 3.471, 3.469, 3.471, 3.472, 3.471, 3.467, 3.466, 3.464, + 3.464, 3.462, 3.458, 3.457, 3.458, 3.461, 3.461, 3.461, 3.461, 3.462, 3.462, 3.461, 3.461, 3.459, 3.459, 3.459, 3.461, 3.461, 3.464, 3.465, 3.465, 3.468, 3.468, 3.469, 3.471, 3.469, 3.469, 3.469, 3.469, 3.464, 3.462, 3.459, + 3.457, 3.458, 3.455, 3.456, 3.456, 3.457, 3.459, 3.459, 3.459, 3.459, 3.458, 3.456, 3.458, 3.457, 3.458, 3.458, 3.458, 3.459, 3.461, 3.463, 3.465, 3.466, 3.468, 3.469, 3.471, 3.468, 3.466, 3.466, 3.465, 3.461, 3.459, 3.457, + 3.456, 3.455, 3.454, 3.454, 3.455, 3.456, 3.458, 3.459, 3.459, 3.456, 3.456, 3.456, 3.455, 3.456, 3.455, 3.455, 3.455, 3.454, 3.457, 3.461, 3.462, 3.464, 3.465, 3.467, 3.467, 3.466, 3.464, 3.464, 3.463, 3.461, 3.457, 3.456, + 3.456, 3.454, 3.453, 3.454, 3.454, 3.455, 3.458, 3.459, 3.459, 3.456, 3.455, 3.455, 3.455, 3.451, 3.453, 3.454, 3.454, 3.455, 3.455, 3.458, 3.461, 3.462, 3.461, 3.463, 3.465, 3.464, 3.463, 3.463, 3.462, 3.459, 3.456, 3.451, + 3.455, 3.452, 3.452, 3.452, 3.455, 3.457, 3.459, 3.459, 3.459, 3.458, 3.456, 3.456, 3.455, 3.453, 3.453, 3.455, 3.457, 3.457, 3.457, 3.461, 3.461, 3.461, 3.459, 3.462, 3.464, 3.464, 3.464, 3.463, 3.463, 3.459, 3.454, 3.451, + 3.452, 3.452, 3.452, 3.453, 3.457, 3.458, 3.458, 3.459, 3.459, 3.458, 3.457, 3.457, 3.455, 3.455, 3.458, 3.459, 3.458, 3.459, 3.459, 3.461, 3.461, 3.461, 3.459, 3.461, 3.463, 3.464, 3.466, 3.463, 3.461, 3.458, 3.453, 3.449 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.403, 3.399, 3.395, 3.391, 3.392, 3.394, 3.401, 3.403, 3.404, 3.404, 3.403, 3.399, 3.398, 3.396, 3.395, 3.396, 3.399, 3.403, 3.404, 3.401, 3.399, 3.398, 3.397, 3.401, 3.401, 3.401, 3.396, 3.394, 3.397, 3.396, 3.388, 3.364, + 3.403, 3.399, 3.393, 3.389, 3.391, 3.395, 3.401, 3.404, 3.406, 3.404, 3.403, 3.399, 3.399, 3.397, 3.397, 3.397, 3.401, 3.404, 3.404, 3.402, 3.398, 3.396, 3.397, 3.401, 3.401, 3.401, 3.395, 3.394, 3.396, 3.393, 3.387, 3.364, + 3.399, 3.398, 3.391, 3.385, 3.386, 3.395, 3.402, 3.405, 3.405, 3.404, 3.402, 3.399, 3.399, 3.398, 3.398, 3.398, 3.401, 3.404, 3.405, 3.403, 3.399, 3.396, 3.396, 3.398, 3.401, 3.401, 3.398, 3.394, 3.392, 3.389, 3.386, 3.364, + 3.398, 3.393, 3.386, 3.382, 3.385, 3.392, 3.399, 3.403, 3.405, 3.404, 3.402, 3.398, 3.398, 3.397, 3.397, 3.398, 3.401, 3.404, 3.405, 3.403, 3.398, 3.394, 3.394, 3.398, 3.401, 3.401, 3.396, 3.392, 3.391, 3.388, 3.383, 3.362, + 3.396, 3.391, 3.384, 3.381, 3.384, 3.389, 3.398, 3.402, 3.402, 3.401, 3.399, 3.395, 3.395, 3.395, 3.397, 3.397, 3.401, 3.402, 3.404, 3.403, 3.399, 3.394, 3.393, 3.395, 3.399, 3.399, 3.397, 3.391, 3.388, 3.384, 3.381, 3.363, + 3.391, 3.386, 3.382, 3.381, 3.385, 3.389, 3.396, 3.398, 3.399, 3.399, 3.398, 3.395, 3.394, 3.394, 3.395, 3.397, 3.399, 3.401, 3.403, 3.401, 3.398, 3.394, 3.393, 3.393, 3.394, 3.396, 3.395, 3.392, 3.387, 3.382, 3.378, 3.361, + 3.389, 3.386, 3.379, 3.379, 3.383, 3.388, 3.394, 3.397, 3.397, 3.397, 3.395, 3.393, 3.393, 3.393, 3.395, 3.395, 3.397, 3.398, 3.401, 3.399, 3.397, 3.395, 3.394, 3.391, 3.393, 3.393, 3.393, 3.389, 3.387, 3.381, 3.374, 3.357, + 3.386, 3.383, 3.376, 3.375, 3.381, 3.386, 3.394, 3.396, 3.396, 3.394, 3.392, 3.392, 3.394, 3.394, 3.395, 3.394, 3.396, 3.398, 3.399, 3.397, 3.397, 3.394, 3.393, 3.391, 3.389, 3.391, 3.392, 3.388, 3.386, 3.379, 3.372, 3.355, + 3.386, 3.379, 3.373, 3.373, 3.378, 3.384, 3.391, 3.396, 3.395, 3.393, 3.389, 3.391, 3.391, 3.393, 3.394, 3.393, 3.394, 3.396, 3.397, 3.396, 3.393, 3.394, 3.393, 3.392, 3.389, 3.389, 3.389, 3.389, 3.386, 3.378, 3.371, 3.351, + 3.379, 3.375, 3.371, 3.371, 3.376, 3.381, 3.388, 3.393, 3.394, 3.391, 3.386, 3.386, 3.388, 3.393, 3.392, 3.392, 3.393, 3.395, 3.394, 3.392, 3.389, 3.391, 3.391, 3.392, 3.389, 3.388, 3.389, 3.389, 3.383, 3.377, 3.369, 3.351, + 3.373, 3.371, 3.367, 3.368, 3.373, 3.381, 3.387, 3.389, 3.391, 3.389, 3.385, 3.386, 3.383, 3.389, 3.389, 3.392, 3.392, 3.394, 3.393, 3.389, 3.387, 3.387, 3.388, 3.389, 3.389, 3.388, 3.386, 3.386, 3.382, 3.374, 3.367, 3.345, + 3.371, 3.369, 3.365, 3.366, 3.373, 3.379, 3.386, 3.389, 3.391, 3.389, 3.385, 3.384, 3.382, 3.386, 3.387, 3.389, 3.391, 3.392, 3.391, 3.387, 3.385, 3.385, 3.386, 3.388, 3.388, 3.388, 3.386, 3.385, 3.381, 3.373, 3.367, 3.345, + 3.367, 3.365, 3.365, 3.366, 3.374, 3.379, 3.384, 3.388, 3.389, 3.387, 3.384, 3.383, 3.383, 3.385, 3.385, 3.386, 3.388, 3.389, 3.388, 3.386, 3.383, 3.382, 3.384, 3.386, 3.387, 3.386, 3.381, 3.381, 3.379, 3.372, 3.364, 3.344, + 3.365, 3.363, 3.362, 3.367, 3.375, 3.379, 3.383, 3.384, 3.386, 3.384, 3.381, 3.379, 3.379, 3.383, 3.383, 3.384, 3.385, 3.387, 3.387, 3.385, 3.381, 3.381, 3.382, 3.384, 3.384, 3.385, 3.382, 3.379, 3.374, 3.369, 3.359, 3.343, + 3.359, 3.358, 3.361, 3.364, 3.373, 3.381, 3.384, 3.384, 3.385, 3.384, 3.381, 3.377, 3.379, 3.379, 3.382, 3.383, 3.384, 3.386, 3.386, 3.385, 3.381, 3.379, 3.381, 3.382, 3.382, 3.383, 3.379, 3.377, 3.371, 3.364, 3.357, 3.339, + 3.357, 3.356, 3.356, 3.362, 3.372, 3.379, 3.384, 3.384, 3.383, 3.381, 3.378, 3.376, 3.377, 3.379, 3.381, 3.382, 3.383, 3.385, 3.385, 3.383, 3.379, 3.379, 3.379, 3.381, 3.381, 3.382, 3.379, 3.372, 3.367, 3.362, 3.354, 3.334, + 3.357, 3.354, 3.357, 3.361, 3.372, 3.381, 3.385, 3.385, 3.384, 3.379, 3.376, 3.376, 3.376, 3.379, 3.381, 3.383, 3.383, 3.384, 3.383, 3.379, 3.378, 3.381, 3.379, 3.379, 3.379, 3.379, 3.378, 3.371, 3.363, 3.358, 3.354, 3.332, + 3.354, 3.351, 3.354, 3.359, 3.371, 3.379, 3.382, 3.384, 3.381, 3.378, 3.375, 3.374, 3.376, 3.378, 3.381, 3.383, 3.384, 3.382, 3.377, 3.377, 3.376, 3.377, 3.378, 3.378, 3.379, 3.379, 3.376, 3.367, 3.361, 3.357, 3.352, 3.333, + 3.352, 3.349, 3.351, 3.357, 3.372, 3.381, 3.383, 3.383, 3.381, 3.376, 3.372, 3.373, 3.375, 3.377, 3.382, 3.384, 3.384, 3.379, 3.376, 3.374, 3.374, 3.375, 3.375, 3.376, 3.377, 3.376, 3.373, 3.366, 3.361, 3.356, 3.347, 3.332, + 3.347, 3.346, 3.346, 3.355, 3.371, 3.377, 3.382, 3.381, 3.379, 3.372, 3.371, 3.371, 3.372, 3.375, 3.379, 3.383, 3.384, 3.379, 3.374, 3.373, 3.371, 3.373, 3.374, 3.375, 3.374, 3.374, 3.371, 3.365, 3.359, 3.352, 3.343, 3.331, + 3.345, 3.344, 3.345, 3.353, 3.367, 3.374, 3.382, 3.382, 3.376, 3.371, 3.369, 3.368, 3.369, 3.373, 3.377, 3.381, 3.379, 3.376, 3.373, 3.369, 3.368, 3.371, 3.372, 3.373, 3.371, 3.371, 3.369, 3.363, 3.357, 3.349, 3.341, 3.326, + 3.343, 3.341, 3.344, 3.351, 3.362, 3.371, 3.376, 3.376, 3.372, 3.369, 3.367, 3.366, 3.367, 3.369, 3.376, 3.378, 3.378, 3.375, 3.371, 3.367, 3.367, 3.368, 3.369, 3.369, 3.369, 3.368, 3.365, 3.361, 3.354, 3.347, 3.338, 3.321, + 3.341, 3.339, 3.342, 3.349, 3.359, 3.367, 3.371, 3.372, 3.371, 3.368, 3.366, 3.363, 3.365, 3.368, 3.371, 3.374, 3.376, 3.374, 3.368, 3.365, 3.365, 3.366, 3.368, 3.367, 3.367, 3.363, 3.361, 3.356, 3.352, 3.346, 3.336, 3.317, + 3.338, 3.336, 3.338, 3.346, 3.359, 3.364, 3.368, 3.369, 3.367, 3.366, 3.363, 3.362, 3.364, 3.364, 3.367, 3.371, 3.372, 3.369, 3.365, 3.362, 3.362, 3.365, 3.367, 3.367, 3.366, 3.362, 3.357, 3.353, 3.349, 3.342, 3.335, 3.317, + 3.334, 3.334, 3.336, 3.346, 3.354, 3.361, 3.365, 3.365, 3.365, 3.362, 3.361, 3.361, 3.362, 3.362, 3.364, 3.366, 3.368, 3.366, 3.361, 3.357, 3.357, 3.359, 3.363, 3.365, 3.363, 3.361, 3.355, 3.351, 3.346, 3.339, 3.336, 3.317, + 3.332, 3.332, 3.334, 3.344, 3.354, 3.359, 3.363, 3.365, 3.363, 3.361, 3.359, 3.359, 3.363, 3.363, 3.365, 3.365, 3.367, 3.366, 3.358, 3.356, 3.356, 3.358, 3.362, 3.364, 3.363, 3.359, 3.353, 3.348, 3.345, 3.339, 3.336, 3.315, + 3.332, 3.328, 3.331, 3.343, 3.351, 3.357, 3.358, 3.362, 3.361, 3.359, 3.357, 3.357, 3.361, 3.362, 3.364, 3.363, 3.363, 3.359, 3.356, 3.354, 3.354, 3.355, 3.358, 3.359, 3.361, 3.359, 3.351, 3.346, 3.344, 3.339, 3.336, 3.313, + 3.324, 3.324, 3.327, 3.334, 3.345, 3.351, 3.354, 3.356, 3.356, 3.354, 3.353, 3.354, 3.357, 3.358, 3.361, 3.358, 3.359, 3.355, 3.352, 3.348, 3.347, 3.351, 3.354, 3.358, 3.359, 3.355, 3.346, 3.343, 3.341, 3.336, 3.331, 3.312, + 3.318, 3.319, 3.321, 3.328, 3.337, 3.339, 3.345, 3.348, 3.346, 3.345, 3.347, 3.348, 3.351, 3.354, 3.356, 3.353, 3.354, 3.344, 3.343, 3.343, 3.343, 3.344, 3.347, 3.349, 3.353, 3.346, 3.341, 3.339, 3.331, 3.329, 3.325, 3.311, + 3.309, 3.313, 3.317, 3.325, 3.329, 3.332, 3.338, 3.339, 3.341, 3.339, 3.339, 3.342, 3.346, 3.346, 3.351, 3.351, 3.343, 3.338, 3.338, 3.339, 3.339, 3.339, 3.341, 3.341, 3.346, 3.343, 3.339, 3.332, 3.327, 3.326, 3.322, 3.309, + 3.305, 3.309, 3.317, 3.325, 3.328, 3.331, 3.334, 3.336, 3.337, 3.336, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.341, 3.336, 3.335, 3.337, 3.339, 3.341, 3.339, 3.339, 3.342, 3.341, 3.337, 3.329, 3.326, 3.325, 3.321, 3.314, + 3.302, 3.306, 3.319, 3.325, 3.329, 3.331, 3.334, 3.335, 3.337, 3.337, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.342, 3.336, 3.336, 3.338, 3.339, 3.341, 3.341, 3.341, 3.339, 3.338, 3.336, 3.331, 3.327, 3.324, 3.321, 3.314 + ] + }, + { + "ct": 5000, + "table": + [ + 1.726, 1.725, 1.723, 1.721, 1.723, 1.724, 1.724, 1.726, 1.727, 1.728, 1.729, 1.728, 1.725, 1.724, 1.726, 1.726, 1.727, 1.729, 1.727, 1.727, 1.724, 1.725, 1.724, 1.726, 1.725, 1.725, 1.724, 1.724, 1.722, 1.721, 1.719, 1.714, + 1.726, 1.724, 1.722, 1.721, 1.722, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.727, 1.725, 1.724, 1.724, 1.725, 1.726, 1.725, 1.724, 1.723, 1.722, 1.721, 1.719, 1.714, + 1.724, 1.722, 1.719, 1.719, 1.721, 1.723, 1.726, 1.726, 1.727, 1.727, 1.727, 1.725, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.725, 1.724, 1.724, 1.724, 1.726, 1.725, 1.724, 1.722, 1.722, 1.721, 1.719, 1.712, + 1.723, 1.721, 1.719, 1.719, 1.719, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.726, 1.728, 1.729, 1.728, 1.725, 1.723, 1.723, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.718, 1.711, + 1.722, 1.719, 1.719, 1.718, 1.719, 1.722, 1.725, 1.726, 1.726, 1.727, 1.727, 1.726, 1.725, 1.726, 1.726, 1.726, 1.727, 1.727, 1.728, 1.727, 1.726, 1.725, 1.724, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.715, 1.711, + 1.721, 1.717, 1.717, 1.716, 1.719, 1.722, 1.724, 1.726, 1.726, 1.727, 1.726, 1.726, 1.726, 1.726, 1.726, 1.727, 1.727, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.722, 1.721, 1.718, 1.715, 1.707, + 1.718, 1.717, 1.716, 1.716, 1.718, 1.721, 1.725, 1.726, 1.726, 1.726, 1.725, 1.725, 1.725, 1.725, 1.726, 1.727, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.718, 1.715, 1.709, + 1.718, 1.716, 1.716, 1.715, 1.717, 1.721, 1.724, 1.725, 1.726, 1.725, 1.725, 1.724, 1.724, 1.725, 1.726, 1.726, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.723, 1.723, 1.723, 1.722, 1.722, 1.719, 1.718, 1.714, 1.709, + 1.718, 1.716, 1.715, 1.715, 1.717, 1.721, 1.723, 1.725, 1.726, 1.725, 1.724, 1.723, 1.724, 1.725, 1.725, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.723, 1.722, 1.722, 1.721, 1.717, 1.714, 1.707, + 1.717, 1.716, 1.714, 1.714, 1.716, 1.721, 1.723, 1.725, 1.725, 1.725, 1.723, 1.723, 1.724, 1.726, 1.726, 1.726, 1.726, 1.725, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.706, + 1.715, 1.714, 1.714, 1.714, 1.716, 1.719, 1.722, 1.724, 1.725, 1.725, 1.723, 1.723, 1.724, 1.725, 1.725, 1.725, 1.726, 1.725, 1.725, 1.725, 1.724, 1.724, 1.724, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.713, 1.705, + 1.714, 1.714, 1.713, 1.714, 1.717, 1.719, 1.722, 1.724, 1.724, 1.724, 1.723, 1.722, 1.723, 1.724, 1.724, 1.724, 1.726, 1.725, 1.726, 1.725, 1.723, 1.723, 1.724, 1.724, 1.724, 1.723, 1.721, 1.719, 1.717, 1.715, 1.713, 1.706, + 1.712, 1.712, 1.712, 1.713, 1.718, 1.719, 1.721, 1.723, 1.724, 1.724, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.725, 1.725, 1.725, 1.725, 1.723, 1.722, 1.724, 1.723, 1.723, 1.722, 1.721, 1.719, 1.717, 1.714, 1.711, 1.706, + 1.712, 1.711, 1.711, 1.713, 1.717, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.724, 1.725, 1.725, 1.724, 1.723, 1.722, 1.722, 1.722, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.711, 1.706, + 1.711, 1.709, 1.711, 1.713, 1.716, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.721, 1.722, 1.724, 1.724, 1.724, 1.723, 1.724, 1.724, 1.724, 1.722, 1.722, 1.722, 1.722, 1.722, 1.721, 1.719, 1.718, 1.714, 1.712, 1.709, 1.702, + 1.709, 1.709, 1.709, 1.712, 1.717, 1.719, 1.721, 1.723, 1.723, 1.723, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.724, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.716, 1.713, 1.711, 1.709, 1.701, + 1.708, 1.707, 1.709, 1.712, 1.716, 1.719, 1.722, 1.723, 1.723, 1.723, 1.721, 1.721, 1.721, 1.722, 1.723, 1.723, 1.723, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.714, 1.712, 1.709, 1.708, 1.702, + 1.707, 1.707, 1.708, 1.711, 1.716, 1.721, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.722, 1.722, 1.723, 1.723, 1.723, 1.722, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.721, 1.717, 1.714, 1.711, 1.709, 1.707, 1.702, + 1.706, 1.706, 1.707, 1.711, 1.714, 1.719, 1.722, 1.722, 1.722, 1.721, 1.719, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.719, 1.719, 1.721, 1.721, 1.719, 1.719, 1.716, 1.713, 1.711, 1.709, 1.706, 1.701, + 1.705, 1.704, 1.706, 1.709, 1.713, 1.718, 1.721, 1.722, 1.721, 1.719, 1.718, 1.719, 1.721, 1.722, 1.723, 1.724, 1.724, 1.721, 1.721, 1.721, 1.719, 1.719, 1.719, 1.719, 1.719, 1.717, 1.715, 1.713, 1.711, 1.707, 1.704, 1.699, + 1.703, 1.703, 1.704, 1.709, 1.712, 1.717, 1.719, 1.721, 1.719, 1.718, 1.717, 1.718, 1.719, 1.721, 1.722, 1.723, 1.723, 1.722, 1.719, 1.719, 1.718, 1.719, 1.719, 1.718, 1.717, 1.716, 1.714, 1.712, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.703, 1.704, 1.708, 1.712, 1.715, 1.718, 1.719, 1.719, 1.717, 1.717, 1.717, 1.717, 1.718, 1.721, 1.722, 1.722, 1.721, 1.719, 1.718, 1.717, 1.718, 1.718, 1.717, 1.716, 1.714, 1.714, 1.711, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.702, 1.703, 1.706, 1.709, 1.715, 1.717, 1.718, 1.717, 1.717, 1.716, 1.716, 1.717, 1.717, 1.719, 1.721, 1.721, 1.721, 1.719, 1.717, 1.716, 1.717, 1.717, 1.716, 1.714, 1.713, 1.712, 1.711, 1.708, 1.706, 1.702, 1.696, + 1.701, 1.701, 1.702, 1.706, 1.709, 1.714, 1.716, 1.717, 1.716, 1.716, 1.716, 1.715, 1.716, 1.716, 1.717, 1.718, 1.719, 1.719, 1.716, 1.715, 1.715, 1.715, 1.715, 1.715, 1.714, 1.713, 1.711, 1.709, 1.708, 1.704, 1.701, 1.695, + 1.699, 1.699, 1.702, 1.706, 1.708, 1.712, 1.714, 1.715, 1.715, 1.715, 1.714, 1.715, 1.714, 1.715, 1.716, 1.716, 1.716, 1.716, 1.714, 1.713, 1.713, 1.714, 1.715, 1.714, 1.714, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.695, + 1.698, 1.699, 1.701, 1.705, 1.708, 1.711, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.715, 1.715, 1.716, 1.716, 1.715, 1.713, 1.713, 1.713, 1.714, 1.714, 1.714, 1.713, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.696, + 1.698, 1.699, 1.701, 1.705, 1.707, 1.711, 1.712, 1.713, 1.713, 1.713, 1.713, 1.714, 1.714, 1.715, 1.715, 1.716, 1.715, 1.714, 1.713, 1.712, 1.712, 1.712, 1.713, 1.713, 1.713, 1.711, 1.709, 1.707, 1.705, 1.703, 1.701, 1.696, + 1.698, 1.697, 1.699, 1.702, 1.705, 1.707, 1.711, 1.711, 1.711, 1.711, 1.711, 1.712, 1.712, 1.713, 1.714, 1.714, 1.713, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.708, 1.706, 1.704, 1.703, 1.699, 1.696, + 1.694, 1.695, 1.697, 1.699, 1.702, 1.705, 1.706, 1.707, 1.707, 1.708, 1.708, 1.708, 1.709, 1.711, 1.711, 1.711, 1.708, 1.708, 1.708, 1.707, 1.707, 1.707, 1.708, 1.708, 1.709, 1.708, 1.706, 1.703, 1.702, 1.701, 1.698, 1.696, + 1.692, 1.692, 1.695, 1.698, 1.699, 1.701, 1.704, 1.704, 1.704, 1.704, 1.705, 1.706, 1.707, 1.709, 1.709, 1.707, 1.706, 1.704, 1.704, 1.705, 1.705, 1.706, 1.706, 1.706, 1.706, 1.706, 1.703, 1.702, 1.701, 1.699, 1.696, 1.694, + 1.691, 1.692, 1.695, 1.697, 1.699, 1.699, 1.702, 1.703, 1.703, 1.702, 1.703, 1.704, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.704, 1.705, 1.705, 1.705, 1.705, 1.704, 1.703, 1.701, 1.699, 1.698, 1.696, 1.695, + 1.689, 1.691, 1.696, 1.698, 1.699, 1.699, 1.701, 1.702, 1.702, 1.702, 1.703, 1.703, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.703, 1.704, 1.704, 1.705, 1.704, 1.704, 1.702, 1.701, 1.698, 1.698, 1.696, 1.696 + ] + } + ], + "luminance_lut": + [ + 1.425, 1.393, 1.341, 1.295, 1.258, 1.226, 1.201, 1.181, 1.162, 1.146, 1.133, 1.123, 1.115, 1.111, 1.107, 1.106, 1.106, 1.107, 1.108, 1.111, 1.114, 1.122, 1.133, 1.148, 1.164, 1.184, 1.208, 1.236, 1.271, 1.309, 1.359, 1.381, + 1.397, 1.367, 1.317, 1.274, 1.237, 1.207, 1.183, 1.163, 1.146, 1.133, 1.123, 1.114, 1.107, 1.101, 1.098, 1.096, 1.096, 1.096, 1.097, 1.102, 1.106, 1.112, 1.122, 1.133, 1.148, 1.166, 1.187, 1.215, 1.249, 1.288, 1.335, 1.359, + 1.374, 1.341, 1.292, 1.251, 1.215, 1.186, 1.166, 1.146, 1.131, 1.117, 1.108, 1.099, 1.091, 1.088, 1.084, 1.082, 1.081, 1.082, 1.084, 1.088, 1.093, 1.098, 1.107, 1.118, 1.133, 1.149, 1.169, 1.195, 1.228, 1.267, 1.313, 1.335, + 1.352, 1.318, 1.271, 1.231, 1.196, 1.169, 1.149, 1.131, 1.115, 1.103, 1.093, 1.086, 1.079, 1.074, 1.071, 1.069, 1.069, 1.069, 1.071, 1.076, 1.079, 1.085, 1.094, 1.102, 1.117, 1.133, 1.152, 1.176, 1.208, 1.246, 1.289, 1.313, + 1.333, 1.298, 1.253, 1.212, 1.179, 1.153, 1.134, 1.116, 1.102, 1.089, 1.079, 1.072, 1.066, 1.062, 1.059, 1.058, 1.057, 1.057, 1.059, 1.064, 1.068, 1.072, 1.081, 1.091, 1.102, 1.119, 1.137, 1.161, 1.191, 1.227, 1.271, 1.293, + 1.317, 1.281, 1.235, 1.196, 1.165, 1.139, 1.119, 1.104, 1.089, 1.078, 1.068, 1.062, 1.055, 1.051, 1.048, 1.047, 1.047, 1.047, 1.048, 1.053, 1.056, 1.061, 1.069, 1.079, 1.091, 1.105, 1.126, 1.147, 1.177, 1.212, 1.253, 1.278, + 1.301, 1.265, 1.221, 1.181, 1.151, 1.127, 1.108, 1.091, 1.078, 1.068, 1.059, 1.051, 1.045, 1.041, 1.038, 1.037, 1.036, 1.037, 1.038, 1.042, 1.046, 1.051, 1.059, 1.069, 1.081, 1.096, 1.113, 1.135, 1.164, 1.198, 1.238, 1.264, + 1.286, 1.251, 1.207, 1.169, 1.141, 1.116, 1.098, 1.081, 1.068, 1.058, 1.049, 1.042, 1.037, 1.033, 1.031, 1.029, 1.028, 1.028, 1.029, 1.033, 1.037, 1.043, 1.051, 1.059, 1.071, 1.086, 1.104, 1.124, 1.152, 1.185, 1.225, 1.252, + 1.275, 1.239, 1.196, 1.161, 1.132, 1.107, 1.089, 1.073, 1.059, 1.049, 1.041, 1.035, 1.028, 1.024, 1.023, 1.021, 1.021, 1.021, 1.022, 1.024, 1.029, 1.036, 1.043, 1.051, 1.063, 1.078, 1.095, 1.115, 1.143, 1.175, 1.214, 1.243, + 1.267, 1.227, 1.187, 1.152, 1.122, 1.101, 1.081, 1.067, 1.054, 1.042, 1.035, 1.028, 1.023, 1.018, 1.015, 1.014, 1.014, 1.014, 1.016, 1.019, 1.024, 1.029, 1.036, 1.045, 1.056, 1.071, 1.088, 1.107, 1.134, 1.167, 1.204, 1.234, + 1.261, 1.219, 1.179, 1.145, 1.116, 1.095, 1.076, 1.061, 1.047, 1.037, 1.031, 1.023, 1.018, 1.014, 1.011, 1.009, 1.009, 1.009, 1.011, 1.013, 1.018, 1.024, 1.031, 1.039, 1.049, 1.065, 1.083, 1.102, 1.128, 1.161, 1.196, 1.228, + 1.256, 1.213, 1.173, 1.139, 1.111, 1.091, 1.071, 1.056, 1.043, 1.033, 1.026, 1.019, 1.014, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.009, 1.013, 1.018, 1.026, 1.035, 1.046, 1.061, 1.078, 1.097, 1.123, 1.154, 1.191, 1.222, + 1.251, 1.208, 1.169, 1.137, 1.108, 1.088, 1.069, 1.053, 1.039, 1.029, 1.023, 1.015, 1.011, 1.006, 1.004, 1.003, 1.001, 1.002, 1.003, 1.006, 1.009, 1.015, 1.022, 1.032, 1.044, 1.057, 1.076, 1.094, 1.119, 1.149, 1.186, 1.218, + 1.249, 1.205, 1.167, 1.133, 1.107, 1.085, 1.067, 1.052, 1.038, 1.029, 1.021, 1.013, 1.008, 1.004, 1.003, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.013, 1.021, 1.031, 1.042, 1.055, 1.073, 1.093, 1.116, 1.147, 1.182, 1.218, + 1.249, 1.204, 1.165, 1.132, 1.106, 1.085, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.007, 1.003, 1.002, 1.001, 1.001, 1.001, 1.001, 1.004, 1.007, 1.013, 1.021, 1.029, 1.042, 1.055, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.249, 1.204, 1.165, 1.132, 1.107, 1.086, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.008, 1.004, 1.002, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.014, 1.021, 1.029, 1.042, 1.056, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.251, 1.205, 1.166, 1.133, 1.108, 1.087, 1.068, 1.052, 1.039, 1.031, 1.021, 1.014, 1.009, 1.006, 1.003, 1.002, 1.001, 1.001, 1.003, 1.006, 1.009, 1.014, 1.022, 1.031, 1.043, 1.056, 1.073, 1.093, 1.116, 1.145, 1.182, 1.218, + 1.252, 1.208, 1.168, 1.137, 1.111, 1.089, 1.071, 1.055, 1.043, 1.033, 1.023, 1.016, 1.012, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.008, 1.012, 1.017, 1.024, 1.034, 1.045, 1.059, 1.075, 1.095, 1.119, 1.149, 1.185, 1.218, + 1.256, 1.213, 1.173, 1.142, 1.115, 1.093, 1.075, 1.059, 1.047, 1.036, 1.027, 1.021, 1.016, 1.012, 1.011, 1.009, 1.008, 1.008, 1.009, 1.012, 1.016, 1.021, 1.028, 1.038, 1.049, 1.064, 1.081, 1.099, 1.126, 1.155, 1.192, 1.223, + 1.261, 1.221, 1.179, 1.148, 1.121, 1.099, 1.081, 1.065, 1.052, 1.042, 1.032, 1.026, 1.021, 1.017, 1.015, 1.014, 1.014, 1.013, 1.013, 1.016, 1.021, 1.026, 1.033, 1.043, 1.054, 1.068, 1.085, 1.106, 1.132, 1.161, 1.199, 1.228, + 1.267, 1.228, 1.188, 1.155, 1.128, 1.105, 1.086, 1.071, 1.059, 1.047, 1.038, 1.031, 1.027, 1.022, 1.021, 1.019, 1.019, 1.019, 1.019, 1.022, 1.026, 1.032, 1.038, 1.049, 1.061, 1.075, 1.092, 1.112, 1.138, 1.169, 1.207, 1.236, + 1.278, 1.241, 1.199, 1.164, 1.137, 1.114, 1.094, 1.078, 1.066, 1.055, 1.046, 1.038, 1.032, 1.029, 1.027, 1.027, 1.027, 1.027, 1.027, 1.029, 1.032, 1.038, 1.047, 1.056, 1.067, 1.083, 1.099, 1.121, 1.146, 1.178, 1.217, 1.244, + 1.291, 1.252, 1.211, 1.175, 1.147, 1.124, 1.103, 1.088, 1.075, 1.063, 1.054, 1.046, 1.041, 1.036, 1.035, 1.035, 1.035, 1.035, 1.036, 1.038, 1.041, 1.047, 1.055, 1.065, 1.075, 1.092, 1.111, 1.132, 1.157, 1.189, 1.231, 1.255, + 1.303, 1.265, 1.222, 1.187, 1.158, 1.133, 1.112, 1.097, 1.083, 1.072, 1.063, 1.054, 1.048, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.046, 1.049, 1.055, 1.065, 1.074, 1.086, 1.102, 1.119, 1.144, 1.171, 1.203, 1.243, 1.268, + 1.317, 1.282, 1.236, 1.201, 1.171, 1.146, 1.125, 1.109, 1.095, 1.083, 1.072, 1.064, 1.058, 1.054, 1.052, 1.051, 1.051, 1.053, 1.054, 1.057, 1.061, 1.065, 1.074, 1.086, 1.099, 1.113, 1.133, 1.156, 1.183, 1.217, 1.259, 1.282, + 1.335, 1.301, 1.254, 1.218, 1.186, 1.159, 1.138, 1.121, 1.108, 1.095, 1.085, 1.076, 1.069, 1.066, 1.065, 1.063, 1.062, 1.063, 1.065, 1.068, 1.073, 1.078, 1.087, 1.098, 1.113, 1.126, 1.146, 1.171, 1.199, 1.235, 1.277, 1.299, + 1.356, 1.321, 1.274, 1.235, 1.202, 1.175, 1.153, 1.137, 1.121, 1.108, 1.097, 1.089, 1.084, 1.081, 1.077, 1.075, 1.075, 1.075, 1.077, 1.081, 1.086, 1.091, 1.099, 1.113, 1.126, 1.144, 1.162, 1.187, 1.218, 1.255, 1.297, 1.321, + 1.376, 1.344, 1.296, 1.257, 1.223, 1.194, 1.171, 1.153, 1.137, 1.124, 1.112, 1.104, 1.099, 1.095, 1.093, 1.091, 1.089, 1.091, 1.092, 1.095, 1.101, 1.108, 1.116, 1.128, 1.144, 1.161, 1.181, 1.206, 1.237, 1.275, 1.321, 1.347, + 1.403, 1.369, 1.319, 1.279, 1.244, 1.214, 1.191, 1.171, 1.154, 1.139, 1.129, 1.121, 1.115, 1.111, 1.109, 1.106, 1.105, 1.105, 1.108, 1.112, 1.117, 1.124, 1.135, 1.147, 1.162, 1.181, 1.203, 1.228, 1.262, 1.301, 1.347, 1.377, + 1.429, 1.398, 1.348, 1.306, 1.269, 1.237, 1.214, 1.191, 1.173, 1.158, 1.146, 1.138, 1.132, 1.128, 1.125, 1.123, 1.122, 1.123, 1.125, 1.129, 1.136, 1.142, 1.154, 1.166, 1.182, 1.203, 1.226, 1.253, 1.288, 1.329, 1.377, 1.406, + 1.465, 1.429, 1.377, 1.335, 1.295, 1.262, 1.236, 1.214, 1.194, 1.179, 1.167, 1.157, 1.151, 1.146, 1.144, 1.142, 1.142, 1.142, 1.144, 1.149, 1.154, 1.163, 1.174, 1.187, 1.205, 1.226, 1.251, 1.279, 1.315, 1.357, 1.406, 1.437, + 1.493, 1.465, 1.409, 1.364, 1.323, 1.289, 1.261, 1.235, 1.214, 1.194, 1.179, 1.171, 1.166, 1.163, 1.161, 1.161, 1.161, 1.161, 1.162, 1.164, 1.168, 1.175, 1.187, 1.205, 1.225, 1.251, 1.276, 1.306, 1.344, 1.387, 1.437, 1.455 + ], + "sigma": 0.0007, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.95054, -0.57435, -0.37619, + -0.46945, 1.86661, -0.39716, + 0.07977, -1.14072, 2.06095 + ] + }, + { + "ct": 2800, + "ccm": + [ + 1.94104, -0.60261, -0.33844, + -0.43162, 1.85422, -0.42261, + 0.03799, -0.95022, 1.91222 + ] + }, + { + "ct": 2900, + "ccm": + [ + 1.91828, -0.59569, -0.32258, + -0.51902, 2.09091, -0.57189, + -0.03324, -0.73462, 1.76785 + ] + }, + { + "ct": 3620, + "ccm": + [ + 1.97199, -0.66403, -0.30797, + -0.46411, 2.02612, -0.56201, + -0.07764, -0.61178, 1.68942 + ] + }, + { + "ct": 4560, + "ccm": + [ + 2.15256, -0.84787, -0.30469, + -0.48422, 2.28962, -0.80541, + -0.15113, -0.53014, 1.68127 + ] + }, + { + "ct": 5600, + "ccm": + [ + 2.04576, -0.74771, -0.29805, + -0.36332, 1.98993, -0.62662, + -0.09328, -0.46543, 1.55871 + ] + }, + { + "ct": 7400, + "ccm": + [ + 2.37532, -0.83069, -0.54462, + -0.48279, 2.84309, -1.36031, + -0.21178, -0.66532, 1.87709 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.06, + "strength": 0.5, + "limit": 0.5 + } + }, + { + "rpi.cac": + { + "lut_rx": + [ + -0.15, -0.12, -0.08, -0.03, 0.02, 0.06, 0.11, 0.14, 0.22, + -0.15, -0.12, -0.08, -0.04, 0.01, 0.05, 0.1, 0.14, 0.21, + -0.15, -0.12, -0.08, -0.04, 0.01, 0.06, 0.1, 0.14, 0.21, + -0.14, -0.11, -0.08, -0.04, 0.01, 0.05, 0.1, 0.13, 0.2, + -0.13, -0.11, -0.08, -0.03, 0.01, 0.05, 0.09, 0.13, 0.2, + -0.14, -0.11, -0.07, -0.03, 0.01, 0.06, 0.09, 0.14, 0.21, + -0.14, -0.11, -0.07, -0.03, 0.01, 0.05, 0.09, 0.13, 0.21, + -0.14, -0.11, -0.07, -0.03, 0.01, 0.05, 0.09, 0.13, 0.2, + -0.14, -0.1, -0.07, -0.03, 0.01, 0.06, 0.09, 0.13, 0.2 + ], + "lut_ry": + [ + -0.13, -0.13, -0.12, -0.13, -0.13, -0.14, -0.14, -0.13, -0.13, + -0.1, -0.1, -0.1, -0.1, -0.11, -0.11, -0.11, -0.11, -0.1, + -0.08, -0.08, -0.09, -0.09, -0.1, -0.09, -0.09, -0.1, -0.09, + -0.07, -0.06, -0.06, -0.07, -0.07, -0.07, -0.07, -0.07, -0.09, + -0.04, -0.03, -0.04, -0.04, -0.04, -0.04, -0.05, -0.04, -0.06, + -0.02, -0.01, -0.01, -0.02, -0.02, -0.02, -0.02, -0.02, -0.03, + -0.0, 0.01, 0.0, -0.0, -0.01, -0.01, -0.0, 0.0, -0.0, + 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.02, 0.02, + 0.04, 0.05, 0.04, 0.03, 0.03, 0.03, 0.03, 0.04, 0.04 + ], + "lut_bx": + [ + -0.35, -0.28, -0.22, -0.13, -0.05, 0.02, 0.1, 0.16, 0.28, + -0.32, -0.25, -0.19, -0.12, -0.05, 0.02, 0.09, 0.16, 0.28, + -0.32, -0.26, -0.19, -0.12, -0.05, 0.02, 0.09, 0.15, 0.28, + -0.32, -0.25, -0.19, -0.11, -0.05, 0.02, 0.09, 0.16, 0.28, + -0.3, -0.25, -0.19, -0.11, -0.04, 0.02, 0.09, 0.16, 0.28, + -0.3, -0.25, -0.18, -0.11, -0.05, 0.02, 0.09, 0.15, 0.28, + -0.3, -0.25, -0.19, -0.11, -0.05, 0.02, 0.09, 0.15, 0.27, + -0.3, -0.24, -0.17, -0.11, -0.04, 0.02, 0.09, 0.15, 0.27, + -0.27, -0.21, -0.15, -0.09, -0.03, 0.03, 0.09, 0.15, 0.27 + ], + "lut_by": + [ + -0.23, -0.22, -0.22, -0.21, -0.21, -0.21, -0.21, -0.21, -0.23, + -0.19, -0.17, -0.17, -0.17, -0.17, -0.17, -0.17, -0.17, -0.19, + -0.16, -0.13, -0.13, -0.13, -0.12, -0.13, -0.12, -0.13, -0.15, + -0.11, -0.08, -0.08, -0.08, -0.07, -0.08, -0.08, -0.08, -0.1, + -0.07, -0.04, -0.04, -0.04, -0.03, -0.03, -0.04, -0.04, -0.07, + -0.02, 0.01, 0.01, 0.01, 0.02, 0.02, 0.01, 0.01, -0.02, + 0.03, 0.07, 0.07, 0.07, 0.07, 0.07, 0.06, 0.06, 0.05, + 0.09, 0.1, 0.1, 0.1, 0.12, 0.12, 0.11, 0.11, 0.09, + 0.13, 0.13, 0.13, 0.14, 0.18, 0.2, 0.19, 0.18, 0.16 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx296_6mm.json b/src/ipa/rpi/pisp/data/imx296_6mm.json new file mode 100644 index 00000000..abbcaa83 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx296_6mm.json @@ -0,0 +1,1247 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 4724, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 860, + "reference_Y": 14551 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.751 + } + }, + { + "rpi.geq": + { + "offset": 226, + "slope": 0.01032 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2875.0, 0.4699, 0.3209, + 3610.0, 0.4089, 0.4265, + 4640.0, 0.3281, 0.5417, + 5912.0, 0.2992, 0.5771, + 7630.0, 0.2285, 0.6524 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01783, + "transverse_neg": 0.02154 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.084, 2.084, 2.085, 2.085, 2.085, 2.087, 2.088, 2.087, 2.086, 2.082, 2.082, 2.084, 2.086, 2.088, 2.088, 2.088, 2.087, 2.088, 2.088, 2.091, 2.092, 2.093, 2.093, 2.093, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.091, 2.088, + 2.086, 2.086, 2.087, 2.088, 2.089, 2.089, 2.091, 2.089, 2.087, 2.086, 2.087, 2.088, 2.091, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.093, 2.093, 2.094, 2.095, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.093, + 2.087, 2.087, 2.088, 2.091, 2.091, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.096, + 2.089, 2.088, 2.089, 2.091, 2.091, 2.092, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.092, 2.092, 2.092, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.096, 2.096, 2.096, 2.096, 2.097, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.091, 2.091, 2.089, 2.088, 2.088, 2.089, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.094, 2.095, 2.095, 2.096, 2.096, 2.097, 2.099, 2.098, 2.097, 2.097, 2.097, + 2.091, 2.091, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.099, 2.098, 2.097, + 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.095, 2.096, 2.096, 2.097, 2.097, 2.099, 2.099, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.094, 2.095, 2.096, 2.097, 2.098, 2.098, 2.098, 2.101, 2.101, 2.099, 2.098, + 2.092, 2.092, 2.093, 2.093, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.089, 2.089, 2.091, 2.092, 2.092, 2.092, 2.092, 2.094, 2.096, 2.096, 2.097, 2.098, 2.099, 2.099, 2.099, 2.099, 2.099, 2.097, + 2.093, 2.094, 2.094, 2.094, 2.095, 2.093, 2.092, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.094, 2.096, 2.096, 2.097, 2.098, 2.098, 2.101, 2.101, 2.099, 2.099, 2.099, + 2.094, 2.094, 2.094, 2.095, 2.095, 2.095, 2.091, 2.089, 2.091, 2.089, 2.089, 2.089, 2.091, 2.091, 2.089, 2.091, 2.091, 2.091, 2.092, 2.092, 2.093, 2.093, 2.095, 2.096, 2.097, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.099, + 2.095, 2.094, 2.094, 2.095, 2.096, 2.095, 2.091, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.093, 2.094, 2.096, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.101, 2.099, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.095, 2.092, 2.089, 2.089, 2.088, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.093, 2.093, 2.095, 2.096, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.101, + 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.092, 2.091, 2.089, 2.089, 2.089, 2.089, 2.091, 2.091, 2.091, 2.093, 2.093, 2.093, 2.092, 2.092, 2.094, 2.094, 2.094, 2.096, 2.096, 2.098, 2.099, 2.102, 2.103, 2.103, 2.102, 2.102, + 2.095, 2.095, 2.095, 2.096, 2.096, 2.094, 2.093, 2.091, 2.091, 2.089, 2.089, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.092, 2.093, 2.094, 2.094, 2.095, 2.096, 2.097, 2.098, 2.099, 2.103, 2.103, 2.103, 2.101, 2.101, + 2.095, 2.096, 2.096, 2.097, 2.096, 2.095, 2.093, 2.092, 2.091, 2.091, 2.091, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.096, 2.097, 2.099, 2.101, 2.103, 2.103, 2.103, 2.101, 2.099, + 2.096, 2.096, 2.097, 2.096, 2.097, 2.096, 2.094, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.099, 2.101, 2.103, 2.103, 2.102, 2.101, 2.101, + 2.096, 2.096, 2.097, 2.097, 2.097, 2.096, 2.094, 2.093, 2.092, 2.092, 2.091, 2.091, 2.092, 2.092, 2.092, 2.093, 2.093, 2.094, 2.093, 2.093, 2.094, 2.095, 2.096, 2.097, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, + 2.097, 2.096, 2.097, 2.097, 2.097, 2.097, 2.095, 2.093, 2.093, 2.093, 2.093, 2.092, 2.091, 2.091, 2.092, 2.092, 2.093, 2.094, 2.093, 2.093, 2.093, 2.095, 2.096, 2.097, 2.099, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.101, + 2.098, 2.097, 2.096, 2.097, 2.097, 2.097, 2.095, 2.094, 2.094, 2.094, 2.092, 2.092, 2.092, 2.092, 2.092, 2.092, 2.094, 2.095, 2.095, 2.094, 2.093, 2.095, 2.096, 2.099, 2.101, 2.101, 2.102, 2.102, 2.102, 2.101, 2.101, 2.102, + 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.095, 2.094, 2.095, 2.093, 2.093, 2.092, 2.092, 2.092, 2.094, 2.094, 2.096, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.099, 2.099, 2.101, 2.102, 2.103, 2.102, 2.102, 2.101, 2.102, + 2.098, 2.097, 2.097, 2.098, 2.097, 2.096, 2.095, 2.095, 2.095, 2.094, 2.093, 2.093, 2.094, 2.094, 2.094, 2.095, 2.096, 2.096, 2.096, 2.095, 2.097, 2.097, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.104, 2.103, 2.102, 2.101, + 2.099, 2.098, 2.098, 2.098, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.094, 2.094, 2.094, 2.094, 2.096, 2.097, 2.097, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.101, 2.101, 2.104, 2.105, 2.105, 2.103, 2.102, + 2.101, 2.099, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.096, 2.096, 2.095, 2.095, 2.095, 2.095, 2.095, 2.096, 2.098, 2.098, 2.097, 2.097, 2.098, 2.099, 2.101, 2.101, 2.102, 2.103, 2.104, 2.105, 2.105, 2.104, 2.103, + 2.102, 2.102, 2.099, 2.098, 2.099, 2.099, 2.099, 2.098, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.104, 2.105, 2.106, 2.106, 2.106, 2.104, 2.104, 2.104, + 2.102, 2.101, 2.099, 2.099, 2.099, 2.101, 2.101, 2.101, 2.099, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.098, 2.099, 2.099, 2.099, 2.099, 2.101, 2.101, 2.102, 2.103, 2.105, 2.106, 2.106, 2.106, 2.106, 2.105, 2.104, 2.104, + 2.099, 2.099, 2.099, 2.098, 2.098, 2.099, 2.101, 2.101, 2.099, 2.098, 2.097, 2.098, 2.098, 2.099, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.105, 2.105, 2.106, 2.106, 2.104, 2.104, 2.103, + 2.096, 2.097, 2.097, 2.097, 2.097, 2.099, 2.099, 2.099, 2.099, 2.097, 2.097, 2.098, 2.098, 2.099, 2.098, 2.097, 2.097, 2.099, 2.101, 2.101, 2.101, 2.101, 2.101, 2.103, 2.105, 2.105, 2.105, 2.104, 2.104, 2.103, 2.101, 2.101, + 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.098, 2.099, 2.097, 2.096, 2.096, 2.097, 2.098, 2.098, 2.097, 2.097, 2.096, 2.098, 2.098, 2.099, 2.101, 2.101, 2.101, 2.102, 2.104, 2.105, 2.104, 2.104, 2.103, 2.101, 2.099, 2.098, + 2.096, 2.096, 2.096, 2.096, 2.097, 2.097, 2.097, 2.097, 2.097, 2.097, 2.096, 2.097, 2.098, 2.097, 2.097, 2.096, 2.096, 2.098, 2.098, 2.098, 2.099, 2.099, 2.101, 2.101, 2.103, 2.103, 2.104, 2.104, 2.102, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.096, 2.098, 2.098, 2.098, 2.098, 2.097, 2.098, 2.097, 2.097, 2.097, 2.097, 2.096, 2.096, 2.096, 2.097, 2.097, 2.098, 2.099, 2.099, 2.099, 2.101, 2.102, 2.103, 2.104, 2.104, 2.104, 2.101, 2.099, 2.098, + 2.097, 2.096, 2.095, 2.097, 2.099, 2.099, 2.099, 2.099, 2.099, 2.099, 2.098, 2.098, 2.097, 2.096, 2.096, 2.097, 2.097, 2.098, 2.097, 2.099, 2.101, 2.099, 2.099, 2.099, 2.102, 2.102, 2.104, 2.105, 2.105, 2.102, 2.099, 2.098 + ] + }, + { + "ct": 5000, + "table": + [ + 3.431, 3.437, 3.439, 3.439, 3.436, 3.438, 3.441, 3.441, 3.441, 3.441, 3.442, 3.443, 3.443, 3.444, 3.446, 3.448, 3.451, 3.451, 3.452, 3.451, 3.449, 3.449, 3.452, 3.453, 3.454, 3.454, 3.453, 3.456, 3.456, 3.456, 3.451, 3.448, + 3.445, 3.446, 3.445, 3.449, 3.453, 3.451, 3.451, 3.446, 3.447, 3.446, 3.447, 3.451, 3.453, 3.455, 3.454, 3.453, 3.453, 3.454, 3.455, 3.456, 3.457, 3.459, 3.461, 3.462, 3.463, 3.463, 3.465, 3.466, 3.467, 3.465, 3.459, 3.457, + 3.449, 3.449, 3.449, 3.454, 3.455, 3.454, 3.453, 3.451, 3.451, 3.448, 3.451, 3.451, 3.455, 3.456, 3.457, 3.456, 3.456, 3.458, 3.457, 3.459, 3.459, 3.461, 3.464, 3.467, 3.467, 3.466, 3.468, 3.469, 3.471, 3.468, 3.465, 3.462, + 3.451, 3.448, 3.451, 3.453, 3.457, 3.455, 3.454, 3.449, 3.449, 3.448, 3.449, 3.449, 3.455, 3.455, 3.456, 3.455, 3.454, 3.455, 3.455, 3.457, 3.458, 3.458, 3.461, 3.464, 3.466, 3.468, 3.469, 3.469, 3.469, 3.468, 3.465, 3.463, + 3.449, 3.449, 3.451, 3.453, 3.456, 3.455, 3.452, 3.449, 3.448, 3.447, 3.446, 3.448, 3.451, 3.452, 3.454, 3.455, 3.455, 3.454, 3.457, 3.458, 3.458, 3.459, 3.461, 3.464, 3.464, 3.466, 3.467, 3.469, 3.469, 3.467, 3.463, 3.459, + 3.449, 3.451, 3.452, 3.454, 3.455, 3.454, 3.452, 3.449, 3.447, 3.447, 3.446, 3.449, 3.449, 3.451, 3.452, 3.452, 3.452, 3.452, 3.454, 3.455, 3.457, 3.459, 3.461, 3.464, 3.464, 3.466, 3.465, 3.468, 3.468, 3.469, 3.465, 3.462, + 3.451, 3.451, 3.452, 3.453, 3.453, 3.453, 3.451, 3.449, 3.449, 3.447, 3.446, 3.447, 3.448, 3.451, 3.451, 3.451, 3.453, 3.452, 3.452, 3.452, 3.457, 3.458, 3.461, 3.463, 3.464, 3.465, 3.464, 3.466, 3.468, 3.469, 3.466, 3.463, + 3.451, 3.451, 3.451, 3.454, 3.453, 3.453, 3.451, 3.448, 3.448, 3.444, 3.444, 3.444, 3.448, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.452, 3.454, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.467, 3.468, 3.469, 3.466, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.454, 3.453, 3.449, 3.448, 3.447, 3.447, 3.444, 3.446, 3.446, 3.446, 3.446, 3.447, 3.449, 3.449, 3.451, 3.452, 3.455, 3.457, 3.461, 3.462, 3.464, 3.466, 3.466, 3.468, 3.469, 3.468, 3.465, 3.462, + 3.453, 3.452, 3.454, 3.456, 3.455, 3.453, 3.449, 3.447, 3.446, 3.446, 3.445, 3.448, 3.447, 3.446, 3.445, 3.446, 3.448, 3.448, 3.449, 3.453, 3.455, 3.457, 3.459, 3.461, 3.464, 3.466, 3.467, 3.468, 3.468, 3.467, 3.465, 3.463, + 3.453, 3.453, 3.454, 3.456, 3.456, 3.451, 3.448, 3.447, 3.447, 3.446, 3.445, 3.446, 3.446, 3.446, 3.446, 3.446, 3.448, 3.448, 3.449, 3.452, 3.454, 3.456, 3.459, 3.459, 3.461, 3.465, 3.466, 3.468, 3.468, 3.468, 3.467, 3.465, + 3.451, 3.451, 3.452, 3.455, 3.456, 3.452, 3.448, 3.446, 3.446, 3.444, 3.446, 3.445, 3.446, 3.446, 3.447, 3.448, 3.449, 3.449, 3.449, 3.452, 3.453, 3.454, 3.458, 3.458, 3.461, 3.461, 3.464, 3.469, 3.469, 3.468, 3.466, 3.466, + 3.452, 3.452, 3.453, 3.454, 3.454, 3.453, 3.447, 3.446, 3.444, 3.444, 3.444, 3.444, 3.445, 3.446, 3.448, 3.451, 3.452, 3.453, 3.451, 3.453, 3.453, 3.455, 3.458, 3.459, 3.461, 3.462, 3.463, 3.468, 3.471, 3.469, 3.467, 3.467, + 3.454, 3.455, 3.457, 3.458, 3.458, 3.455, 3.449, 3.446, 3.445, 3.445, 3.445, 3.445, 3.447, 3.447, 3.448, 3.451, 3.452, 3.453, 3.452, 3.452, 3.452, 3.454, 3.457, 3.459, 3.459, 3.462, 3.464, 3.468, 3.469, 3.467, 3.465, 3.465, + 3.457, 3.455, 3.455, 3.459, 3.458, 3.454, 3.451, 3.448, 3.445, 3.445, 3.445, 3.446, 3.448, 3.449, 3.451, 3.452, 3.451, 3.453, 3.452, 3.452, 3.453, 3.457, 3.457, 3.461, 3.461, 3.463, 3.465, 3.468, 3.471, 3.468, 3.465, 3.463, + 3.458, 3.456, 3.456, 3.459, 3.457, 3.454, 3.452, 3.449, 3.447, 3.445, 3.446, 3.447, 3.447, 3.448, 3.449, 3.448, 3.449, 3.451, 3.451, 3.451, 3.451, 3.455, 3.456, 3.458, 3.462, 3.463, 3.464, 3.465, 3.467, 3.466, 3.464, 3.462, + 3.457, 3.456, 3.455, 3.457, 3.457, 3.454, 3.449, 3.447, 3.445, 3.445, 3.446, 3.446, 3.448, 3.446, 3.448, 3.449, 3.449, 3.451, 3.451, 3.451, 3.453, 3.455, 3.457, 3.459, 3.462, 3.464, 3.464, 3.465, 3.467, 3.464, 3.464, 3.463, + 3.458, 3.457, 3.455, 3.456, 3.456, 3.456, 3.453, 3.449, 3.447, 3.448, 3.447, 3.447, 3.447, 3.447, 3.447, 3.448, 3.449, 3.451, 3.451, 3.452, 3.453, 3.455, 3.458, 3.459, 3.459, 3.463, 3.464, 3.463, 3.464, 3.463, 3.464, 3.464, + 3.457, 3.456, 3.456, 3.456, 3.456, 3.456, 3.455, 3.449, 3.447, 3.448, 3.451, 3.449, 3.449, 3.449, 3.448, 3.449, 3.449, 3.451, 3.451, 3.452, 3.453, 3.456, 3.458, 3.459, 3.461, 3.462, 3.464, 3.464, 3.465, 3.464, 3.464, 3.463, + 3.457, 3.456, 3.455, 3.455, 3.455, 3.455, 3.453, 3.451, 3.449, 3.448, 3.448, 3.449, 3.449, 3.449, 3.448, 3.449, 3.451, 3.452, 3.452, 3.453, 3.454, 3.457, 3.458, 3.459, 3.462, 3.464, 3.465, 3.464, 3.465, 3.464, 3.463, 3.463, + 3.456, 3.456, 3.454, 3.453, 3.454, 3.453, 3.452, 3.451, 3.449, 3.448, 3.448, 3.449, 3.451, 3.451, 3.448, 3.449, 3.451, 3.454, 3.454, 3.454, 3.455, 3.457, 3.458, 3.461, 3.461, 3.462, 3.464, 3.464, 3.466, 3.465, 3.464, 3.464, + 3.459, 3.457, 3.456, 3.455, 3.454, 3.453, 3.453, 3.452, 3.452, 3.451, 3.449, 3.449, 3.449, 3.448, 3.447, 3.449, 3.451, 3.454, 3.455, 3.455, 3.456, 3.458, 3.459, 3.461, 3.461, 3.462, 3.463, 3.466, 3.469, 3.465, 3.465, 3.464, + 3.463, 3.461, 3.458, 3.458, 3.457, 3.456, 3.456, 3.454, 3.454, 3.452, 3.452, 3.451, 3.451, 3.449, 3.448, 3.448, 3.452, 3.454, 3.456, 3.455, 3.457, 3.458, 3.461, 3.464, 3.462, 3.461, 3.463, 3.466, 3.469, 3.469, 3.467, 3.467, + 3.466, 3.462, 3.461, 3.461, 3.459, 3.457, 3.457, 3.457, 3.456, 3.454, 3.455, 3.455, 3.455, 3.451, 3.452, 3.453, 3.454, 3.455, 3.456, 3.456, 3.459, 3.462, 3.463, 3.466, 3.466, 3.467, 3.466, 3.469, 3.471, 3.469, 3.468, 3.466, + 3.467, 3.463, 3.463, 3.459, 3.461, 3.459, 3.461, 3.459, 3.458, 3.456, 3.457, 3.456, 3.457, 3.455, 3.456, 3.455, 3.456, 3.457, 3.459, 3.461, 3.461, 3.464, 3.465, 3.468, 3.469, 3.469, 3.469, 3.469, 3.471, 3.468, 3.467, 3.468, + 3.467, 3.464, 3.459, 3.459, 3.462, 3.462, 3.462, 3.461, 3.461, 3.462, 3.461, 3.459, 3.461, 3.459, 3.458, 3.457, 3.459, 3.461, 3.462, 3.463, 3.464, 3.466, 3.468, 3.469, 3.471, 3.469, 3.471, 3.472, 3.471, 3.467, 3.466, 3.464, + 3.464, 3.462, 3.458, 3.457, 3.458, 3.461, 3.461, 3.461, 3.461, 3.462, 3.462, 3.461, 3.461, 3.459, 3.459, 3.459, 3.461, 3.461, 3.464, 3.465, 3.465, 3.468, 3.468, 3.469, 3.471, 3.469, 3.469, 3.469, 3.469, 3.464, 3.462, 3.459, + 3.457, 3.458, 3.455, 3.456, 3.456, 3.457, 3.459, 3.459, 3.459, 3.459, 3.458, 3.456, 3.458, 3.457, 3.458, 3.458, 3.458, 3.459, 3.461, 3.463, 3.465, 3.466, 3.468, 3.469, 3.471, 3.468, 3.466, 3.466, 3.465, 3.461, 3.459, 3.457, + 3.456, 3.455, 3.454, 3.454, 3.455, 3.456, 3.458, 3.459, 3.459, 3.456, 3.456, 3.456, 3.455, 3.456, 3.455, 3.455, 3.455, 3.454, 3.457, 3.461, 3.462, 3.464, 3.465, 3.467, 3.467, 3.466, 3.464, 3.464, 3.463, 3.461, 3.457, 3.456, + 3.456, 3.454, 3.453, 3.454, 3.454, 3.455, 3.458, 3.459, 3.459, 3.456, 3.455, 3.455, 3.455, 3.451, 3.453, 3.454, 3.454, 3.455, 3.455, 3.458, 3.461, 3.462, 3.461, 3.463, 3.465, 3.464, 3.463, 3.463, 3.462, 3.459, 3.456, 3.451, + 3.455, 3.452, 3.452, 3.452, 3.455, 3.457, 3.459, 3.459, 3.459, 3.458, 3.456, 3.456, 3.455, 3.453, 3.453, 3.455, 3.457, 3.457, 3.457, 3.461, 3.461, 3.461, 3.459, 3.462, 3.464, 3.464, 3.464, 3.463, 3.463, 3.459, 3.454, 3.451, + 3.452, 3.452, 3.452, 3.453, 3.457, 3.458, 3.458, 3.459, 3.459, 3.458, 3.457, 3.457, 3.455, 3.455, 3.458, 3.459, 3.458, 3.459, 3.459, 3.461, 3.461, 3.461, 3.459, 3.461, 3.463, 3.464, 3.466, 3.463, 3.461, 3.458, 3.453, 3.449 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.403, 3.399, 3.395, 3.391, 3.392, 3.394, 3.401, 3.403, 3.404, 3.404, 3.403, 3.399, 3.398, 3.396, 3.395, 3.396, 3.399, 3.403, 3.404, 3.401, 3.399, 3.398, 3.397, 3.401, 3.401, 3.401, 3.396, 3.394, 3.397, 3.396, 3.388, 3.364, + 3.403, 3.399, 3.393, 3.389, 3.391, 3.395, 3.401, 3.404, 3.406, 3.404, 3.403, 3.399, 3.399, 3.397, 3.397, 3.397, 3.401, 3.404, 3.404, 3.402, 3.398, 3.396, 3.397, 3.401, 3.401, 3.401, 3.395, 3.394, 3.396, 3.393, 3.387, 3.364, + 3.399, 3.398, 3.391, 3.385, 3.386, 3.395, 3.402, 3.405, 3.405, 3.404, 3.402, 3.399, 3.399, 3.398, 3.398, 3.398, 3.401, 3.404, 3.405, 3.403, 3.399, 3.396, 3.396, 3.398, 3.401, 3.401, 3.398, 3.394, 3.392, 3.389, 3.386, 3.364, + 3.398, 3.393, 3.386, 3.382, 3.385, 3.392, 3.399, 3.403, 3.405, 3.404, 3.402, 3.398, 3.398, 3.397, 3.397, 3.398, 3.401, 3.404, 3.405, 3.403, 3.398, 3.394, 3.394, 3.398, 3.401, 3.401, 3.396, 3.392, 3.391, 3.388, 3.383, 3.362, + 3.396, 3.391, 3.384, 3.381, 3.384, 3.389, 3.398, 3.402, 3.402, 3.401, 3.399, 3.395, 3.395, 3.395, 3.397, 3.397, 3.401, 3.402, 3.404, 3.403, 3.399, 3.394, 3.393, 3.395, 3.399, 3.399, 3.397, 3.391, 3.388, 3.384, 3.381, 3.363, + 3.391, 3.386, 3.382, 3.381, 3.385, 3.389, 3.396, 3.398, 3.399, 3.399, 3.398, 3.395, 3.394, 3.394, 3.395, 3.397, 3.399, 3.401, 3.403, 3.401, 3.398, 3.394, 3.393, 3.393, 3.394, 3.396, 3.395, 3.392, 3.387, 3.382, 3.378, 3.361, + 3.389, 3.386, 3.379, 3.379, 3.383, 3.388, 3.394, 3.397, 3.397, 3.397, 3.395, 3.393, 3.393, 3.393, 3.395, 3.395, 3.397, 3.398, 3.401, 3.399, 3.397, 3.395, 3.394, 3.391, 3.393, 3.393, 3.393, 3.389, 3.387, 3.381, 3.374, 3.357, + 3.386, 3.383, 3.376, 3.375, 3.381, 3.386, 3.394, 3.396, 3.396, 3.394, 3.392, 3.392, 3.394, 3.394, 3.395, 3.394, 3.396, 3.398, 3.399, 3.397, 3.397, 3.394, 3.393, 3.391, 3.389, 3.391, 3.392, 3.388, 3.386, 3.379, 3.372, 3.355, + 3.386, 3.379, 3.373, 3.373, 3.378, 3.384, 3.391, 3.396, 3.395, 3.393, 3.389, 3.391, 3.391, 3.393, 3.394, 3.393, 3.394, 3.396, 3.397, 3.396, 3.393, 3.394, 3.393, 3.392, 3.389, 3.389, 3.389, 3.389, 3.386, 3.378, 3.371, 3.351, + 3.379, 3.375, 3.371, 3.371, 3.376, 3.381, 3.388, 3.393, 3.394, 3.391, 3.386, 3.386, 3.388, 3.393, 3.392, 3.392, 3.393, 3.395, 3.394, 3.392, 3.389, 3.391, 3.391, 3.392, 3.389, 3.388, 3.389, 3.389, 3.383, 3.377, 3.369, 3.351, + 3.373, 3.371, 3.367, 3.368, 3.373, 3.381, 3.387, 3.389, 3.391, 3.389, 3.385, 3.386, 3.383, 3.389, 3.389, 3.392, 3.392, 3.394, 3.393, 3.389, 3.387, 3.387, 3.388, 3.389, 3.389, 3.388, 3.386, 3.386, 3.382, 3.374, 3.367, 3.345, + 3.371, 3.369, 3.365, 3.366, 3.373, 3.379, 3.386, 3.389, 3.391, 3.389, 3.385, 3.384, 3.382, 3.386, 3.387, 3.389, 3.391, 3.392, 3.391, 3.387, 3.385, 3.385, 3.386, 3.388, 3.388, 3.388, 3.386, 3.385, 3.381, 3.373, 3.367, 3.345, + 3.367, 3.365, 3.365, 3.366, 3.374, 3.379, 3.384, 3.388, 3.389, 3.387, 3.384, 3.383, 3.383, 3.385, 3.385, 3.386, 3.388, 3.389, 3.388, 3.386, 3.383, 3.382, 3.384, 3.386, 3.387, 3.386, 3.381, 3.381, 3.379, 3.372, 3.364, 3.344, + 3.365, 3.363, 3.362, 3.367, 3.375, 3.379, 3.383, 3.384, 3.386, 3.384, 3.381, 3.379, 3.379, 3.383, 3.383, 3.384, 3.385, 3.387, 3.387, 3.385, 3.381, 3.381, 3.382, 3.384, 3.384, 3.385, 3.382, 3.379, 3.374, 3.369, 3.359, 3.343, + 3.359, 3.358, 3.361, 3.364, 3.373, 3.381, 3.384, 3.384, 3.385, 3.384, 3.381, 3.377, 3.379, 3.379, 3.382, 3.383, 3.384, 3.386, 3.386, 3.385, 3.381, 3.379, 3.381, 3.382, 3.382, 3.383, 3.379, 3.377, 3.371, 3.364, 3.357, 3.339, + 3.357, 3.356, 3.356, 3.362, 3.372, 3.379, 3.384, 3.384, 3.383, 3.381, 3.378, 3.376, 3.377, 3.379, 3.381, 3.382, 3.383, 3.385, 3.385, 3.383, 3.379, 3.379, 3.379, 3.381, 3.381, 3.382, 3.379, 3.372, 3.367, 3.362, 3.354, 3.334, + 3.357, 3.354, 3.357, 3.361, 3.372, 3.381, 3.385, 3.385, 3.384, 3.379, 3.376, 3.376, 3.376, 3.379, 3.381, 3.383, 3.383, 3.384, 3.383, 3.379, 3.378, 3.381, 3.379, 3.379, 3.379, 3.379, 3.378, 3.371, 3.363, 3.358, 3.354, 3.332, + 3.354, 3.351, 3.354, 3.359, 3.371, 3.379, 3.382, 3.384, 3.381, 3.378, 3.375, 3.374, 3.376, 3.378, 3.381, 3.383, 3.384, 3.382, 3.377, 3.377, 3.376, 3.377, 3.378, 3.378, 3.379, 3.379, 3.376, 3.367, 3.361, 3.357, 3.352, 3.333, + 3.352, 3.349, 3.351, 3.357, 3.372, 3.381, 3.383, 3.383, 3.381, 3.376, 3.372, 3.373, 3.375, 3.377, 3.382, 3.384, 3.384, 3.379, 3.376, 3.374, 3.374, 3.375, 3.375, 3.376, 3.377, 3.376, 3.373, 3.366, 3.361, 3.356, 3.347, 3.332, + 3.347, 3.346, 3.346, 3.355, 3.371, 3.377, 3.382, 3.381, 3.379, 3.372, 3.371, 3.371, 3.372, 3.375, 3.379, 3.383, 3.384, 3.379, 3.374, 3.373, 3.371, 3.373, 3.374, 3.375, 3.374, 3.374, 3.371, 3.365, 3.359, 3.352, 3.343, 3.331, + 3.345, 3.344, 3.345, 3.353, 3.367, 3.374, 3.382, 3.382, 3.376, 3.371, 3.369, 3.368, 3.369, 3.373, 3.377, 3.381, 3.379, 3.376, 3.373, 3.369, 3.368, 3.371, 3.372, 3.373, 3.371, 3.371, 3.369, 3.363, 3.357, 3.349, 3.341, 3.326, + 3.343, 3.341, 3.344, 3.351, 3.362, 3.371, 3.376, 3.376, 3.372, 3.369, 3.367, 3.366, 3.367, 3.369, 3.376, 3.378, 3.378, 3.375, 3.371, 3.367, 3.367, 3.368, 3.369, 3.369, 3.369, 3.368, 3.365, 3.361, 3.354, 3.347, 3.338, 3.321, + 3.341, 3.339, 3.342, 3.349, 3.359, 3.367, 3.371, 3.372, 3.371, 3.368, 3.366, 3.363, 3.365, 3.368, 3.371, 3.374, 3.376, 3.374, 3.368, 3.365, 3.365, 3.366, 3.368, 3.367, 3.367, 3.363, 3.361, 3.356, 3.352, 3.346, 3.336, 3.317, + 3.338, 3.336, 3.338, 3.346, 3.359, 3.364, 3.368, 3.369, 3.367, 3.366, 3.363, 3.362, 3.364, 3.364, 3.367, 3.371, 3.372, 3.369, 3.365, 3.362, 3.362, 3.365, 3.367, 3.367, 3.366, 3.362, 3.357, 3.353, 3.349, 3.342, 3.335, 3.317, + 3.334, 3.334, 3.336, 3.346, 3.354, 3.361, 3.365, 3.365, 3.365, 3.362, 3.361, 3.361, 3.362, 3.362, 3.364, 3.366, 3.368, 3.366, 3.361, 3.357, 3.357, 3.359, 3.363, 3.365, 3.363, 3.361, 3.355, 3.351, 3.346, 3.339, 3.336, 3.317, + 3.332, 3.332, 3.334, 3.344, 3.354, 3.359, 3.363, 3.365, 3.363, 3.361, 3.359, 3.359, 3.363, 3.363, 3.365, 3.365, 3.367, 3.366, 3.358, 3.356, 3.356, 3.358, 3.362, 3.364, 3.363, 3.359, 3.353, 3.348, 3.345, 3.339, 3.336, 3.315, + 3.332, 3.328, 3.331, 3.343, 3.351, 3.357, 3.358, 3.362, 3.361, 3.359, 3.357, 3.357, 3.361, 3.362, 3.364, 3.363, 3.363, 3.359, 3.356, 3.354, 3.354, 3.355, 3.358, 3.359, 3.361, 3.359, 3.351, 3.346, 3.344, 3.339, 3.336, 3.313, + 3.324, 3.324, 3.327, 3.334, 3.345, 3.351, 3.354, 3.356, 3.356, 3.354, 3.353, 3.354, 3.357, 3.358, 3.361, 3.358, 3.359, 3.355, 3.352, 3.348, 3.347, 3.351, 3.354, 3.358, 3.359, 3.355, 3.346, 3.343, 3.341, 3.336, 3.331, 3.312, + 3.318, 3.319, 3.321, 3.328, 3.337, 3.339, 3.345, 3.348, 3.346, 3.345, 3.347, 3.348, 3.351, 3.354, 3.356, 3.353, 3.354, 3.344, 3.343, 3.343, 3.343, 3.344, 3.347, 3.349, 3.353, 3.346, 3.341, 3.339, 3.331, 3.329, 3.325, 3.311, + 3.309, 3.313, 3.317, 3.325, 3.329, 3.332, 3.338, 3.339, 3.341, 3.339, 3.339, 3.342, 3.346, 3.346, 3.351, 3.351, 3.343, 3.338, 3.338, 3.339, 3.339, 3.339, 3.341, 3.341, 3.346, 3.343, 3.339, 3.332, 3.327, 3.326, 3.322, 3.309, + 3.305, 3.309, 3.317, 3.325, 3.328, 3.331, 3.334, 3.336, 3.337, 3.336, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.341, 3.336, 3.335, 3.337, 3.339, 3.341, 3.339, 3.339, 3.342, 3.341, 3.337, 3.329, 3.326, 3.325, 3.321, 3.314, + 3.302, 3.306, 3.319, 3.325, 3.329, 3.331, 3.334, 3.335, 3.337, 3.337, 3.339, 3.341, 3.344, 3.346, 3.348, 3.347, 3.342, 3.336, 3.336, 3.338, 3.339, 3.341, 3.341, 3.341, 3.339, 3.338, 3.336, 3.331, 3.327, 3.324, 3.321, 3.314 + ] + }, + { + "ct": 5000, + "table": + [ + 1.726, 1.725, 1.723, 1.721, 1.723, 1.724, 1.724, 1.726, 1.727, 1.728, 1.729, 1.728, 1.725, 1.724, 1.726, 1.726, 1.727, 1.729, 1.727, 1.727, 1.724, 1.725, 1.724, 1.726, 1.725, 1.725, 1.724, 1.724, 1.722, 1.721, 1.719, 1.714, + 1.726, 1.724, 1.722, 1.721, 1.722, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.727, 1.725, 1.724, 1.724, 1.725, 1.726, 1.725, 1.724, 1.723, 1.722, 1.721, 1.719, 1.714, + 1.724, 1.722, 1.719, 1.719, 1.721, 1.723, 1.726, 1.726, 1.727, 1.727, 1.727, 1.725, 1.726, 1.725, 1.725, 1.725, 1.726, 1.727, 1.728, 1.728, 1.725, 1.724, 1.724, 1.724, 1.726, 1.725, 1.724, 1.722, 1.722, 1.721, 1.719, 1.712, + 1.723, 1.721, 1.719, 1.719, 1.719, 1.723, 1.725, 1.726, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.726, 1.726, 1.728, 1.729, 1.728, 1.725, 1.723, 1.723, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.718, 1.711, + 1.722, 1.719, 1.719, 1.718, 1.719, 1.722, 1.725, 1.726, 1.726, 1.727, 1.727, 1.726, 1.725, 1.726, 1.726, 1.726, 1.727, 1.727, 1.728, 1.727, 1.726, 1.725, 1.724, 1.725, 1.726, 1.725, 1.724, 1.722, 1.721, 1.719, 1.715, 1.711, + 1.721, 1.717, 1.717, 1.716, 1.719, 1.722, 1.724, 1.726, 1.726, 1.727, 1.726, 1.726, 1.726, 1.726, 1.726, 1.727, 1.727, 1.727, 1.727, 1.727, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.722, 1.721, 1.718, 1.715, 1.707, + 1.718, 1.717, 1.716, 1.716, 1.718, 1.721, 1.725, 1.726, 1.726, 1.726, 1.725, 1.725, 1.725, 1.725, 1.726, 1.727, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.718, 1.715, 1.709, + 1.718, 1.716, 1.716, 1.715, 1.717, 1.721, 1.724, 1.725, 1.726, 1.725, 1.725, 1.724, 1.724, 1.725, 1.726, 1.726, 1.727, 1.727, 1.727, 1.726, 1.726, 1.726, 1.725, 1.723, 1.723, 1.723, 1.722, 1.722, 1.719, 1.718, 1.714, 1.709, + 1.718, 1.716, 1.715, 1.715, 1.717, 1.721, 1.723, 1.725, 1.726, 1.725, 1.724, 1.723, 1.724, 1.725, 1.725, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.726, 1.725, 1.724, 1.724, 1.723, 1.722, 1.722, 1.721, 1.717, 1.714, 1.707, + 1.717, 1.716, 1.714, 1.714, 1.716, 1.721, 1.723, 1.725, 1.725, 1.725, 1.723, 1.723, 1.724, 1.726, 1.726, 1.726, 1.726, 1.725, 1.726, 1.725, 1.725, 1.725, 1.725, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.706, + 1.715, 1.714, 1.714, 1.714, 1.716, 1.719, 1.722, 1.724, 1.725, 1.725, 1.723, 1.723, 1.724, 1.725, 1.725, 1.725, 1.726, 1.725, 1.725, 1.725, 1.724, 1.724, 1.724, 1.725, 1.724, 1.723, 1.722, 1.721, 1.718, 1.716, 1.713, 1.705, + 1.714, 1.714, 1.713, 1.714, 1.717, 1.719, 1.722, 1.724, 1.724, 1.724, 1.723, 1.722, 1.723, 1.724, 1.724, 1.724, 1.726, 1.725, 1.726, 1.725, 1.723, 1.723, 1.724, 1.724, 1.724, 1.723, 1.721, 1.719, 1.717, 1.715, 1.713, 1.706, + 1.712, 1.712, 1.712, 1.713, 1.718, 1.719, 1.721, 1.723, 1.724, 1.724, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.725, 1.725, 1.725, 1.725, 1.723, 1.722, 1.724, 1.723, 1.723, 1.722, 1.721, 1.719, 1.717, 1.714, 1.711, 1.706, + 1.712, 1.711, 1.711, 1.713, 1.717, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.722, 1.723, 1.724, 1.724, 1.724, 1.724, 1.725, 1.725, 1.724, 1.723, 1.722, 1.722, 1.722, 1.723, 1.722, 1.721, 1.718, 1.716, 1.714, 1.711, 1.706, + 1.711, 1.709, 1.711, 1.713, 1.716, 1.719, 1.722, 1.724, 1.724, 1.723, 1.722, 1.721, 1.722, 1.724, 1.724, 1.724, 1.723, 1.724, 1.724, 1.724, 1.722, 1.722, 1.722, 1.722, 1.722, 1.721, 1.719, 1.718, 1.714, 1.712, 1.709, 1.702, + 1.709, 1.709, 1.709, 1.712, 1.717, 1.719, 1.721, 1.723, 1.723, 1.723, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.724, 1.724, 1.724, 1.724, 1.723, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.716, 1.713, 1.711, 1.709, 1.701, + 1.708, 1.707, 1.709, 1.712, 1.716, 1.719, 1.722, 1.723, 1.723, 1.723, 1.721, 1.721, 1.721, 1.722, 1.723, 1.723, 1.723, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.719, 1.714, 1.712, 1.709, 1.708, 1.702, + 1.707, 1.707, 1.708, 1.711, 1.716, 1.721, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.722, 1.722, 1.723, 1.723, 1.723, 1.722, 1.722, 1.722, 1.722, 1.721, 1.721, 1.721, 1.721, 1.721, 1.717, 1.714, 1.711, 1.709, 1.707, 1.702, + 1.706, 1.706, 1.707, 1.711, 1.714, 1.719, 1.722, 1.722, 1.722, 1.721, 1.719, 1.721, 1.721, 1.722, 1.723, 1.724, 1.723, 1.722, 1.722, 1.721, 1.719, 1.719, 1.721, 1.721, 1.719, 1.719, 1.716, 1.713, 1.711, 1.709, 1.706, 1.701, + 1.705, 1.704, 1.706, 1.709, 1.713, 1.718, 1.721, 1.722, 1.721, 1.719, 1.718, 1.719, 1.721, 1.722, 1.723, 1.724, 1.724, 1.721, 1.721, 1.721, 1.719, 1.719, 1.719, 1.719, 1.719, 1.717, 1.715, 1.713, 1.711, 1.707, 1.704, 1.699, + 1.703, 1.703, 1.704, 1.709, 1.712, 1.717, 1.719, 1.721, 1.719, 1.718, 1.717, 1.718, 1.719, 1.721, 1.722, 1.723, 1.723, 1.722, 1.719, 1.719, 1.718, 1.719, 1.719, 1.718, 1.717, 1.716, 1.714, 1.712, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.703, 1.704, 1.708, 1.712, 1.715, 1.718, 1.719, 1.719, 1.717, 1.717, 1.717, 1.717, 1.718, 1.721, 1.722, 1.722, 1.721, 1.719, 1.718, 1.717, 1.718, 1.718, 1.717, 1.716, 1.714, 1.714, 1.711, 1.709, 1.706, 1.703, 1.697, + 1.702, 1.702, 1.703, 1.706, 1.709, 1.715, 1.717, 1.718, 1.717, 1.717, 1.716, 1.716, 1.717, 1.717, 1.719, 1.721, 1.721, 1.721, 1.719, 1.717, 1.716, 1.717, 1.717, 1.716, 1.714, 1.713, 1.712, 1.711, 1.708, 1.706, 1.702, 1.696, + 1.701, 1.701, 1.702, 1.706, 1.709, 1.714, 1.716, 1.717, 1.716, 1.716, 1.716, 1.715, 1.716, 1.716, 1.717, 1.718, 1.719, 1.719, 1.716, 1.715, 1.715, 1.715, 1.715, 1.715, 1.714, 1.713, 1.711, 1.709, 1.708, 1.704, 1.701, 1.695, + 1.699, 1.699, 1.702, 1.706, 1.708, 1.712, 1.714, 1.715, 1.715, 1.715, 1.714, 1.715, 1.714, 1.715, 1.716, 1.716, 1.716, 1.716, 1.714, 1.713, 1.713, 1.714, 1.715, 1.714, 1.714, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.695, + 1.698, 1.699, 1.701, 1.705, 1.708, 1.711, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.714, 1.715, 1.715, 1.716, 1.716, 1.715, 1.713, 1.713, 1.713, 1.714, 1.714, 1.714, 1.713, 1.712, 1.709, 1.707, 1.706, 1.703, 1.701, 1.696, + 1.698, 1.699, 1.701, 1.705, 1.707, 1.711, 1.712, 1.713, 1.713, 1.713, 1.713, 1.714, 1.714, 1.715, 1.715, 1.716, 1.715, 1.714, 1.713, 1.712, 1.712, 1.712, 1.713, 1.713, 1.713, 1.711, 1.709, 1.707, 1.705, 1.703, 1.701, 1.696, + 1.698, 1.697, 1.699, 1.702, 1.705, 1.707, 1.711, 1.711, 1.711, 1.711, 1.711, 1.712, 1.712, 1.713, 1.714, 1.714, 1.713, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.711, 1.708, 1.706, 1.704, 1.703, 1.699, 1.696, + 1.694, 1.695, 1.697, 1.699, 1.702, 1.705, 1.706, 1.707, 1.707, 1.708, 1.708, 1.708, 1.709, 1.711, 1.711, 1.711, 1.708, 1.708, 1.708, 1.707, 1.707, 1.707, 1.708, 1.708, 1.709, 1.708, 1.706, 1.703, 1.702, 1.701, 1.698, 1.696, + 1.692, 1.692, 1.695, 1.698, 1.699, 1.701, 1.704, 1.704, 1.704, 1.704, 1.705, 1.706, 1.707, 1.709, 1.709, 1.707, 1.706, 1.704, 1.704, 1.705, 1.705, 1.706, 1.706, 1.706, 1.706, 1.706, 1.703, 1.702, 1.701, 1.699, 1.696, 1.694, + 1.691, 1.692, 1.695, 1.697, 1.699, 1.699, 1.702, 1.703, 1.703, 1.702, 1.703, 1.704, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.704, 1.705, 1.705, 1.705, 1.705, 1.704, 1.703, 1.701, 1.699, 1.698, 1.696, 1.695, + 1.689, 1.691, 1.696, 1.698, 1.699, 1.699, 1.701, 1.702, 1.702, 1.702, 1.703, 1.703, 1.706, 1.707, 1.708, 1.706, 1.705, 1.703, 1.703, 1.703, 1.703, 1.704, 1.704, 1.705, 1.704, 1.704, 1.702, 1.701, 1.698, 1.698, 1.696, 1.696 + ] + } + ], + "luminance_lut": + [ + 1.425, 1.393, 1.341, 1.295, 1.258, 1.226, 1.201, 1.181, 1.162, 1.146, 1.133, 1.123, 1.115, 1.111, 1.107, 1.106, 1.106, 1.107, 1.108, 1.111, 1.114, 1.122, 1.133, 1.148, 1.164, 1.184, 1.208, 1.236, 1.271, 1.309, 1.359, 1.381, + 1.397, 1.367, 1.317, 1.274, 1.237, 1.207, 1.183, 1.163, 1.146, 1.133, 1.123, 1.114, 1.107, 1.101, 1.098, 1.096, 1.096, 1.096, 1.097, 1.102, 1.106, 1.112, 1.122, 1.133, 1.148, 1.166, 1.187, 1.215, 1.249, 1.288, 1.335, 1.359, + 1.374, 1.341, 1.292, 1.251, 1.215, 1.186, 1.166, 1.146, 1.131, 1.117, 1.108, 1.099, 1.091, 1.088, 1.084, 1.082, 1.081, 1.082, 1.084, 1.088, 1.093, 1.098, 1.107, 1.118, 1.133, 1.149, 1.169, 1.195, 1.228, 1.267, 1.313, 1.335, + 1.352, 1.318, 1.271, 1.231, 1.196, 1.169, 1.149, 1.131, 1.115, 1.103, 1.093, 1.086, 1.079, 1.074, 1.071, 1.069, 1.069, 1.069, 1.071, 1.076, 1.079, 1.085, 1.094, 1.102, 1.117, 1.133, 1.152, 1.176, 1.208, 1.246, 1.289, 1.313, + 1.333, 1.298, 1.253, 1.212, 1.179, 1.153, 1.134, 1.116, 1.102, 1.089, 1.079, 1.072, 1.066, 1.062, 1.059, 1.058, 1.057, 1.057, 1.059, 1.064, 1.068, 1.072, 1.081, 1.091, 1.102, 1.119, 1.137, 1.161, 1.191, 1.227, 1.271, 1.293, + 1.317, 1.281, 1.235, 1.196, 1.165, 1.139, 1.119, 1.104, 1.089, 1.078, 1.068, 1.062, 1.055, 1.051, 1.048, 1.047, 1.047, 1.047, 1.048, 1.053, 1.056, 1.061, 1.069, 1.079, 1.091, 1.105, 1.126, 1.147, 1.177, 1.212, 1.253, 1.278, + 1.301, 1.265, 1.221, 1.181, 1.151, 1.127, 1.108, 1.091, 1.078, 1.068, 1.059, 1.051, 1.045, 1.041, 1.038, 1.037, 1.036, 1.037, 1.038, 1.042, 1.046, 1.051, 1.059, 1.069, 1.081, 1.096, 1.113, 1.135, 1.164, 1.198, 1.238, 1.264, + 1.286, 1.251, 1.207, 1.169, 1.141, 1.116, 1.098, 1.081, 1.068, 1.058, 1.049, 1.042, 1.037, 1.033, 1.031, 1.029, 1.028, 1.028, 1.029, 1.033, 1.037, 1.043, 1.051, 1.059, 1.071, 1.086, 1.104, 1.124, 1.152, 1.185, 1.225, 1.252, + 1.275, 1.239, 1.196, 1.161, 1.132, 1.107, 1.089, 1.073, 1.059, 1.049, 1.041, 1.035, 1.028, 1.024, 1.023, 1.021, 1.021, 1.021, 1.022, 1.024, 1.029, 1.036, 1.043, 1.051, 1.063, 1.078, 1.095, 1.115, 1.143, 1.175, 1.214, 1.243, + 1.267, 1.227, 1.187, 1.152, 1.122, 1.101, 1.081, 1.067, 1.054, 1.042, 1.035, 1.028, 1.023, 1.018, 1.015, 1.014, 1.014, 1.014, 1.016, 1.019, 1.024, 1.029, 1.036, 1.045, 1.056, 1.071, 1.088, 1.107, 1.134, 1.167, 1.204, 1.234, + 1.261, 1.219, 1.179, 1.145, 1.116, 1.095, 1.076, 1.061, 1.047, 1.037, 1.031, 1.023, 1.018, 1.014, 1.011, 1.009, 1.009, 1.009, 1.011, 1.013, 1.018, 1.024, 1.031, 1.039, 1.049, 1.065, 1.083, 1.102, 1.128, 1.161, 1.196, 1.228, + 1.256, 1.213, 1.173, 1.139, 1.111, 1.091, 1.071, 1.056, 1.043, 1.033, 1.026, 1.019, 1.014, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.009, 1.013, 1.018, 1.026, 1.035, 1.046, 1.061, 1.078, 1.097, 1.123, 1.154, 1.191, 1.222, + 1.251, 1.208, 1.169, 1.137, 1.108, 1.088, 1.069, 1.053, 1.039, 1.029, 1.023, 1.015, 1.011, 1.006, 1.004, 1.003, 1.001, 1.002, 1.003, 1.006, 1.009, 1.015, 1.022, 1.032, 1.044, 1.057, 1.076, 1.094, 1.119, 1.149, 1.186, 1.218, + 1.249, 1.205, 1.167, 1.133, 1.107, 1.085, 1.067, 1.052, 1.038, 1.029, 1.021, 1.013, 1.008, 1.004, 1.003, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.013, 1.021, 1.031, 1.042, 1.055, 1.073, 1.093, 1.116, 1.147, 1.182, 1.218, + 1.249, 1.204, 1.165, 1.132, 1.106, 1.085, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.007, 1.003, 1.002, 1.001, 1.001, 1.001, 1.001, 1.004, 1.007, 1.013, 1.021, 1.029, 1.042, 1.055, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.249, 1.204, 1.165, 1.132, 1.107, 1.086, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.008, 1.004, 1.002, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.014, 1.021, 1.029, 1.042, 1.056, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.251, 1.205, 1.166, 1.133, 1.108, 1.087, 1.068, 1.052, 1.039, 1.031, 1.021, 1.014, 1.009, 1.006, 1.003, 1.002, 1.001, 1.001, 1.003, 1.006, 1.009, 1.014, 1.022, 1.031, 1.043, 1.056, 1.073, 1.093, 1.116, 1.145, 1.182, 1.218, + 1.252, 1.208, 1.168, 1.137, 1.111, 1.089, 1.071, 1.055, 1.043, 1.033, 1.023, 1.016, 1.012, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.008, 1.012, 1.017, 1.024, 1.034, 1.045, 1.059, 1.075, 1.095, 1.119, 1.149, 1.185, 1.218, + 1.256, 1.213, 1.173, 1.142, 1.115, 1.093, 1.075, 1.059, 1.047, 1.036, 1.027, 1.021, 1.016, 1.012, 1.011, 1.009, 1.008, 1.008, 1.009, 1.012, 1.016, 1.021, 1.028, 1.038, 1.049, 1.064, 1.081, 1.099, 1.126, 1.155, 1.192, 1.223, + 1.261, 1.221, 1.179, 1.148, 1.121, 1.099, 1.081, 1.065, 1.052, 1.042, 1.032, 1.026, 1.021, 1.017, 1.015, 1.014, 1.014, 1.013, 1.013, 1.016, 1.021, 1.026, 1.033, 1.043, 1.054, 1.068, 1.085, 1.106, 1.132, 1.161, 1.199, 1.228, + 1.267, 1.228, 1.188, 1.155, 1.128, 1.105, 1.086, 1.071, 1.059, 1.047, 1.038, 1.031, 1.027, 1.022, 1.021, 1.019, 1.019, 1.019, 1.019, 1.022, 1.026, 1.032, 1.038, 1.049, 1.061, 1.075, 1.092, 1.112, 1.138, 1.169, 1.207, 1.236, + 1.278, 1.241, 1.199, 1.164, 1.137, 1.114, 1.094, 1.078, 1.066, 1.055, 1.046, 1.038, 1.032, 1.029, 1.027, 1.027, 1.027, 1.027, 1.027, 1.029, 1.032, 1.038, 1.047, 1.056, 1.067, 1.083, 1.099, 1.121, 1.146, 1.178, 1.217, 1.244, + 1.291, 1.252, 1.211, 1.175, 1.147, 1.124, 1.103, 1.088, 1.075, 1.063, 1.054, 1.046, 1.041, 1.036, 1.035, 1.035, 1.035, 1.035, 1.036, 1.038, 1.041, 1.047, 1.055, 1.065, 1.075, 1.092, 1.111, 1.132, 1.157, 1.189, 1.231, 1.255, + 1.303, 1.265, 1.222, 1.187, 1.158, 1.133, 1.112, 1.097, 1.083, 1.072, 1.063, 1.054, 1.048, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.046, 1.049, 1.055, 1.065, 1.074, 1.086, 1.102, 1.119, 1.144, 1.171, 1.203, 1.243, 1.268, + 1.317, 1.282, 1.236, 1.201, 1.171, 1.146, 1.125, 1.109, 1.095, 1.083, 1.072, 1.064, 1.058, 1.054, 1.052, 1.051, 1.051, 1.053, 1.054, 1.057, 1.061, 1.065, 1.074, 1.086, 1.099, 1.113, 1.133, 1.156, 1.183, 1.217, 1.259, 1.282, + 1.335, 1.301, 1.254, 1.218, 1.186, 1.159, 1.138, 1.121, 1.108, 1.095, 1.085, 1.076, 1.069, 1.066, 1.065, 1.063, 1.062, 1.063, 1.065, 1.068, 1.073, 1.078, 1.087, 1.098, 1.113, 1.126, 1.146, 1.171, 1.199, 1.235, 1.277, 1.299, + 1.356, 1.321, 1.274, 1.235, 1.202, 1.175, 1.153, 1.137, 1.121, 1.108, 1.097, 1.089, 1.084, 1.081, 1.077, 1.075, 1.075, 1.075, 1.077, 1.081, 1.086, 1.091, 1.099, 1.113, 1.126, 1.144, 1.162, 1.187, 1.218, 1.255, 1.297, 1.321, + 1.376, 1.344, 1.296, 1.257, 1.223, 1.194, 1.171, 1.153, 1.137, 1.124, 1.112, 1.104, 1.099, 1.095, 1.093, 1.091, 1.089, 1.091, 1.092, 1.095, 1.101, 1.108, 1.116, 1.128, 1.144, 1.161, 1.181, 1.206, 1.237, 1.275, 1.321, 1.347, + 1.403, 1.369, 1.319, 1.279, 1.244, 1.214, 1.191, 1.171, 1.154, 1.139, 1.129, 1.121, 1.115, 1.111, 1.109, 1.106, 1.105, 1.105, 1.108, 1.112, 1.117, 1.124, 1.135, 1.147, 1.162, 1.181, 1.203, 1.228, 1.262, 1.301, 1.347, 1.377, + 1.429, 1.398, 1.348, 1.306, 1.269, 1.237, 1.214, 1.191, 1.173, 1.158, 1.146, 1.138, 1.132, 1.128, 1.125, 1.123, 1.122, 1.123, 1.125, 1.129, 1.136, 1.142, 1.154, 1.166, 1.182, 1.203, 1.226, 1.253, 1.288, 1.329, 1.377, 1.406, + 1.465, 1.429, 1.377, 1.335, 1.295, 1.262, 1.236, 1.214, 1.194, 1.179, 1.167, 1.157, 1.151, 1.146, 1.144, 1.142, 1.142, 1.142, 1.144, 1.149, 1.154, 1.163, 1.174, 1.187, 1.205, 1.226, 1.251, 1.279, 1.315, 1.357, 1.406, 1.437, + 1.493, 1.465, 1.409, 1.364, 1.323, 1.289, 1.261, 1.235, 1.214, 1.194, 1.179, 1.171, 1.166, 1.163, 1.161, 1.161, 1.161, 1.161, 1.162, 1.164, 1.168, 1.175, 1.187, 1.205, 1.225, 1.251, 1.276, 1.306, 1.344, 1.387, 1.437, 1.455 + ], + "sigma": 0.0007, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.95054, -0.57435, -0.37619, + -0.46945, 1.86661, -0.39716, + 0.07977, -1.14072, 2.06095 + ] + }, + { + "ct": 2800, + "ccm": + [ + 1.94104, -0.60261, -0.33844, + -0.43162, 1.85422, -0.42261, + 0.03799, -0.95022, 1.91222 + ] + }, + { + "ct": 2900, + "ccm": + [ + 1.91828, -0.59569, -0.32258, + -0.51902, 2.09091, -0.57189, + -0.03324, -0.73462, 1.76785 + ] + }, + { + "ct": 3620, + "ccm": + [ + 1.97199, -0.66403, -0.30797, + -0.46411, 2.02612, -0.56201, + -0.07764, -0.61178, 1.68942 + ] + }, + { + "ct": 4560, + "ccm": + [ + 2.15256, -0.84787, -0.30469, + -0.48422, 2.28962, -0.80541, + -0.15113, -0.53014, 1.68127 + ] + }, + { + "ct": 5600, + "ccm": + [ + 2.04576, -0.74771, -0.29805, + -0.36332, 1.98993, -0.62662, + -0.09328, -0.46543, 1.55871 + ] + }, + { + "ct": 7400, + "ccm": + [ + 2.37532, -0.83069, -0.54462, + -0.48279, 2.84309, -1.36031, + -0.21178, -0.66532, 1.87709 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.06, + "strength": 0.5, + "limit": 0.5 + } + }, + { + "rpi.cac": + { + "lut_rx": + [ + -0.28, -0.22, -0.16, -0.09, -0.02, 0.04, 0.11, 0.17, 0.29, + -0.28, -0.22, -0.16, -0.09, -0.02, 0.04, 0.11, 0.18, 0.3, + -0.28, -0.22, -0.16, -0.09, -0.02, 0.05, 0.11, 0.18, 0.31, + -0.28, -0.22, -0.16, -0.09, -0.02, 0.05, 0.12, 0.18, 0.31, + -0.27, -0.22, -0.16, -0.09, -0.02, 0.05, 0.12, 0.19, 0.31, + -0.27, -0.21, -0.15, -0.08, -0.02, 0.05, 0.12, 0.18, 0.31, + -0.27, -0.21, -0.15, -0.08, -0.02, 0.05, 0.11, 0.18, 0.3, + -0.25, -0.2, -0.15, -0.09, -0.02, 0.05, 0.11, 0.17, 0.29, + -0.24, -0.19, -0.14, -0.08, -0.02, 0.04, 0.11, 0.17, 0.29 + ], + "lut_ry": + [ + -0.19, -0.18, -0.19, -0.19, -0.19, -0.18, -0.19, -0.19, -0.2, + -0.14, -0.14, -0.15, -0.16, -0.16, -0.16, -0.16, -0.16, -0.17, + -0.11, -0.1, -0.11, -0.12, -0.12, -0.12, -0.12, -0.12, -0.14, + -0.06, -0.05, -0.05, -0.06, -0.07, -0.07, -0.06, -0.06, -0.08, + -0.01, 0.0, -0.01, -0.01, -0.01, -0.01, -0.01, -0.01, -0.02, + 0.04, 0.05, 0.04, 0.03, 0.03, 0.03, 0.03, 0.04, 0.03, + 0.07, 0.08, 0.07, 0.07, 0.07, 0.07, 0.08, 0.08, 0.07, + 0.1, 0.11, 0.1, 0.1, 0.1, 0.1, 0.1, 0.11, 0.1, + 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.15, 0.15, 0.14 + ], + "lut_bx": + [ + -0.21, -0.17, -0.13, -0.06, 0.01, 0.07, 0.13, 0.18, 0.27, + -0.21, -0.17, -0.13, -0.06, 0.01, 0.08, 0.14, 0.2, 0.28, + -0.22, -0.18, -0.13, -0.06, 0.01, 0.08, 0.15, 0.21, 0.3, + -0.22, -0.18, -0.13, -0.06, 0.01, 0.08, 0.15, 0.21, 0.31, + -0.21, -0.17, -0.13, -0.07, 0.01, 0.08, 0.15, 0.2, 0.31, + -0.2, -0.16, -0.12, -0.06, 0.0, 0.07, 0.14, 0.18, 0.28, + -0.19, -0.15, -0.11, -0.06, 0.01, 0.07, 0.13, 0.18, 0.26, + -0.17, -0.14, -0.1, -0.05, 0.01, 0.07, 0.12, 0.16, 0.25, + -0.15, -0.12, -0.08, -0.04, 0.01, 0.07, 0.1, 0.13, 0.22 + ], + "lut_by": + [ + -0.15, -0.15, -0.17, -0.18, -0.18, -0.18, -0.17, -0.16, -0.14, + -0.12, -0.12, -0.13, -0.14, -0.14, -0.14, -0.13, -0.12, -0.11, + -0.09, -0.08, -0.09, -0.1, -0.1, -0.09, -0.09, -0.08, -0.09, + -0.06, -0.04, -0.04, -0.05, -0.04, -0.04, -0.04, -0.04, -0.06, + -0.02, 0.01, 0.01, 0.02, 0.02, 0.02, 0.02, 0.01, -0.02, + 0.02, 0.05, 0.07, 0.08, 0.09, 0.09, 0.08, 0.06, 0.02, + 0.05, 0.08, 0.1, 0.12, 0.13, 0.13, 0.12, 0.1, 0.06, + 0.07, 0.09, 0.11, 0.14, 0.16, 0.16, 0.14, 0.12, 0.07, + 0.09, 0.11, 0.14, 0.17, 0.19, 0.19, 0.18, 0.15, 0.1 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx296_mono.json b/src/ipa/rpi/pisp/data/imx296_mono.json new file mode 100644 index 00000000..153f86a0 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx296_mono.json @@ -0,0 +1,960 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 4724, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 860, + "reference_Y": 14551 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.751 + } + }, + { + "rpi.geq": + { + "offset": 226, + "slope": 0.01032 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 4000, + "table": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 4000, + "table": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + ], + "luminance_lut": + [ + 1.425, 1.393, 1.341, 1.295, 1.258, 1.226, 1.201, 1.181, 1.162, 1.146, 1.133, 1.123, 1.115, 1.111, 1.107, 1.106, 1.106, 1.107, 1.108, 1.111, 1.114, 1.122, 1.133, 1.148, 1.164, 1.184, 1.208, 1.236, 1.271, 1.309, 1.359, 1.381, + 1.397, 1.367, 1.317, 1.274, 1.237, 1.207, 1.183, 1.163, 1.146, 1.133, 1.123, 1.114, 1.107, 1.101, 1.098, 1.096, 1.096, 1.096, 1.097, 1.102, 1.106, 1.112, 1.122, 1.133, 1.148, 1.166, 1.187, 1.215, 1.249, 1.288, 1.335, 1.359, + 1.374, 1.341, 1.292, 1.251, 1.215, 1.186, 1.166, 1.146, 1.131, 1.117, 1.108, 1.099, 1.091, 1.088, 1.084, 1.082, 1.081, 1.082, 1.084, 1.088, 1.093, 1.098, 1.107, 1.118, 1.133, 1.149, 1.169, 1.195, 1.228, 1.267, 1.313, 1.335, + 1.352, 1.318, 1.271, 1.231, 1.196, 1.169, 1.149, 1.131, 1.115, 1.103, 1.093, 1.086, 1.079, 1.074, 1.071, 1.069, 1.069, 1.069, 1.071, 1.076, 1.079, 1.085, 1.094, 1.102, 1.117, 1.133, 1.152, 1.176, 1.208, 1.246, 1.289, 1.313, + 1.333, 1.298, 1.253, 1.212, 1.179, 1.153, 1.134, 1.116, 1.102, 1.089, 1.079, 1.072, 1.066, 1.062, 1.059, 1.058, 1.057, 1.057, 1.059, 1.064, 1.068, 1.072, 1.081, 1.091, 1.102, 1.119, 1.137, 1.161, 1.191, 1.227, 1.271, 1.293, + 1.317, 1.281, 1.235, 1.196, 1.165, 1.139, 1.119, 1.104, 1.089, 1.078, 1.068, 1.062, 1.055, 1.051, 1.048, 1.047, 1.047, 1.047, 1.048, 1.053, 1.056, 1.061, 1.069, 1.079, 1.091, 1.105, 1.126, 1.147, 1.177, 1.212, 1.253, 1.278, + 1.301, 1.265, 1.221, 1.181, 1.151, 1.127, 1.108, 1.091, 1.078, 1.068, 1.059, 1.051, 1.045, 1.041, 1.038, 1.037, 1.036, 1.037, 1.038, 1.042, 1.046, 1.051, 1.059, 1.069, 1.081, 1.096, 1.113, 1.135, 1.164, 1.198, 1.238, 1.264, + 1.286, 1.251, 1.207, 1.169, 1.141, 1.116, 1.098, 1.081, 1.068, 1.058, 1.049, 1.042, 1.037, 1.033, 1.031, 1.029, 1.028, 1.028, 1.029, 1.033, 1.037, 1.043, 1.051, 1.059, 1.071, 1.086, 1.104, 1.124, 1.152, 1.185, 1.225, 1.252, + 1.275, 1.239, 1.196, 1.161, 1.132, 1.107, 1.089, 1.073, 1.059, 1.049, 1.041, 1.035, 1.028, 1.024, 1.023, 1.021, 1.021, 1.021, 1.022, 1.024, 1.029, 1.036, 1.043, 1.051, 1.063, 1.078, 1.095, 1.115, 1.143, 1.175, 1.214, 1.243, + 1.267, 1.227, 1.187, 1.152, 1.122, 1.101, 1.081, 1.067, 1.054, 1.042, 1.035, 1.028, 1.023, 1.018, 1.015, 1.014, 1.014, 1.014, 1.016, 1.019, 1.024, 1.029, 1.036, 1.045, 1.056, 1.071, 1.088, 1.107, 1.134, 1.167, 1.204, 1.234, + 1.261, 1.219, 1.179, 1.145, 1.116, 1.095, 1.076, 1.061, 1.047, 1.037, 1.031, 1.023, 1.018, 1.014, 1.011, 1.009, 1.009, 1.009, 1.011, 1.013, 1.018, 1.024, 1.031, 1.039, 1.049, 1.065, 1.083, 1.102, 1.128, 1.161, 1.196, 1.228, + 1.256, 1.213, 1.173, 1.139, 1.111, 1.091, 1.071, 1.056, 1.043, 1.033, 1.026, 1.019, 1.014, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.009, 1.013, 1.018, 1.026, 1.035, 1.046, 1.061, 1.078, 1.097, 1.123, 1.154, 1.191, 1.222, + 1.251, 1.208, 1.169, 1.137, 1.108, 1.088, 1.069, 1.053, 1.039, 1.029, 1.023, 1.015, 1.011, 1.006, 1.004, 1.003, 1.001, 1.002, 1.003, 1.006, 1.009, 1.015, 1.022, 1.032, 1.044, 1.057, 1.076, 1.094, 1.119, 1.149, 1.186, 1.218, + 1.249, 1.205, 1.167, 1.133, 1.107, 1.085, 1.067, 1.052, 1.038, 1.029, 1.021, 1.013, 1.008, 1.004, 1.003, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.013, 1.021, 1.031, 1.042, 1.055, 1.073, 1.093, 1.116, 1.147, 1.182, 1.218, + 1.249, 1.204, 1.165, 1.132, 1.106, 1.085, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.007, 1.003, 1.002, 1.001, 1.001, 1.001, 1.001, 1.004, 1.007, 1.013, 1.021, 1.029, 1.042, 1.055, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.249, 1.204, 1.165, 1.132, 1.107, 1.086, 1.067, 1.051, 1.038, 1.029, 1.019, 1.013, 1.008, 1.004, 1.002, 1.001, 1.001, 1.001, 1.002, 1.004, 1.007, 1.014, 1.021, 1.029, 1.042, 1.056, 1.072, 1.091, 1.115, 1.145, 1.181, 1.217, + 1.251, 1.205, 1.166, 1.133, 1.108, 1.087, 1.068, 1.052, 1.039, 1.031, 1.021, 1.014, 1.009, 1.006, 1.003, 1.002, 1.001, 1.001, 1.003, 1.006, 1.009, 1.014, 1.022, 1.031, 1.043, 1.056, 1.073, 1.093, 1.116, 1.145, 1.182, 1.218, + 1.252, 1.208, 1.168, 1.137, 1.111, 1.089, 1.071, 1.055, 1.043, 1.033, 1.023, 1.016, 1.012, 1.009, 1.006, 1.005, 1.004, 1.004, 1.006, 1.008, 1.012, 1.017, 1.024, 1.034, 1.045, 1.059, 1.075, 1.095, 1.119, 1.149, 1.185, 1.218, + 1.256, 1.213, 1.173, 1.142, 1.115, 1.093, 1.075, 1.059, 1.047, 1.036, 1.027, 1.021, 1.016, 1.012, 1.011, 1.009, 1.008, 1.008, 1.009, 1.012, 1.016, 1.021, 1.028, 1.038, 1.049, 1.064, 1.081, 1.099, 1.126, 1.155, 1.192, 1.223, + 1.261, 1.221, 1.179, 1.148, 1.121, 1.099, 1.081, 1.065, 1.052, 1.042, 1.032, 1.026, 1.021, 1.017, 1.015, 1.014, 1.014, 1.013, 1.013, 1.016, 1.021, 1.026, 1.033, 1.043, 1.054, 1.068, 1.085, 1.106, 1.132, 1.161, 1.199, 1.228, + 1.267, 1.228, 1.188, 1.155, 1.128, 1.105, 1.086, 1.071, 1.059, 1.047, 1.038, 1.031, 1.027, 1.022, 1.021, 1.019, 1.019, 1.019, 1.019, 1.022, 1.026, 1.032, 1.038, 1.049, 1.061, 1.075, 1.092, 1.112, 1.138, 1.169, 1.207, 1.236, + 1.278, 1.241, 1.199, 1.164, 1.137, 1.114, 1.094, 1.078, 1.066, 1.055, 1.046, 1.038, 1.032, 1.029, 1.027, 1.027, 1.027, 1.027, 1.027, 1.029, 1.032, 1.038, 1.047, 1.056, 1.067, 1.083, 1.099, 1.121, 1.146, 1.178, 1.217, 1.244, + 1.291, 1.252, 1.211, 1.175, 1.147, 1.124, 1.103, 1.088, 1.075, 1.063, 1.054, 1.046, 1.041, 1.036, 1.035, 1.035, 1.035, 1.035, 1.036, 1.038, 1.041, 1.047, 1.055, 1.065, 1.075, 1.092, 1.111, 1.132, 1.157, 1.189, 1.231, 1.255, + 1.303, 1.265, 1.222, 1.187, 1.158, 1.133, 1.112, 1.097, 1.083, 1.072, 1.063, 1.054, 1.048, 1.043, 1.043, 1.043, 1.043, 1.043, 1.043, 1.046, 1.049, 1.055, 1.065, 1.074, 1.086, 1.102, 1.119, 1.144, 1.171, 1.203, 1.243, 1.268, + 1.317, 1.282, 1.236, 1.201, 1.171, 1.146, 1.125, 1.109, 1.095, 1.083, 1.072, 1.064, 1.058, 1.054, 1.052, 1.051, 1.051, 1.053, 1.054, 1.057, 1.061, 1.065, 1.074, 1.086, 1.099, 1.113, 1.133, 1.156, 1.183, 1.217, 1.259, 1.282, + 1.335, 1.301, 1.254, 1.218, 1.186, 1.159, 1.138, 1.121, 1.108, 1.095, 1.085, 1.076, 1.069, 1.066, 1.065, 1.063, 1.062, 1.063, 1.065, 1.068, 1.073, 1.078, 1.087, 1.098, 1.113, 1.126, 1.146, 1.171, 1.199, 1.235, 1.277, 1.299, + 1.356, 1.321, 1.274, 1.235, 1.202, 1.175, 1.153, 1.137, 1.121, 1.108, 1.097, 1.089, 1.084, 1.081, 1.077, 1.075, 1.075, 1.075, 1.077, 1.081, 1.086, 1.091, 1.099, 1.113, 1.126, 1.144, 1.162, 1.187, 1.218, 1.255, 1.297, 1.321, + 1.376, 1.344, 1.296, 1.257, 1.223, 1.194, 1.171, 1.153, 1.137, 1.124, 1.112, 1.104, 1.099, 1.095, 1.093, 1.091, 1.089, 1.091, 1.092, 1.095, 1.101, 1.108, 1.116, 1.128, 1.144, 1.161, 1.181, 1.206, 1.237, 1.275, 1.321, 1.347, + 1.403, 1.369, 1.319, 1.279, 1.244, 1.214, 1.191, 1.171, 1.154, 1.139, 1.129, 1.121, 1.115, 1.111, 1.109, 1.106, 1.105, 1.105, 1.108, 1.112, 1.117, 1.124, 1.135, 1.147, 1.162, 1.181, 1.203, 1.228, 1.262, 1.301, 1.347, 1.377, + 1.429, 1.398, 1.348, 1.306, 1.269, 1.237, 1.214, 1.191, 1.173, 1.158, 1.146, 1.138, 1.132, 1.128, 1.125, 1.123, 1.122, 1.123, 1.125, 1.129, 1.136, 1.142, 1.154, 1.166, 1.182, 1.203, 1.226, 1.253, 1.288, 1.329, 1.377, 1.406, + 1.465, 1.429, 1.377, 1.335, 1.295, 1.262, 1.236, 1.214, 1.194, 1.179, 1.167, 1.157, 1.151, 1.146, 1.144, 1.142, 1.142, 1.142, 1.144, 1.149, 1.154, 1.163, 1.174, 1.187, 1.205, 1.226, 1.251, 1.279, 1.315, 1.357, 1.406, 1.437, + 1.493, 1.465, 1.409, 1.364, 1.323, 1.289, 1.261, 1.235, 1.214, 1.194, 1.179, 1.171, 1.166, 1.163, 1.161, 1.161, 1.161, 1.161, 1.162, 1.164, 1.168, 1.175, 1.187, 1.205, 1.225, 1.251, 1.276, 1.306, 1.344, 1.387, 1.437, 1.455 + ], + "sigma": 0.0007, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.06, + "strength": 0.5, + "limit": 0.5 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": [ 0.0, 2.5, 0.01, 2.5, 0.06, 1.0, 1.0, 1.0 ], + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": [ 0.0, 2.5, 0.01, 2.5, 0.06, 1.0, 1.0, 1.0 ], + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx378.json b/src/ipa/rpi/pisp/data/imx378.json new file mode 100644 index 00000000..ac760f79 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx378.json @@ -0,0 +1,634 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 9999, + "reference_gain": 1.95, + "reference_aperture": 1.0, + "reference_lux": 1000, + "reference_Y": 12996 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.641 + } + }, + { + "rpi.geq": + { + "offset": 235, + "slope": 0.00902 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 8000 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8100 + } + }, + "bayes": 1, + "ct_curve": + [ + 2850.0, 0.6361, 0.3911, + 3550.0, 0.5386, 0.5077, + 4500.0, 0.4472, 0.6171, + 5600.0, 0.3906, 0.6848, + 8000.0, 0.3412, 0.7441 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01667, + "transverse_neg": 0.01195 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.5, + "calibrations_Cr": [ + { + "ct": 2800, + "table": + [ + 1.604, 1.603, 1.601, 1.597, 1.594, 1.588, 1.582, 1.576, 1.57, 1.566, 1.562, 1.561, 1.561, 1.561, 1.561, 1.561, 1.561, 1.562, 1.565, 1.57, 1.577, 1.584, 1.591, 1.598, 1.604, 1.61, 1.617, 1.623, 1.627, 1.632, 1.634, 1.636, + 1.603, 1.601, 1.599, 1.595, 1.591, 1.585, 1.579, 1.572, 1.565, 1.561, 1.557, 1.555, 1.555, 1.555, 1.555, 1.555, 1.556, 1.558, 1.561, 1.566, 1.573, 1.581, 1.587, 1.594, 1.6, 1.607, 1.613, 1.62, 1.625, 1.63, 1.632, 1.635, + 1.602, 1.599, 1.596, 1.592, 1.589, 1.582, 1.575, 1.568, 1.561, 1.556, 1.552, 1.55, 1.549, 1.548, 1.548, 1.549, 1.55, 1.553, 1.556, 1.562, 1.57, 1.577, 1.583, 1.589, 1.596, 1.603, 1.61, 1.617, 1.622, 1.627, 1.63, 1.633, + 1.601, 1.597, 1.594, 1.59, 1.586, 1.579, 1.571, 1.564, 1.556, 1.551, 1.546, 1.544, 1.542, 1.541, 1.541, 1.542, 1.544, 1.547, 1.551, 1.557, 1.565, 1.573, 1.579, 1.585, 1.592, 1.6, 1.607, 1.614, 1.62, 1.625, 1.628, 1.632, + 1.6, 1.596, 1.591, 1.586, 1.58, 1.573, 1.566, 1.559, 1.551, 1.546, 1.54, 1.537, 1.534, 1.533, 1.533, 1.534, 1.536, 1.539, 1.544, 1.551, 1.559, 1.567, 1.574, 1.581, 1.589, 1.596, 1.604, 1.612, 1.618, 1.624, 1.627, 1.631, + 1.599, 1.594, 1.588, 1.582, 1.575, 1.568, 1.56, 1.553, 1.547, 1.54, 1.534, 1.529, 1.525, 1.524, 1.524, 1.525, 1.527, 1.531, 1.537, 1.545, 1.553, 1.561, 1.569, 1.577, 1.585, 1.593, 1.601, 1.609, 1.616, 1.623, 1.626, 1.63, + 1.599, 1.592, 1.586, 1.578, 1.571, 1.563, 1.555, 1.548, 1.542, 1.535, 1.528, 1.522, 1.517, 1.515, 1.515, 1.516, 1.519, 1.523, 1.531, 1.539, 1.547, 1.556, 1.564, 1.573, 1.581, 1.59, 1.599, 1.607, 1.615, 1.622, 1.625, 1.629, + 1.598, 1.591, 1.583, 1.575, 1.567, 1.559, 1.55, 1.543, 1.537, 1.53, 1.523, 1.516, 1.509, 1.506, 1.506, 1.507, 1.51, 1.516, 1.525, 1.533, 1.541, 1.55, 1.56, 1.57, 1.579, 1.587, 1.596, 1.605, 1.613, 1.621, 1.625, 1.629, + 1.597, 1.589, 1.581, 1.572, 1.564, 1.555, 1.546, 1.539, 1.532, 1.525, 1.517, 1.509, 1.5, 1.497, 1.497, 1.499, 1.501, 1.508, 1.519, 1.528, 1.535, 1.544, 1.556, 1.567, 1.576, 1.585, 1.594, 1.603, 1.612, 1.62, 1.624, 1.629, + 1.597, 1.588, 1.579, 1.57, 1.561, 1.552, 1.543, 1.535, 1.527, 1.519, 1.511, 1.503, 1.494, 1.491, 1.49, 1.492, 1.496, 1.502, 1.512, 1.521, 1.529, 1.539, 1.552, 1.564, 1.574, 1.583, 1.592, 1.602, 1.611, 1.619, 1.624, 1.629, + 1.597, 1.588, 1.579, 1.569, 1.56, 1.55, 1.54, 1.531, 1.522, 1.513, 1.504, 1.497, 1.489, 1.486, 1.486, 1.488, 1.493, 1.498, 1.506, 1.514, 1.523, 1.535, 1.548, 1.561, 1.572, 1.582, 1.591, 1.601, 1.61, 1.619, 1.624, 1.629, + 1.597, 1.587, 1.578, 1.568, 1.559, 1.548, 1.538, 1.527, 1.516, 1.507, 1.498, 1.491, 1.485, 1.482, 1.481, 1.484, 1.489, 1.495, 1.499, 1.506, 1.518, 1.53, 1.545, 1.559, 1.57, 1.58, 1.59, 1.6, 1.61, 1.619, 1.624, 1.629, + 1.597, 1.587, 1.578, 1.568, 1.558, 1.548, 1.537, 1.526, 1.514, 1.504, 1.494, 1.487, 1.482, 1.479, 1.479, 1.481, 1.486, 1.491, 1.496, 1.503, 1.515, 1.529, 1.544, 1.558, 1.569, 1.58, 1.59, 1.6, 1.61, 1.619, 1.624, 1.629, + 1.597, 1.587, 1.578, 1.568, 1.558, 1.548, 1.537, 1.525, 1.513, 1.502, 1.491, 1.484, 1.48, 1.478, 1.477, 1.479, 1.483, 1.488, 1.494, 1.502, 1.515, 1.528, 1.543, 1.557, 1.569, 1.58, 1.59, 1.6, 1.61, 1.619, 1.624, 1.629, + 1.597, 1.587, 1.578, 1.568, 1.558, 1.547, 1.536, 1.524, 1.511, 1.499, 1.487, 1.481, 1.478, 1.476, 1.476, 1.477, 1.48, 1.485, 1.492, 1.501, 1.514, 1.527, 1.543, 1.557, 1.569, 1.58, 1.59, 1.6, 1.61, 1.619, 1.624, 1.629, + 1.597, 1.587, 1.578, 1.568, 1.558, 1.547, 1.536, 1.524, 1.511, 1.499, 1.487, 1.481, 1.479, 1.477, 1.477, 1.478, 1.48, 1.484, 1.492, 1.501, 1.514, 1.527, 1.543, 1.557, 1.569, 1.58, 1.591, 1.601, 1.61, 1.619, 1.624, 1.63, + 1.597, 1.588, 1.578, 1.568, 1.558, 1.547, 1.536, 1.524, 1.511, 1.499, 1.487, 1.482, 1.48, 1.479, 1.478, 1.479, 1.48, 1.484, 1.492, 1.501, 1.514, 1.527, 1.543, 1.557, 1.569, 1.581, 1.591, 1.602, 1.611, 1.619, 1.625, 1.63, + 1.597, 1.588, 1.579, 1.569, 1.559, 1.548, 1.536, 1.524, 1.512, 1.5, 1.488, 1.483, 1.482, 1.48, 1.48, 1.48, 1.481, 1.485, 1.492, 1.501, 1.514, 1.527, 1.543, 1.557, 1.57, 1.581, 1.592, 1.602, 1.611, 1.62, 1.625, 1.631, + 1.597, 1.588, 1.58, 1.57, 1.56, 1.549, 1.538, 1.526, 1.515, 1.504, 1.494, 1.488, 1.484, 1.481, 1.48, 1.482, 1.485, 1.49, 1.497, 1.506, 1.518, 1.531, 1.546, 1.56, 1.572, 1.583, 1.594, 1.604, 1.613, 1.621, 1.626, 1.631, + 1.597, 1.589, 1.581, 1.571, 1.562, 1.551, 1.54, 1.529, 1.518, 1.509, 1.501, 1.493, 1.486, 1.482, 1.481, 1.483, 1.489, 1.496, 1.503, 1.511, 1.522, 1.534, 1.548, 1.562, 1.574, 1.586, 1.596, 1.607, 1.615, 1.622, 1.627, 1.632, + 1.597, 1.59, 1.582, 1.573, 1.564, 1.553, 1.543, 1.532, 1.522, 1.514, 1.507, 1.499, 1.489, 1.485, 1.484, 1.487, 1.494, 1.501, 1.508, 1.517, 1.527, 1.538, 1.552, 1.565, 1.577, 1.588, 1.599, 1.609, 1.616, 1.624, 1.628, 1.632, + 1.599, 1.592, 1.585, 1.576, 1.566, 1.557, 1.548, 1.538, 1.529, 1.521, 1.513, 1.504, 1.496, 1.492, 1.492, 1.494, 1.5, 1.507, 1.515, 1.524, 1.534, 1.545, 1.557, 1.569, 1.581, 1.592, 1.602, 1.611, 1.619, 1.626, 1.63, 1.634, + 1.6, 1.594, 1.588, 1.579, 1.569, 1.561, 1.553, 1.545, 1.537, 1.528, 1.518, 1.51, 1.503, 1.5, 1.5, 1.502, 1.506, 1.512, 1.522, 1.531, 1.542, 1.552, 1.563, 1.574, 1.585, 1.596, 1.605, 1.614, 1.621, 1.628, 1.632, 1.635, + 1.602, 1.597, 1.591, 1.582, 1.573, 1.565, 1.558, 1.551, 1.543, 1.534, 1.524, 1.517, 1.511, 1.509, 1.509, 1.511, 1.514, 1.52, 1.529, 1.539, 1.549, 1.559, 1.569, 1.579, 1.589, 1.6, 1.608, 1.617, 1.624, 1.631, 1.634, 1.637, + 1.605, 1.6, 1.596, 1.587, 1.579, 1.571, 1.563, 1.556, 1.549, 1.541, 1.533, 1.527, 1.522, 1.52, 1.52, 1.522, 1.525, 1.53, 1.538, 1.546, 1.557, 1.566, 1.576, 1.585, 1.595, 1.604, 1.612, 1.621, 1.627, 1.633, 1.637, 1.641, + 1.608, 1.604, 1.6, 1.592, 1.585, 1.577, 1.569, 1.561, 1.554, 1.547, 1.541, 1.536, 1.533, 1.531, 1.531, 1.533, 1.536, 1.54, 1.546, 1.554, 1.564, 1.574, 1.583, 1.592, 1.6, 1.608, 1.616, 1.624, 1.63, 1.636, 1.64, 1.644, + 1.611, 1.607, 1.604, 1.597, 1.59, 1.582, 1.574, 1.567, 1.56, 1.554, 1.549, 1.545, 1.543, 1.542, 1.542, 1.543, 1.546, 1.55, 1.556, 1.562, 1.571, 1.58, 1.589, 1.598, 1.605, 1.613, 1.621, 1.628, 1.634, 1.639, 1.643, 1.647, + 1.614, 1.61, 1.607, 1.601, 1.595, 1.588, 1.58, 1.574, 1.567, 1.562, 1.557, 1.554, 1.553, 1.552, 1.552, 1.554, 1.557, 1.561, 1.565, 1.571, 1.579, 1.587, 1.595, 1.603, 1.61, 1.618, 1.625, 1.633, 1.638, 1.642, 1.646, 1.65, + 1.616, 1.614, 1.611, 1.606, 1.601, 1.594, 1.586, 1.58, 1.574, 1.569, 1.565, 1.563, 1.562, 1.562, 1.562, 1.564, 1.567, 1.571, 1.575, 1.58, 1.586, 1.593, 1.601, 1.609, 1.616, 1.623, 1.63, 1.637, 1.641, 1.646, 1.65, 1.653, + 1.618, 1.615, 1.613, 1.609, 1.604, 1.598, 1.592, 1.586, 1.58, 1.575, 1.572, 1.571, 1.57, 1.57, 1.57, 1.572, 1.574, 1.577, 1.581, 1.585, 1.592, 1.599, 1.606, 1.614, 1.621, 1.628, 1.634, 1.64, 1.644, 1.649, 1.651, 1.654, + 1.618, 1.617, 1.615, 1.612, 1.608, 1.603, 1.597, 1.591, 1.585, 1.581, 1.579, 1.578, 1.578, 1.578, 1.578, 1.579, 1.581, 1.583, 1.586, 1.59, 1.597, 1.604, 1.612, 1.619, 1.626, 1.633, 1.638, 1.643, 1.647, 1.651, 1.653, 1.655, + 1.619, 1.618, 1.617, 1.614, 1.611, 1.607, 1.602, 1.596, 1.59, 1.587, 1.585, 1.585, 1.585, 1.585, 1.585, 1.586, 1.587, 1.589, 1.591, 1.595, 1.602, 1.609, 1.617, 1.624, 1.631, 1.638, 1.642, 1.646, 1.65, 1.654, 1.655, 1.655 + ] + }, + { + "ct": 5500, + "table": + [ + 2.664, 2.661, 2.658, 2.652, 2.646, 2.638, 2.631, 2.619, 2.605, 2.602, 2.602, 2.602, 2.602, 2.603, 2.605, 2.609, 2.614, 2.619, 2.625, 2.632, 2.642, 2.654, 2.667, 2.68, 2.69, 2.701, 2.712, 2.723, 2.73, 2.736, 2.742, 2.747, + 2.662, 2.659, 2.656, 2.649, 2.64, 2.631, 2.622, 2.61, 2.596, 2.593, 2.592, 2.593, 2.593, 2.595, 2.599, 2.604, 2.61, 2.616, 2.621, 2.628, 2.636, 2.646, 2.659, 2.671, 2.682, 2.694, 2.706, 2.718, 2.726, 2.733, 2.739, 2.745, + 2.66, 2.657, 2.655, 2.645, 2.635, 2.625, 2.614, 2.601, 2.587, 2.583, 2.583, 2.583, 2.584, 2.588, 2.593, 2.599, 2.606, 2.612, 2.618, 2.624, 2.63, 2.639, 2.65, 2.662, 2.674, 2.687, 2.7, 2.713, 2.721, 2.73, 2.736, 2.743, + 2.657, 2.655, 2.652, 2.641, 2.629, 2.617, 2.605, 2.592, 2.579, 2.575, 2.574, 2.574, 2.575, 2.58, 2.587, 2.593, 2.6, 2.607, 2.613, 2.619, 2.625, 2.632, 2.643, 2.654, 2.667, 2.68, 2.694, 2.708, 2.717, 2.727, 2.734, 2.741, + 2.654, 2.649, 2.644, 2.633, 2.621, 2.608, 2.595, 2.584, 2.573, 2.569, 2.566, 2.566, 2.566, 2.57, 2.575, 2.582, 2.59, 2.598, 2.607, 2.615, 2.621, 2.628, 2.639, 2.65, 2.664, 2.677, 2.691, 2.705, 2.715, 2.725, 2.733, 2.741, + 2.651, 2.644, 2.636, 2.624, 2.612, 2.599, 2.585, 2.576, 2.568, 2.563, 2.559, 2.557, 2.558, 2.56, 2.563, 2.57, 2.579, 2.589, 2.6, 2.61, 2.617, 2.625, 2.635, 2.647, 2.66, 2.674, 2.688, 2.701, 2.712, 2.723, 2.732, 2.741, + 2.648, 2.639, 2.629, 2.617, 2.604, 2.59, 2.577, 2.569, 2.563, 2.557, 2.551, 2.549, 2.549, 2.55, 2.552, 2.558, 2.568, 2.58, 2.593, 2.604, 2.612, 2.621, 2.632, 2.644, 2.658, 2.671, 2.685, 2.699, 2.71, 2.722, 2.731, 2.741, + 2.646, 2.635, 2.623, 2.61, 2.596, 2.584, 2.572, 2.565, 2.559, 2.552, 2.544, 2.541, 2.54, 2.541, 2.542, 2.548, 2.559, 2.57, 2.583, 2.595, 2.605, 2.616, 2.63, 2.643, 2.656, 2.67, 2.684, 2.698, 2.71, 2.722, 2.731, 2.741, + 2.644, 2.63, 2.617, 2.603, 2.588, 2.578, 2.567, 2.561, 2.555, 2.547, 2.538, 2.533, 2.532, 2.531, 2.532, 2.538, 2.549, 2.561, 2.574, 2.586, 2.598, 2.612, 2.627, 2.642, 2.655, 2.668, 2.682, 2.696, 2.709, 2.722, 2.731, 2.741, + 2.643, 2.628, 2.613, 2.598, 2.583, 2.573, 2.564, 2.557, 2.55, 2.541, 2.531, 2.526, 2.524, 2.523, 2.524, 2.53, 2.541, 2.552, 2.565, 2.578, 2.593, 2.608, 2.625, 2.641, 2.654, 2.668, 2.682, 2.696, 2.709, 2.722, 2.731, 2.741, + 2.643, 2.627, 2.61, 2.595, 2.581, 2.571, 2.562, 2.553, 2.543, 2.534, 2.526, 2.52, 2.516, 2.516, 2.519, 2.525, 2.533, 2.543, 2.556, 2.57, 2.588, 2.606, 2.623, 2.64, 2.654, 2.668, 2.682, 2.696, 2.709, 2.722, 2.731, 2.741, + 2.643, 2.625, 2.608, 2.593, 2.578, 2.569, 2.56, 2.549, 2.536, 2.528, 2.52, 2.514, 2.508, 2.509, 2.515, 2.52, 2.526, 2.535, 2.546, 2.562, 2.583, 2.603, 2.622, 2.639, 2.653, 2.668, 2.682, 2.696, 2.709, 2.722, 2.731, 2.741, + 2.643, 2.625, 2.607, 2.592, 2.577, 2.568, 2.56, 2.548, 2.534, 2.524, 2.516, 2.51, 2.505, 2.506, 2.51, 2.516, 2.522, 2.531, 2.544, 2.56, 2.581, 2.601, 2.621, 2.639, 2.654, 2.669, 2.683, 2.696, 2.71, 2.723, 2.732, 2.742, + 2.643, 2.625, 2.607, 2.592, 2.577, 2.568, 2.56, 2.548, 2.534, 2.523, 2.512, 2.506, 2.503, 2.504, 2.506, 2.512, 2.52, 2.53, 2.543, 2.559, 2.58, 2.6, 2.62, 2.639, 2.655, 2.67, 2.684, 2.697, 2.711, 2.725, 2.734, 2.743, + 2.643, 2.625, 2.607, 2.592, 2.577, 2.568, 2.56, 2.548, 2.534, 2.521, 2.508, 2.503, 2.502, 2.501, 2.502, 2.508, 2.517, 2.529, 2.543, 2.559, 2.579, 2.599, 2.62, 2.639, 2.656, 2.671, 2.685, 2.698, 2.713, 2.727, 2.736, 2.744, + 2.645, 2.627, 2.609, 2.593, 2.578, 2.569, 2.56, 2.548, 2.535, 2.522, 2.51, 2.504, 2.502, 2.502, 2.504, 2.509, 2.518, 2.529, 2.543, 2.559, 2.579, 2.599, 2.621, 2.642, 2.658, 2.673, 2.686, 2.7, 2.715, 2.729, 2.738, 2.747, + 2.646, 2.628, 2.611, 2.595, 2.58, 2.569, 2.56, 2.548, 2.535, 2.523, 2.512, 2.506, 2.503, 2.504, 2.507, 2.512, 2.518, 2.529, 2.543, 2.559, 2.579, 2.6, 2.623, 2.644, 2.659, 2.674, 2.688, 2.702, 2.716, 2.73, 2.74, 2.749, + 2.648, 2.63, 2.612, 2.597, 2.581, 2.571, 2.56, 2.548, 2.536, 2.525, 2.515, 2.509, 2.504, 2.505, 2.509, 2.514, 2.52, 2.53, 2.544, 2.56, 2.58, 2.6, 2.625, 2.646, 2.661, 2.676, 2.69, 2.704, 2.718, 2.732, 2.742, 2.752, + 2.648, 2.632, 2.615, 2.6, 2.585, 2.575, 2.565, 2.555, 2.544, 2.533, 2.523, 2.516, 2.511, 2.511, 2.514, 2.52, 2.529, 2.539, 2.551, 2.566, 2.585, 2.605, 2.628, 2.649, 2.664, 2.68, 2.694, 2.709, 2.723, 2.736, 2.745, 2.754, + 2.648, 2.633, 2.618, 2.603, 2.588, 2.579, 2.569, 2.561, 2.552, 2.542, 2.53, 2.523, 2.517, 2.516, 2.518, 2.526, 2.538, 2.549, 2.558, 2.571, 2.589, 2.609, 2.631, 2.651, 2.667, 2.683, 2.699, 2.713, 2.727, 2.74, 2.748, 2.756, + 2.649, 2.635, 2.622, 2.607, 2.593, 2.584, 2.575, 2.567, 2.559, 2.549, 2.538, 2.531, 2.525, 2.523, 2.525, 2.533, 2.546, 2.558, 2.566, 2.579, 2.597, 2.615, 2.635, 2.655, 2.671, 2.688, 2.703, 2.718, 2.732, 2.745, 2.752, 2.76, + 2.653, 2.64, 2.628, 2.615, 2.602, 2.591, 2.581, 2.573, 2.565, 2.556, 2.546, 2.54, 2.537, 2.536, 2.537, 2.543, 2.553, 2.565, 2.577, 2.592, 2.609, 2.627, 2.645, 2.662, 2.678, 2.693, 2.708, 2.723, 2.737, 2.75, 2.758, 2.766, + 2.657, 2.646, 2.634, 2.622, 2.61, 2.599, 2.588, 2.579, 2.57, 2.562, 2.554, 2.55, 2.549, 2.548, 2.548, 2.553, 2.561, 2.572, 2.588, 2.605, 2.622, 2.639, 2.655, 2.67, 2.685, 2.699, 2.714, 2.728, 2.742, 2.756, 2.764, 2.773, + 2.662, 2.652, 2.642, 2.63, 2.618, 2.607, 2.595, 2.586, 2.578, 2.571, 2.565, 2.563, 2.562, 2.562, 2.563, 2.567, 2.573, 2.584, 2.601, 2.618, 2.635, 2.651, 2.665, 2.679, 2.692, 2.706, 2.72, 2.735, 2.748, 2.761, 2.77, 2.778, + 2.669, 2.661, 2.652, 2.64, 2.627, 2.615, 2.604, 2.596, 2.589, 2.584, 2.58, 2.578, 2.578, 2.579, 2.581, 2.585, 2.591, 2.601, 2.615, 2.63, 2.646, 2.661, 2.675, 2.688, 2.701, 2.714, 2.729, 2.744, 2.756, 2.768, 2.775, 2.782, + 2.676, 2.669, 2.662, 2.649, 2.636, 2.624, 2.612, 2.605, 2.6, 2.596, 2.594, 2.593, 2.593, 2.595, 2.598, 2.603, 2.609, 2.618, 2.629, 2.642, 2.657, 2.672, 2.685, 2.698, 2.71, 2.723, 2.738, 2.752, 2.763, 2.774, 2.78, 2.786, + 2.683, 2.676, 2.67, 2.658, 2.646, 2.635, 2.623, 2.616, 2.611, 2.609, 2.608, 2.608, 2.608, 2.611, 2.614, 2.619, 2.625, 2.633, 2.644, 2.655, 2.668, 2.681, 2.694, 2.707, 2.719, 2.732, 2.747, 2.76, 2.77, 2.779, 2.785, 2.791, + 2.688, 2.682, 2.676, 2.667, 2.658, 2.646, 2.635, 2.628, 2.623, 2.621, 2.621, 2.621, 2.623, 2.625, 2.629, 2.634, 2.64, 2.648, 2.658, 2.669, 2.679, 2.691, 2.703, 2.716, 2.729, 2.742, 2.755, 2.768, 2.776, 2.783, 2.79, 2.796, + 2.694, 2.689, 2.683, 2.676, 2.67, 2.658, 2.646, 2.64, 2.635, 2.634, 2.634, 2.635, 2.637, 2.64, 2.644, 2.649, 2.655, 2.663, 2.673, 2.682, 2.691, 2.7, 2.712, 2.725, 2.738, 2.752, 2.764, 2.775, 2.782, 2.788, 2.795, 2.802, + 2.697, 2.693, 2.689, 2.682, 2.676, 2.666, 2.655, 2.65, 2.645, 2.644, 2.644, 2.645, 2.647, 2.649, 2.652, 2.657, 2.663, 2.671, 2.68, 2.69, 2.699, 2.709, 2.722, 2.735, 2.747, 2.76, 2.77, 2.78, 2.787, 2.793, 2.798, 2.804, + 2.7, 2.697, 2.694, 2.687, 2.68, 2.672, 2.664, 2.659, 2.655, 2.654, 2.654, 2.655, 2.655, 2.657, 2.66, 2.664, 2.671, 2.678, 2.687, 2.696, 2.707, 2.718, 2.732, 2.744, 2.756, 2.767, 2.776, 2.785, 2.791, 2.798, 2.801, 2.804, + 2.702, 2.701, 2.699, 2.692, 2.685, 2.679, 2.672, 2.668, 2.665, 2.664, 2.664, 2.664, 2.664, 2.665, 2.667, 2.671, 2.678, 2.685, 2.693, 2.703, 2.715, 2.728, 2.741, 2.754, 2.764, 2.774, 2.782, 2.789, 2.796, 2.803, 2.804, 2.805 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 2800, + "table": + [ + 2.876, 2.872, 2.868, 2.866, 2.863, 2.858, 2.852, 2.849, 2.847, 2.846, 2.846, 2.846, 2.847, 2.848, 2.85, 2.851, 2.851, 2.852, 2.855, 2.859, 2.864, 2.868, 2.872, 2.877, 2.884, 2.89, 2.895, 2.9, 2.907, 2.913, 2.92, 2.926, + 2.871, 2.869, 2.866, 2.863, 2.861, 2.856, 2.85, 2.848, 2.846, 2.846, 2.846, 2.847, 2.847, 2.848, 2.85, 2.852, 2.853, 2.854, 2.856, 2.86, 2.866, 2.871, 2.875, 2.879, 2.884, 2.889, 2.894, 2.899, 2.905, 2.912, 2.917, 2.923, + 2.867, 2.865, 2.863, 2.861, 2.858, 2.854, 2.848, 2.847, 2.846, 2.846, 2.847, 2.847, 2.848, 2.849, 2.85, 2.852, 2.854, 2.856, 2.858, 2.861, 2.868, 2.874, 2.877, 2.881, 2.884, 2.888, 2.893, 2.898, 2.904, 2.91, 2.915, 2.92, + 2.863, 2.862, 2.861, 2.858, 2.856, 2.851, 2.847, 2.846, 2.846, 2.846, 2.847, 2.848, 2.849, 2.85, 2.852, 2.854, 2.857, 2.859, 2.86, 2.864, 2.871, 2.877, 2.88, 2.883, 2.884, 2.887, 2.892, 2.896, 2.903, 2.909, 2.913, 2.917, + 2.862, 2.861, 2.859, 2.856, 2.852, 2.848, 2.845, 2.844, 2.844, 2.846, 2.849, 2.852, 2.855, 2.857, 2.86, 2.863, 2.868, 2.87, 2.871, 2.873, 2.877, 2.88, 2.881, 2.883, 2.885, 2.887, 2.89, 2.894, 2.9, 2.906, 2.911, 2.915, + 2.861, 2.859, 2.857, 2.853, 2.849, 2.846, 2.843, 2.842, 2.842, 2.846, 2.851, 2.856, 2.861, 2.865, 2.868, 2.873, 2.878, 2.881, 2.881, 2.882, 2.882, 2.883, 2.883, 2.883, 2.885, 2.886, 2.889, 2.891, 2.897, 2.903, 2.909, 2.914, + 2.861, 2.858, 2.856, 2.851, 2.847, 2.844, 2.842, 2.842, 2.843, 2.848, 2.854, 2.861, 2.867, 2.872, 2.876, 2.881, 2.887, 2.89, 2.89, 2.889, 2.888, 2.887, 2.885, 2.884, 2.886, 2.887, 2.888, 2.89, 2.896, 2.901, 2.907, 2.912, + 2.86, 2.857, 2.854, 2.85, 2.846, 2.845, 2.844, 2.845, 2.847, 2.852, 2.859, 2.865, 2.872, 2.878, 2.883, 2.887, 2.892, 2.895, 2.895, 2.894, 2.893, 2.892, 2.889, 2.887, 2.888, 2.889, 2.89, 2.892, 2.897, 2.901, 2.906, 2.911, + 2.858, 2.855, 2.852, 2.849, 2.846, 2.846, 2.845, 2.848, 2.852, 2.857, 2.863, 2.87, 2.878, 2.884, 2.889, 2.894, 2.898, 2.9, 2.9, 2.899, 2.899, 2.897, 2.893, 2.89, 2.89, 2.89, 2.892, 2.894, 2.897, 2.901, 2.905, 2.91, + 2.858, 2.855, 2.851, 2.849, 2.846, 2.846, 2.846, 2.85, 2.856, 2.862, 2.868, 2.875, 2.883, 2.889, 2.894, 2.898, 2.902, 2.904, 2.904, 2.904, 2.903, 2.901, 2.896, 2.893, 2.892, 2.892, 2.894, 2.895, 2.899, 2.902, 2.905, 2.909, + 2.858, 2.855, 2.851, 2.849, 2.846, 2.846, 2.846, 2.852, 2.86, 2.867, 2.874, 2.881, 2.887, 2.893, 2.897, 2.901, 2.904, 2.907, 2.908, 2.909, 2.907, 2.905, 2.9, 2.896, 2.894, 2.893, 2.895, 2.897, 2.9, 2.903, 2.906, 2.909, + 2.858, 2.855, 2.851, 2.849, 2.846, 2.846, 2.846, 2.854, 2.863, 2.872, 2.88, 2.886, 2.892, 2.896, 2.9, 2.903, 2.907, 2.91, 2.912, 2.913, 2.911, 2.908, 2.904, 2.899, 2.897, 2.895, 2.896, 2.898, 2.901, 2.904, 2.906, 2.909, + 2.858, 2.855, 2.851, 2.849, 2.847, 2.847, 2.848, 2.856, 2.866, 2.875, 2.882, 2.889, 2.894, 2.899, 2.902, 2.906, 2.909, 2.912, 2.915, 2.916, 2.914, 2.911, 2.907, 2.903, 2.899, 2.897, 2.898, 2.899, 2.902, 2.904, 2.907, 2.909, + 2.858, 2.855, 2.851, 2.85, 2.848, 2.849, 2.85, 2.858, 2.869, 2.877, 2.884, 2.89, 2.896, 2.901, 2.905, 2.908, 2.912, 2.915, 2.918, 2.918, 2.916, 2.913, 2.91, 2.906, 2.902, 2.899, 2.899, 2.899, 2.902, 2.905, 2.907, 2.908, + 2.858, 2.855, 2.851, 2.85, 2.849, 2.851, 2.852, 2.861, 2.871, 2.879, 2.886, 2.892, 2.898, 2.903, 2.907, 2.911, 2.915, 2.918, 2.92, 2.921, 2.918, 2.916, 2.913, 2.909, 2.905, 2.901, 2.9, 2.899, 2.902, 2.905, 2.907, 2.908, + 2.859, 2.856, 2.853, 2.851, 2.85, 2.852, 2.853, 2.862, 2.871, 2.879, 2.886, 2.892, 2.898, 2.904, 2.908, 2.912, 2.916, 2.918, 2.921, 2.921, 2.919, 2.917, 2.914, 2.91, 2.905, 2.901, 2.9, 2.9, 2.903, 2.906, 2.907, 2.908, + 2.86, 2.857, 2.854, 2.853, 2.852, 2.853, 2.854, 2.862, 2.871, 2.879, 2.886, 2.892, 2.898, 2.904, 2.909, 2.913, 2.916, 2.919, 2.921, 2.922, 2.92, 2.918, 2.914, 2.91, 2.905, 2.901, 2.901, 2.901, 2.904, 2.906, 2.907, 2.908, + 2.861, 2.858, 2.855, 2.854, 2.853, 2.854, 2.855, 2.862, 2.871, 2.879, 2.886, 2.892, 2.898, 2.904, 2.91, 2.914, 2.917, 2.919, 2.921, 2.922, 2.921, 2.919, 2.914, 2.91, 2.905, 2.901, 2.901, 2.902, 2.904, 2.907, 2.908, 2.908, + 2.861, 2.859, 2.857, 2.855, 2.854, 2.854, 2.855, 2.862, 2.871, 2.878, 2.885, 2.891, 2.898, 2.903, 2.908, 2.912, 2.915, 2.918, 2.919, 2.919, 2.918, 2.916, 2.912, 2.909, 2.906, 2.903, 2.903, 2.904, 2.906, 2.907, 2.908, 2.908, + 2.862, 2.86, 2.858, 2.856, 2.855, 2.855, 2.856, 2.862, 2.87, 2.877, 2.884, 2.89, 2.897, 2.902, 2.906, 2.91, 2.914, 2.916, 2.918, 2.917, 2.915, 2.913, 2.91, 2.908, 2.906, 2.905, 2.905, 2.906, 2.907, 2.908, 2.908, 2.909, + 2.862, 2.861, 2.859, 2.858, 2.856, 2.856, 2.857, 2.863, 2.87, 2.876, 2.883, 2.889, 2.895, 2.9, 2.904, 2.908, 2.912, 2.914, 2.915, 2.915, 2.912, 2.91, 2.908, 2.907, 2.907, 2.907, 2.907, 2.907, 2.908, 2.909, 2.909, 2.909, + 2.862, 2.862, 2.861, 2.859, 2.857, 2.858, 2.859, 2.864, 2.87, 2.876, 2.881, 2.886, 2.891, 2.896, 2.901, 2.905, 2.909, 2.911, 2.911, 2.911, 2.909, 2.907, 2.906, 2.906, 2.906, 2.907, 2.908, 2.908, 2.909, 2.91, 2.911, 2.911, + 2.863, 2.863, 2.862, 2.86, 2.858, 2.86, 2.862, 2.866, 2.87, 2.875, 2.88, 2.884, 2.887, 2.891, 2.897, 2.902, 2.905, 2.907, 2.907, 2.907, 2.906, 2.905, 2.905, 2.905, 2.906, 2.907, 2.908, 2.909, 2.91, 2.912, 2.912, 2.912, + 2.863, 2.863, 2.863, 2.862, 2.86, 2.862, 2.864, 2.867, 2.87, 2.874, 2.878, 2.881, 2.883, 2.888, 2.894, 2.898, 2.901, 2.903, 2.903, 2.903, 2.903, 2.903, 2.903, 2.904, 2.906, 2.907, 2.908, 2.91, 2.912, 2.913, 2.913, 2.914, + 2.865, 2.864, 2.864, 2.863, 2.862, 2.864, 2.865, 2.867, 2.869, 2.872, 2.875, 2.878, 2.882, 2.886, 2.89, 2.893, 2.895, 2.897, 2.899, 2.899, 2.899, 2.9, 2.902, 2.903, 2.905, 2.907, 2.909, 2.911, 2.912, 2.914, 2.914, 2.915, + 2.866, 2.865, 2.865, 2.865, 2.864, 2.865, 2.866, 2.867, 2.868, 2.87, 2.872, 2.876, 2.88, 2.883, 2.886, 2.888, 2.89, 2.892, 2.894, 2.896, 2.896, 2.897, 2.9, 2.903, 2.905, 2.907, 2.91, 2.913, 2.913, 2.914, 2.915, 2.916, + 2.868, 2.868, 2.867, 2.867, 2.866, 2.867, 2.868, 2.868, 2.869, 2.87, 2.871, 2.874, 2.877, 2.879, 2.881, 2.883, 2.885, 2.888, 2.891, 2.893, 2.894, 2.896, 2.898, 2.901, 2.904, 2.907, 2.91, 2.913, 2.914, 2.915, 2.916, 2.918, + 2.871, 2.871, 2.871, 2.87, 2.869, 2.869, 2.869, 2.869, 2.87, 2.87, 2.871, 2.872, 2.874, 2.875, 2.875, 2.877, 2.881, 2.885, 2.889, 2.892, 2.893, 2.895, 2.897, 2.899, 2.903, 2.907, 2.91, 2.914, 2.915, 2.916, 2.918, 2.919, + 2.874, 2.874, 2.874, 2.873, 2.871, 2.871, 2.871, 2.871, 2.871, 2.871, 2.871, 2.871, 2.87, 2.87, 2.87, 2.872, 2.876, 2.881, 2.886, 2.89, 2.893, 2.894, 2.895, 2.897, 2.902, 2.907, 2.911, 2.914, 2.916, 2.917, 2.919, 2.921, + 2.877, 2.877, 2.876, 2.874, 2.873, 2.872, 2.872, 2.872, 2.871, 2.871, 2.871, 2.87, 2.869, 2.869, 2.869, 2.871, 2.874, 2.878, 2.883, 2.887, 2.891, 2.893, 2.894, 2.896, 2.901, 2.907, 2.911, 2.914, 2.916, 2.918, 2.919, 2.921, + 2.88, 2.879, 2.878, 2.876, 2.874, 2.874, 2.873, 2.872, 2.871, 2.871, 2.871, 2.87, 2.869, 2.869, 2.869, 2.87, 2.871, 2.874, 2.879, 2.884, 2.889, 2.892, 2.894, 2.896, 2.901, 2.906, 2.91, 2.914, 2.916, 2.918, 2.92, 2.921, + 2.882, 2.881, 2.879, 2.878, 2.876, 2.875, 2.874, 2.873, 2.871, 2.871, 2.871, 2.87, 2.869, 2.869, 2.869, 2.869, 2.869, 2.871, 2.875, 2.881, 2.887, 2.891, 2.893, 2.895, 2.901, 2.906, 2.91, 2.914, 2.917, 2.919, 2.92, 2.921 + ] + }, + { + "ct": 5500, + "table": + [ + 1.488, 1.488, 1.488, 1.488, 1.488, 1.488, 1.488, 1.489, 1.491, 1.491, 1.492, 1.492, 1.492, 1.492, 1.491, 1.491, 1.491, 1.491, 1.491, 1.491, 1.492, 1.492, 1.494, 1.495, 1.496, 1.497, 1.498, 1.499, 1.499, 1.499, 1.501, 1.503, + 1.486, 1.486, 1.487, 1.487, 1.487, 1.487, 1.488, 1.489, 1.49, 1.491, 1.492, 1.492, 1.492, 1.492, 1.492, 1.491, 1.491, 1.491, 1.491, 1.492, 1.492, 1.493, 1.494, 1.495, 1.495, 1.495, 1.496, 1.496, 1.497, 1.497, 1.498, 1.5, + 1.484, 1.485, 1.486, 1.486, 1.486, 1.486, 1.487, 1.488, 1.489, 1.49, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.493, 1.494, 1.494, 1.494, 1.494, 1.493, 1.493, 1.493, 1.494, 1.495, 1.496, 1.497, + 1.482, 1.483, 1.485, 1.485, 1.485, 1.486, 1.487, 1.488, 1.489, 1.49, 1.491, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.492, 1.493, 1.493, 1.494, 1.494, 1.494, 1.493, 1.492, 1.491, 1.491, 1.492, 1.493, 1.493, 1.494, + 1.482, 1.483, 1.484, 1.484, 1.485, 1.485, 1.486, 1.487, 1.488, 1.49, 1.491, 1.493, 1.493, 1.494, 1.494, 1.495, 1.495, 1.495, 1.495, 1.494, 1.494, 1.494, 1.493, 1.493, 1.492, 1.492, 1.491, 1.491, 1.492, 1.492, 1.492, 1.493, + 1.482, 1.482, 1.483, 1.483, 1.484, 1.485, 1.485, 1.486, 1.487, 1.489, 1.491, 1.493, 1.494, 1.496, 1.496, 1.497, 1.497, 1.497, 1.497, 1.496, 1.495, 1.494, 1.493, 1.492, 1.492, 1.491, 1.491, 1.491, 1.491, 1.491, 1.492, 1.492, + 1.482, 1.482, 1.482, 1.483, 1.484, 1.484, 1.485, 1.486, 1.487, 1.489, 1.492, 1.494, 1.496, 1.497, 1.498, 1.498, 1.499, 1.499, 1.498, 1.497, 1.496, 1.494, 1.493, 1.492, 1.491, 1.491, 1.491, 1.491, 1.491, 1.491, 1.491, 1.491, + 1.482, 1.482, 1.482, 1.482, 1.483, 1.484, 1.485, 1.487, 1.488, 1.491, 1.493, 1.495, 1.496, 1.497, 1.498, 1.499, 1.5, 1.5, 1.499, 1.498, 1.497, 1.495, 1.494, 1.492, 1.492, 1.491, 1.49, 1.49, 1.49, 1.49, 1.49, 1.49, + 1.481, 1.481, 1.481, 1.482, 1.482, 1.483, 1.485, 1.487, 1.49, 1.492, 1.495, 1.496, 1.497, 1.498, 1.499, 1.499, 1.5, 1.5, 1.499, 1.499, 1.498, 1.497, 1.494, 1.493, 1.492, 1.491, 1.49, 1.488, 1.488, 1.488, 1.488, 1.488, + 1.481, 1.481, 1.481, 1.481, 1.482, 1.483, 1.484, 1.487, 1.49, 1.493, 1.495, 1.497, 1.498, 1.498, 1.499, 1.5, 1.5, 1.501, 1.5, 1.499, 1.498, 1.497, 1.495, 1.492, 1.491, 1.49, 1.489, 1.487, 1.487, 1.487, 1.487, 1.487, + 1.481, 1.481, 1.481, 1.481, 1.481, 1.482, 1.484, 1.487, 1.49, 1.493, 1.496, 1.497, 1.498, 1.498, 1.499, 1.5, 1.5, 1.501, 1.5, 1.499, 1.498, 1.497, 1.494, 1.492, 1.491, 1.489, 1.488, 1.486, 1.486, 1.485, 1.485, 1.485, + 1.481, 1.481, 1.481, 1.481, 1.481, 1.482, 1.483, 1.486, 1.49, 1.493, 1.496, 1.497, 1.498, 1.498, 1.499, 1.5, 1.5, 1.501, 1.5, 1.499, 1.498, 1.497, 1.494, 1.492, 1.49, 1.488, 1.487, 1.485, 1.484, 1.483, 1.483, 1.483, + 1.48, 1.48, 1.48, 1.481, 1.481, 1.482, 1.483, 1.486, 1.489, 1.493, 1.496, 1.497, 1.497, 1.498, 1.499, 1.499, 1.5, 1.5, 1.499, 1.499, 1.498, 1.496, 1.494, 1.491, 1.489, 1.487, 1.485, 1.484, 1.483, 1.483, 1.483, 1.482, + 1.48, 1.48, 1.48, 1.48, 1.481, 1.482, 1.482, 1.485, 1.489, 1.492, 1.495, 1.496, 1.497, 1.498, 1.498, 1.499, 1.499, 1.5, 1.499, 1.498, 1.497, 1.495, 1.493, 1.491, 1.488, 1.486, 1.484, 1.483, 1.483, 1.482, 1.482, 1.482, + 1.479, 1.479, 1.479, 1.48, 1.481, 1.481, 1.482, 1.485, 1.488, 1.491, 1.494, 1.496, 1.497, 1.497, 1.498, 1.498, 1.499, 1.499, 1.499, 1.498, 1.496, 1.495, 1.493, 1.491, 1.488, 1.485, 1.483, 1.482, 1.482, 1.482, 1.482, 1.481, + 1.479, 1.479, 1.479, 1.48, 1.48, 1.481, 1.482, 1.485, 1.488, 1.491, 1.494, 1.495, 1.496, 1.497, 1.497, 1.498, 1.498, 1.498, 1.498, 1.497, 1.496, 1.494, 1.492, 1.49, 1.487, 1.484, 1.483, 1.482, 1.481, 1.481, 1.48, 1.48, + 1.479, 1.479, 1.479, 1.479, 1.48, 1.48, 1.481, 1.484, 1.488, 1.491, 1.493, 1.495, 1.496, 1.497, 1.497, 1.497, 1.498, 1.497, 1.497, 1.497, 1.496, 1.494, 1.492, 1.489, 1.486, 1.483, 1.482, 1.481, 1.481, 1.48, 1.479, 1.478, + 1.479, 1.479, 1.479, 1.479, 1.479, 1.48, 1.481, 1.484, 1.488, 1.491, 1.493, 1.495, 1.496, 1.496, 1.497, 1.497, 1.497, 1.497, 1.496, 1.496, 1.495, 1.494, 1.491, 1.488, 1.485, 1.482, 1.481, 1.481, 1.48, 1.479, 1.478, 1.477, + 1.479, 1.479, 1.479, 1.479, 1.479, 1.48, 1.481, 1.484, 1.487, 1.49, 1.492, 1.494, 1.495, 1.496, 1.496, 1.497, 1.497, 1.496, 1.496, 1.495, 1.494, 1.493, 1.49, 1.487, 1.484, 1.482, 1.481, 1.48, 1.479, 1.479, 1.478, 1.477, + 1.478, 1.478, 1.478, 1.479, 1.479, 1.48, 1.481, 1.484, 1.487, 1.489, 1.491, 1.493, 1.494, 1.495, 1.496, 1.496, 1.496, 1.496, 1.496, 1.495, 1.494, 1.492, 1.489, 1.487, 1.484, 1.482, 1.481, 1.479, 1.479, 1.478, 1.477, 1.476, + 1.478, 1.478, 1.478, 1.478, 1.479, 1.48, 1.481, 1.483, 1.486, 1.488, 1.49, 1.492, 1.493, 1.494, 1.495, 1.496, 1.496, 1.495, 1.495, 1.494, 1.493, 1.491, 1.488, 1.486, 1.484, 1.482, 1.48, 1.479, 1.478, 1.478, 1.477, 1.476, + 1.478, 1.478, 1.478, 1.478, 1.479, 1.48, 1.481, 1.483, 1.486, 1.488, 1.489, 1.491, 1.492, 1.493, 1.494, 1.495, 1.495, 1.494, 1.494, 1.493, 1.491, 1.489, 1.487, 1.485, 1.483, 1.481, 1.48, 1.479, 1.478, 1.477, 1.477, 1.476, + 1.478, 1.478, 1.478, 1.478, 1.479, 1.48, 1.482, 1.484, 1.486, 1.487, 1.488, 1.49, 1.491, 1.492, 1.493, 1.494, 1.494, 1.493, 1.493, 1.492, 1.489, 1.487, 1.486, 1.484, 1.483, 1.481, 1.48, 1.479, 1.478, 1.476, 1.476, 1.476, + 1.478, 1.478, 1.478, 1.479, 1.479, 1.481, 1.482, 1.484, 1.485, 1.486, 1.487, 1.489, 1.49, 1.491, 1.492, 1.492, 1.492, 1.492, 1.491, 1.49, 1.488, 1.486, 1.485, 1.483, 1.482, 1.481, 1.48, 1.479, 1.477, 1.476, 1.476, 1.476, + 1.477, 1.478, 1.478, 1.479, 1.48, 1.481, 1.482, 1.484, 1.485, 1.486, 1.487, 1.488, 1.489, 1.49, 1.49, 1.49, 1.49, 1.49, 1.49, 1.489, 1.487, 1.485, 1.484, 1.483, 1.482, 1.481, 1.48, 1.479, 1.477, 1.476, 1.476, 1.476, + 1.477, 1.478, 1.479, 1.48, 1.481, 1.482, 1.483, 1.484, 1.485, 1.486, 1.486, 1.487, 1.488, 1.488, 1.489, 1.488, 1.488, 1.488, 1.488, 1.487, 1.485, 1.484, 1.484, 1.483, 1.482, 1.481, 1.48, 1.479, 1.477, 1.476, 1.476, 1.476, + 1.477, 1.478, 1.479, 1.48, 1.481, 1.482, 1.483, 1.484, 1.485, 1.485, 1.486, 1.486, 1.487, 1.487, 1.487, 1.486, 1.486, 1.486, 1.486, 1.486, 1.485, 1.484, 1.483, 1.483, 1.482, 1.481, 1.48, 1.479, 1.477, 1.476, 1.476, 1.476, + 1.477, 1.478, 1.479, 1.48, 1.481, 1.482, 1.483, 1.484, 1.484, 1.485, 1.485, 1.486, 1.486, 1.485, 1.485, 1.484, 1.484, 1.484, 1.485, 1.485, 1.484, 1.483, 1.483, 1.482, 1.482, 1.481, 1.48, 1.479, 1.478, 1.477, 1.476, 1.476, + 1.477, 1.478, 1.479, 1.48, 1.482, 1.482, 1.483, 1.483, 1.484, 1.484, 1.485, 1.485, 1.484, 1.484, 1.483, 1.482, 1.482, 1.483, 1.484, 1.484, 1.483, 1.483, 1.482, 1.482, 1.481, 1.481, 1.48, 1.479, 1.478, 1.477, 1.476, 1.476, + 1.477, 1.478, 1.479, 1.48, 1.482, 1.482, 1.483, 1.483, 1.484, 1.484, 1.485, 1.484, 1.484, 1.483, 1.482, 1.482, 1.482, 1.482, 1.483, 1.483, 1.483, 1.483, 1.482, 1.482, 1.481, 1.48, 1.48, 1.479, 1.478, 1.478, 1.477, 1.477, + 1.477, 1.478, 1.479, 1.48, 1.482, 1.482, 1.483, 1.483, 1.484, 1.484, 1.484, 1.484, 1.483, 1.483, 1.482, 1.482, 1.482, 1.482, 1.483, 1.483, 1.482, 1.482, 1.482, 1.481, 1.48, 1.48, 1.479, 1.479, 1.479, 1.478, 1.478, 1.478, + 1.477, 1.478, 1.479, 1.48, 1.482, 1.482, 1.483, 1.483, 1.484, 1.484, 1.484, 1.483, 1.482, 1.482, 1.482, 1.482, 1.482, 1.482, 1.482, 1.482, 1.482, 1.482, 1.481, 1.481, 1.48, 1.479, 1.479, 1.479, 1.479, 1.479, 1.479, 1.479 + ] + } + ], + "luminance_lut": + [ + 2.764, 2.711, 2.658, 2.504, 2.342, 2.204, 2.07, 1.937, 1.803, 1.706, 1.622, 1.582, 1.565, 1.558, 1.558, 1.558, 1.558, 1.56, 1.565, 1.586, 1.631, 1.7, 1.818, 1.941, 2.081, 2.222, 2.37, 2.522, 2.711, 2.893, 2.968, 3.043, + 2.725, 2.642, 2.56, 2.405, 2.246, 2.115, 1.989, 1.86, 1.732, 1.642, 1.567, 1.527, 1.504, 1.493, 1.488, 1.486, 1.486, 1.492, 1.503, 1.528, 1.574, 1.64, 1.746, 1.86, 1.995, 2.131, 2.274, 2.423, 2.608, 2.788, 2.888, 2.988, + 2.686, 2.574, 2.462, 2.307, 2.149, 2.026, 1.908, 1.784, 1.66, 1.578, 1.511, 1.471, 1.443, 1.427, 1.419, 1.415, 1.415, 1.423, 1.44, 1.47, 1.516, 1.579, 1.674, 1.779, 1.909, 2.04, 2.179, 2.323, 2.504, 2.682, 2.808, 2.933, + 2.636, 2.502, 2.368, 2.212, 2.055, 1.937, 1.825, 1.709, 1.592, 1.517, 1.457, 1.416, 1.385, 1.365, 1.353, 1.347, 1.347, 1.358, 1.38, 1.413, 1.46, 1.52, 1.605, 1.7, 1.824, 1.949, 2.085, 2.227, 2.405, 2.581, 2.725, 2.87, + 2.537, 2.413, 2.289, 2.133, 1.974, 1.853, 1.737, 1.637, 1.54, 1.468, 1.406, 1.366, 1.336, 1.317, 1.306, 1.301, 1.301, 1.311, 1.332, 1.362, 1.407, 1.464, 1.545, 1.633, 1.743, 1.858, 1.999, 2.144, 2.323, 2.5, 2.635, 2.771, + 2.439, 2.325, 2.211, 2.053, 1.892, 1.768, 1.649, 1.564, 1.488, 1.42, 1.355, 1.315, 1.288, 1.27, 1.259, 1.254, 1.254, 1.263, 1.283, 1.312, 1.355, 1.409, 1.485, 1.567, 1.662, 1.767, 1.912, 2.062, 2.242, 2.419, 2.545, 2.672, + 2.362, 2.251, 2.14, 1.982, 1.82, 1.695, 1.576, 1.501, 1.437, 1.375, 1.313, 1.273, 1.243, 1.225, 1.214, 1.209, 1.209, 1.218, 1.238, 1.267, 1.31, 1.361, 1.43, 1.504, 1.591, 1.689, 1.835, 1.986, 2.167, 2.345, 2.469, 2.594, + 2.323, 2.202, 2.081, 1.924, 1.764, 1.643, 1.529, 1.454, 1.389, 1.335, 1.286, 1.244, 1.206, 1.184, 1.172, 1.166, 1.166, 1.177, 1.201, 1.233, 1.278, 1.327, 1.385, 1.45, 1.537, 1.634, 1.776, 1.924, 2.105, 2.283, 2.418, 2.552, + 2.285, 2.154, 2.023, 1.866, 1.708, 1.592, 1.481, 1.406, 1.341, 1.296, 1.258, 1.215, 1.169, 1.143, 1.13, 1.124, 1.124, 1.137, 1.164, 1.199, 1.246, 1.292, 1.339, 1.397, 1.483, 1.58, 1.717, 1.861, 2.043, 2.222, 2.366, 2.51, + 2.258, 2.116, 1.975, 1.82, 1.665, 1.552, 1.446, 1.372, 1.306, 1.262, 1.227, 1.185, 1.141, 1.113, 1.097, 1.089, 1.089, 1.103, 1.134, 1.17, 1.214, 1.259, 1.303, 1.358, 1.443, 1.537, 1.671, 1.81, 1.992, 2.171, 2.325, 2.479, + 2.24, 2.088, 1.936, 1.784, 1.631, 1.522, 1.42, 1.347, 1.282, 1.234, 1.192, 1.156, 1.121, 1.093, 1.07, 1.06, 1.06, 1.076, 1.11, 1.146, 1.184, 1.225, 1.274, 1.332, 1.414, 1.505, 1.634, 1.77, 1.951, 2.13, 2.294, 2.457, + 2.223, 2.06, 1.898, 1.747, 1.597, 1.492, 1.394, 1.323, 1.258, 1.206, 1.158, 1.126, 1.101, 1.074, 1.044, 1.03, 1.031, 1.049, 1.087, 1.122, 1.153, 1.192, 1.245, 1.306, 1.385, 1.473, 1.598, 1.73, 1.91, 2.089, 2.262, 2.435, + 2.218, 2.047, 1.876, 1.727, 1.579, 1.476, 1.38, 1.309, 1.245, 1.19, 1.138, 1.104, 1.078, 1.053, 1.028, 1.018, 1.019, 1.035, 1.065, 1.097, 1.131, 1.172, 1.228, 1.29, 1.367, 1.453, 1.575, 1.705, 1.884, 2.062, 2.245, 2.428, + 2.218, 2.039, 1.861, 1.712, 1.566, 1.465, 1.37, 1.299, 1.234, 1.178, 1.124, 1.085, 1.054, 1.032, 1.017, 1.012, 1.015, 1.026, 1.045, 1.072, 1.112, 1.158, 1.216, 1.279, 1.354, 1.438, 1.559, 1.686, 1.863, 2.041, 2.234, 2.427, + 2.218, 2.032, 1.846, 1.698, 1.553, 1.453, 1.36, 1.29, 1.224, 1.166, 1.109, 1.066, 1.03, 1.012, 1.006, 1.005, 1.011, 1.017, 1.024, 1.047, 1.093, 1.143, 1.203, 1.267, 1.341, 1.424, 1.542, 1.667, 1.842, 2.02, 2.223, 2.426, + 2.218, 2.031, 1.844, 1.697, 1.552, 1.452, 1.36, 1.289, 1.223, 1.164, 1.108, 1.064, 1.027, 1.009, 1.004, 1.004, 1.009, 1.015, 1.022, 1.045, 1.091, 1.142, 1.202, 1.266, 1.34, 1.423, 1.54, 1.665, 1.841, 2.018, 2.222, 2.426, + 2.218, 2.031, 1.844, 1.697, 1.552, 1.452, 1.36, 1.289, 1.223, 1.164, 1.108, 1.064, 1.027, 1.009, 1.004, 1.004, 1.008, 1.014, 1.021, 1.045, 1.091, 1.142, 1.202, 1.266, 1.34, 1.423, 1.54, 1.665, 1.841, 2.018, 2.222, 2.426, + 2.218, 2.032, 1.846, 1.699, 1.554, 1.454, 1.361, 1.29, 1.225, 1.166, 1.11, 1.066, 1.028, 1.01, 1.004, 1.004, 1.007, 1.014, 1.023, 1.047, 1.093, 1.143, 1.203, 1.267, 1.341, 1.424, 1.542, 1.667, 1.842, 2.02, 2.223, 2.426, + 2.22, 2.044, 1.869, 1.719, 1.572, 1.471, 1.376, 1.305, 1.24, 1.183, 1.129, 1.089, 1.054, 1.031, 1.014, 1.009, 1.013, 1.026, 1.048, 1.077, 1.115, 1.159, 1.218, 1.28, 1.356, 1.44, 1.56, 1.687, 1.863, 2.041, 2.234, 2.427, + 2.222, 2.056, 1.891, 1.74, 1.591, 1.487, 1.391, 1.319, 1.254, 1.2, 1.149, 1.111, 1.08, 1.052, 1.025, 1.015, 1.019, 1.038, 1.073, 1.106, 1.136, 1.175, 1.232, 1.294, 1.371, 1.457, 1.578, 1.706, 1.884, 2.062, 2.245, 2.427, + 2.231, 2.074, 1.918, 1.766, 1.614, 1.508, 1.41, 1.338, 1.273, 1.22, 1.173, 1.136, 1.106, 1.074, 1.041, 1.028, 1.033, 1.055, 1.097, 1.134, 1.16, 1.196, 1.251, 1.311, 1.39, 1.477, 1.601, 1.732, 1.911, 2.089, 2.262, 2.434, + 2.255, 2.108, 1.961, 1.807, 1.652, 1.542, 1.439, 1.365, 1.299, 1.25, 1.207, 1.167, 1.13, 1.099, 1.074, 1.063, 1.066, 1.084, 1.119, 1.155, 1.192, 1.233, 1.281, 1.338, 1.419, 1.509, 1.637, 1.772, 1.953, 2.132, 2.294, 2.456, + 2.279, 2.142, 2.004, 1.848, 1.69, 1.576, 1.468, 1.393, 1.326, 1.279, 1.241, 1.198, 1.153, 1.125, 1.107, 1.099, 1.1, 1.113, 1.141, 1.177, 1.223, 1.269, 1.312, 1.366, 1.449, 1.541, 1.673, 1.812, 1.995, 2.175, 2.327, 2.479, + 2.317, 2.187, 2.057, 1.898, 1.737, 1.619, 1.508, 1.431, 1.363, 1.314, 1.273, 1.229, 1.183, 1.157, 1.143, 1.137, 1.137, 1.148, 1.171, 1.205, 1.255, 1.304, 1.35, 1.406, 1.49, 1.584, 1.72, 1.863, 2.047, 2.228, 2.37, 2.512, + 2.37, 2.247, 2.123, 1.961, 1.797, 1.675, 1.559, 1.481, 1.413, 1.356, 1.303, 1.259, 1.22, 1.196, 1.184, 1.178, 1.178, 1.188, 1.21, 1.242, 1.287, 1.337, 1.396, 1.462, 1.545, 1.64, 1.781, 1.928, 2.112, 2.292, 2.425, 2.559, + 2.424, 2.306, 2.188, 2.024, 1.857, 1.731, 1.611, 1.532, 1.462, 1.397, 1.334, 1.29, 1.256, 1.236, 1.224, 1.219, 1.219, 1.229, 1.249, 1.278, 1.32, 1.371, 1.443, 1.518, 1.6, 1.695, 1.842, 1.994, 2.177, 2.356, 2.48, 2.605, + 2.513, 2.39, 2.266, 2.102, 1.935, 1.808, 1.687, 1.597, 1.516, 1.446, 1.379, 1.335, 1.302, 1.282, 1.271, 1.266, 1.266, 1.276, 1.296, 1.325, 1.366, 1.42, 1.497, 1.58, 1.674, 1.779, 1.923, 2.073, 2.255, 2.434, 2.563, 2.693, + 2.621, 2.486, 2.352, 2.189, 2.024, 1.897, 1.775, 1.672, 1.572, 1.498, 1.433, 1.388, 1.353, 1.333, 1.322, 1.316, 1.316, 1.326, 1.348, 1.378, 1.421, 1.476, 1.556, 1.645, 1.759, 1.877, 2.016, 2.159, 2.341, 2.52, 2.662, 2.804, + 2.728, 2.583, 2.438, 2.276, 2.113, 1.986, 1.864, 1.746, 1.628, 1.55, 1.487, 1.441, 1.405, 1.383, 1.372, 1.367, 1.367, 1.377, 1.399, 1.431, 1.476, 1.533, 1.615, 1.71, 1.843, 1.976, 2.108, 2.246, 2.427, 2.606, 2.76, 2.915, + 2.781, 2.661, 2.54, 2.378, 2.213, 2.08, 1.952, 1.826, 1.699, 1.613, 1.542, 1.497, 1.465, 1.447, 1.439, 1.436, 1.436, 1.444, 1.461, 1.489, 1.533, 1.594, 1.686, 1.791, 1.93, 2.069, 2.206, 2.349, 2.533, 2.714, 2.845, 2.977, + 2.822, 2.734, 2.646, 2.483, 2.316, 2.176, 2.04, 1.907, 1.774, 1.678, 1.597, 1.553, 1.527, 1.515, 1.511, 1.509, 1.509, 1.514, 1.525, 1.549, 1.591, 1.655, 1.76, 1.875, 2.018, 2.161, 2.305, 2.454, 2.643, 2.827, 2.927, 3.027, + 2.862, 2.807, 2.752, 2.589, 2.418, 2.271, 2.128, 1.988, 1.848, 1.744, 1.652, 1.608, 1.59, 1.582, 1.582, 1.582, 1.582, 1.584, 1.589, 1.608, 1.65, 1.716, 1.833, 1.958, 2.105, 2.253, 2.404, 2.56, 2.754, 2.94, 3.009, 3.078 + ], + "sigma": 0.00428, + "sigma_Cb": 0.00363 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2850, + "ccm": + [ + 1.42601, -0.20537, -0.22063, + -0.47682, 1.81987, -0.34305, + 0.01854, -0.86036, 1.84181 + ] + }, + { + "ct": 2900, + "ccm": + [ + 1.29755, 0.04602, -0.34356, + -0.41491, 1.73477, -0.31987, + -0.01345, -0.97115, 1.98459 + ] + }, + { + "ct": 3550, + "ccm": + [ + 1.49811, -0.33412, -0.16398, + -0.40869, 1.72995, -0.32127, + -0.01924, -0.62181, 1.64105 + ] + }, + { + "ct": 4500, + "ccm": + [ + 1.47015, -0.29229, -0.17786, + -0.36561, 1.88919, -0.52358, + -0.03552, -0.56717, 1.60269 + ] + }, + { + "ct": 5600, + "ccm": + [ + 1.60962, -0.47434, -0.13528, + -0.32701, 1.73797, -0.41096, + -0.07626, -0.40171, 1.47796 + ] + }, + { + "ct": 8000, + "ccm": + [ + 1.54642, -0.20396, -0.34246, + -0.31748, 2.22559, -0.90811, + -0.10035, -0.65877, 1.75912 + ] + } + ] + } + }, + { + "rpi.sharpen": { } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx415.json b/src/ipa/rpi/pisp/data/imx415.json new file mode 100755 index 00000000..824a5371 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx415.json @@ -0,0 +1,1159 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 19230, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 1198, + "reference_Y": 14876 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 17, + "reference_slope": 3.439 + } + }, + { + "rpi.geq": + { + "offset": 193, + "slope": 0.00902 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2698.0, 0.7746, 0.2063, + 2930.0, 0.7579, 0.2155, + 3643.0, 0.6412, 0.2905, + 4605.0, 0.5038, 0.4099, + 5658.0, 0.4541, 0.4634 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01128, + "transverse_neg": 0.01437 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.027, 1.027, 1.022, 1.018, 1.014, 1.012, 1.009, 1.009, 1.007, 1.006, 1.004, 1.002, 1.002, 1.001, 1.001, 1.001, 1.001, 1.002, 1.003, 1.004, 1.006, 1.008, 1.009, 1.011, 1.014, 1.018, 1.019, 1.026, 1.029, 1.033, 1.039, 1.039, + 1.039, 1.034, 1.031, 1.028, 1.027, 1.025, 1.023, 1.021, 1.019, 1.019, 1.017, 1.016, 1.016, 1.016, 1.016, 1.016, 1.016, 1.016, 1.017, 1.018, 1.019, 1.021, 1.024, 1.025, 1.027, 1.029, 1.032, 1.035, 1.037, 1.042, 1.046, 1.049, + 1.041, 1.038, 1.033, 1.029, 1.027, 1.026, 1.024, 1.023, 1.021, 1.019, 1.018, 1.017, 1.016, 1.016, 1.016, 1.017, 1.017, 1.017, 1.018, 1.019, 1.021, 1.022, 1.024, 1.026, 1.029, 1.031, 1.034, 1.037, 1.042, 1.044, 1.049, 1.052, + 1.041, 1.037, 1.032, 1.028, 1.027, 1.025, 1.023, 1.022, 1.021, 1.019, 1.018, 1.017, 1.016, 1.016, 1.016, 1.016, 1.016, 1.017, 1.018, 1.019, 1.021, 1.022, 1.024, 1.026, 1.028, 1.031, 1.033, 1.037, 1.039, 1.044, 1.047, 1.051, + 1.039, 1.036, 1.031, 1.027, 1.026, 1.024, 1.022, 1.021, 1.019, 1.018, 1.017, 1.016, 1.015, 1.015, 1.015, 1.015, 1.015, 1.016, 1.017, 1.018, 1.019, 1.021, 1.023, 1.025, 1.028, 1.031, 1.033, 1.036, 1.039, 1.043, 1.046, 1.049, + 1.037, 1.034, 1.029, 1.026, 1.024, 1.023, 1.021, 1.019, 1.018, 1.017, 1.016, 1.015, 1.015, 1.013, 1.014, 1.014, 1.014, 1.014, 1.016, 1.017, 1.019, 1.019, 1.022, 1.025, 1.027, 1.029, 1.033, 1.036, 1.039, 1.042, 1.045, 1.049, + 1.036, 1.031, 1.029, 1.025, 1.023, 1.021, 1.019, 1.018, 1.017, 1.016, 1.015, 1.014, 1.013, 1.013, 1.012, 1.013, 1.013, 1.014, 1.015, 1.016, 1.018, 1.019, 1.021, 1.023, 1.026, 1.029, 1.032, 1.035, 1.039, 1.041, 1.044, 1.048, + 1.034, 1.031, 1.028, 1.024, 1.022, 1.021, 1.018, 1.017, 1.016, 1.015, 1.014, 1.013, 1.012, 1.011, 1.011, 1.012, 1.012, 1.013, 1.014, 1.015, 1.017, 1.018, 1.021, 1.022, 1.025, 1.028, 1.032, 1.035, 1.038, 1.041, 1.044, 1.048, + 1.032, 1.031, 1.027, 1.023, 1.021, 1.019, 1.017, 1.016, 1.015, 1.014, 1.013, 1.012, 1.011, 1.011, 1.011, 1.011, 1.012, 1.012, 1.013, 1.014, 1.016, 1.018, 1.019, 1.021, 1.024, 1.027, 1.031, 1.033, 1.037, 1.041, 1.043, 1.048, + 1.031, 1.029, 1.025, 1.023, 1.021, 1.019, 1.017, 1.015, 1.014, 1.013, 1.012, 1.011, 1.011, 1.009, 1.009, 1.009, 1.011, 1.012, 1.012, 1.013, 1.015, 1.017, 1.019, 1.021, 1.023, 1.027, 1.031, 1.033, 1.036, 1.041, 1.043, 1.048, + 1.031, 1.029, 1.025, 1.023, 1.021, 1.018, 1.016, 1.015, 1.014, 1.012, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.011, 1.011, 1.012, 1.014, 1.015, 1.017, 1.019, 1.021, 1.023, 1.027, 1.031, 1.032, 1.036, 1.041, 1.043, 1.047, + 1.029, 1.027, 1.025, 1.022, 1.021, 1.018, 1.015, 1.014, 1.013, 1.012, 1.012, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.012, 1.014, 1.015, 1.017, 1.019, 1.021, 1.024, 1.028, 1.031, 1.033, 1.036, 1.041, 1.045, 1.048, + 1.029, 1.027, 1.024, 1.022, 1.021, 1.018, 1.016, 1.014, 1.013, 1.012, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.013, 1.014, 1.015, 1.016, 1.018, 1.021, 1.024, 1.027, 1.031, 1.033, 1.037, 1.041, 1.045, 1.047, + 1.029, 1.027, 1.024, 1.022, 1.021, 1.018, 1.016, 1.014, 1.013, 1.012, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.013, 1.013, 1.015, 1.016, 1.018, 1.021, 1.024, 1.027, 1.031, 1.034, 1.037, 1.041, 1.045, 1.048, + 1.029, 1.027, 1.024, 1.021, 1.021, 1.018, 1.016, 1.014, 1.013, 1.012, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.012, 1.013, 1.015, 1.016, 1.018, 1.019, 1.023, 1.027, 1.031, 1.033, 1.037, 1.039, 1.044, 1.049, + 1.029, 1.027, 1.023, 1.022, 1.021, 1.019, 1.016, 1.014, 1.013, 1.012, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.012, 1.013, 1.015, 1.016, 1.018, 1.021, 1.023, 1.028, 1.031, 1.034, 1.037, 1.039, 1.044, 1.049, + 1.029, 1.027, 1.024, 1.022, 1.021, 1.019, 1.017, 1.015, 1.013, 1.012, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.012, 1.014, 1.015, 1.017, 1.018, 1.021, 1.024, 1.028, 1.031, 1.034, 1.038, 1.041, 1.045, 1.049, + 1.031, 1.027, 1.025, 1.023, 1.022, 1.019, 1.018, 1.015, 1.014, 1.013, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.011, 1.011, 1.013, 1.014, 1.016, 1.017, 1.019, 1.021, 1.023, 1.028, 1.031, 1.034, 1.038, 1.041, 1.046, 1.051, + 1.031, 1.027, 1.025, 1.023, 1.022, 1.021, 1.018, 1.016, 1.014, 1.013, 1.012, 1.011, 1.011, 1.009, 1.011, 1.011, 1.011, 1.012, 1.013, 1.015, 1.016, 1.018, 1.019, 1.021, 1.024, 1.028, 1.032, 1.035, 1.039, 1.041, 1.046, 1.051, + 1.031, 1.028, 1.025, 1.023, 1.023, 1.019, 1.018, 1.016, 1.014, 1.014, 1.013, 1.012, 1.011, 1.011, 1.011, 1.011, 1.011, 1.012, 1.013, 1.015, 1.017, 1.018, 1.021, 1.022, 1.025, 1.028, 1.032, 1.035, 1.039, 1.043, 1.047, 1.051, + 1.031, 1.028, 1.025, 1.024, 1.023, 1.021, 1.018, 1.017, 1.015, 1.014, 1.013, 1.012, 1.011, 1.011, 1.011, 1.012, 1.012, 1.012, 1.014, 1.015, 1.018, 1.019, 1.021, 1.023, 1.026, 1.029, 1.033, 1.037, 1.041, 1.044, 1.048, 1.052, + 1.031, 1.028, 1.025, 1.024, 1.023, 1.022, 1.019, 1.017, 1.015, 1.015, 1.013, 1.012, 1.012, 1.012, 1.012, 1.013, 1.013, 1.014, 1.014, 1.017, 1.019, 1.019, 1.022, 1.024, 1.027, 1.031, 1.034, 1.038, 1.041, 1.046, 1.051, 1.055, + 1.031, 1.029, 1.026, 1.025, 1.023, 1.022, 1.019, 1.017, 1.016, 1.015, 1.014, 1.013, 1.012, 1.012, 1.013, 1.013, 1.014, 1.015, 1.016, 1.018, 1.019, 1.021, 1.023, 1.025, 1.028, 1.032, 1.035, 1.038, 1.042, 1.047, 1.052, 1.056, + 1.033, 1.031, 1.027, 1.025, 1.024, 1.023, 1.021, 1.019, 1.017, 1.016, 1.015, 1.014, 1.014, 1.013, 1.014, 1.014, 1.015, 1.016, 1.017, 1.019, 1.021, 1.022, 1.025, 1.027, 1.029, 1.033, 1.036, 1.039, 1.044, 1.048, 1.053, 1.056, + 1.036, 1.032, 1.029, 1.027, 1.026, 1.025, 1.022, 1.021, 1.019, 1.018, 1.017, 1.016, 1.015, 1.014, 1.015, 1.016, 1.017, 1.017, 1.018, 1.021, 1.022, 1.024, 1.027, 1.029, 1.031, 1.035, 1.038, 1.041, 1.046, 1.049, 1.054, 1.058, + 1.038, 1.035, 1.031, 1.029, 1.028, 1.026, 1.025, 1.022, 1.021, 1.019, 1.019, 1.018, 1.017, 1.016, 1.016, 1.017, 1.018, 1.019, 1.021, 1.022, 1.024, 1.025, 1.028, 1.031, 1.034, 1.036, 1.041, 1.043, 1.049, 1.052, 1.057, 1.059, + 1.041, 1.037, 1.034, 1.031, 1.029, 1.028, 1.027, 1.024, 1.022, 1.021, 1.021, 1.019, 1.019, 1.018, 1.018, 1.019, 1.019, 1.021, 1.022, 1.024, 1.025, 1.028, 1.031, 1.033, 1.035, 1.038, 1.041, 1.045, 1.051, 1.055, 1.059, 1.063, + 1.043, 1.039, 1.036, 1.033, 1.031, 1.029, 1.028, 1.027, 1.024, 1.023, 1.022, 1.021, 1.021, 1.021, 1.021, 1.021, 1.021, 1.022, 1.024, 1.026, 1.028, 1.029, 1.032, 1.035, 1.037, 1.039, 1.044, 1.049, 1.054, 1.058, 1.063, 1.066, + 1.045, 1.043, 1.038, 1.034, 1.032, 1.031, 1.029, 1.029, 1.027, 1.026, 1.024, 1.023, 1.022, 1.021, 1.021, 1.022, 1.022, 1.024, 1.026, 1.028, 1.029, 1.032, 1.034, 1.036, 1.039, 1.042, 1.047, 1.049, 1.056, 1.061, 1.064, 1.067, + 1.048, 1.043, 1.039, 1.035, 1.033, 1.031, 1.031, 1.029, 1.028, 1.027, 1.026, 1.026, 1.024, 1.023, 1.023, 1.023, 1.024, 1.025, 1.027, 1.029, 1.031, 1.033, 1.035, 1.038, 1.039, 1.044, 1.047, 1.053, 1.058, 1.063, 1.066, 1.071, + 1.049, 1.045, 1.041, 1.038, 1.034, 1.032, 1.031, 1.031, 1.029, 1.028, 1.027, 1.027, 1.026, 1.024, 1.024, 1.024, 1.025, 1.027, 1.028, 1.029, 1.032, 1.034, 1.036, 1.039, 1.042, 1.046, 1.051, 1.055, 1.061, 1.064, 1.069, 1.072, + 1.051, 1.049, 1.044, 1.041, 1.036, 1.033, 1.032, 1.031, 1.031, 1.029, 1.028, 1.028, 1.027, 1.027, 1.025, 1.025, 1.026, 1.028, 1.029, 1.031, 1.033, 1.034, 1.038, 1.039, 1.043, 1.048, 1.053, 1.058, 1.062, 1.064, 1.071, 1.074 + ] + }, + { + "ct": 5000, + "table": + [ + 1.028, 1.028, 1.022, 1.014, 1.008, 1.006, 1.006, 1.005, 1.004, 1.003, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.002, 1.003, 1.004, 1.006, 1.008, 1.009, 1.011, 1.015, 1.018, 1.021, 1.028, 1.034, 1.039, 1.044, 1.044, + 1.038, 1.031, 1.025, 1.023, 1.022, 1.021, 1.021, 1.021, 1.019, 1.019, 1.017, 1.016, 1.015, 1.015, 1.015, 1.016, 1.017, 1.018, 1.019, 1.019, 1.021, 1.023, 1.024, 1.027, 1.028, 1.031, 1.034, 1.038, 1.042, 1.047, 1.054, 1.058, + 1.043, 1.038, 1.031, 1.024, 1.023, 1.022, 1.021, 1.021, 1.021, 1.019, 1.018, 1.017, 1.016, 1.016, 1.017, 1.017, 1.018, 1.018, 1.019, 1.021, 1.022, 1.024, 1.026, 1.028, 1.031, 1.033, 1.037, 1.041, 1.047, 1.053, 1.058, 1.062, + 1.043, 1.037, 1.029, 1.024, 1.022, 1.021, 1.021, 1.019, 1.019, 1.019, 1.017, 1.016, 1.016, 1.016, 1.016, 1.017, 1.018, 1.018, 1.019, 1.021, 1.022, 1.024, 1.026, 1.029, 1.031, 1.034, 1.038, 1.042, 1.046, 1.052, 1.058, 1.062, + 1.041, 1.036, 1.029, 1.024, 1.022, 1.021, 1.019, 1.019, 1.018, 1.017, 1.016, 1.015, 1.015, 1.015, 1.016, 1.016, 1.016, 1.017, 1.018, 1.021, 1.022, 1.024, 1.026, 1.028, 1.031, 1.033, 1.037, 1.042, 1.047, 1.051, 1.058, 1.062, + 1.039, 1.034, 1.028, 1.023, 1.021, 1.019, 1.018, 1.018, 1.017, 1.016, 1.015, 1.015, 1.014, 1.014, 1.014, 1.015, 1.016, 1.016, 1.017, 1.019, 1.022, 1.024, 1.025, 1.028, 1.031, 1.033, 1.037, 1.041, 1.047, 1.051, 1.058, 1.062, + 1.039, 1.033, 1.027, 1.022, 1.019, 1.018, 1.017, 1.017, 1.016, 1.015, 1.014, 1.014, 1.013, 1.013, 1.013, 1.013, 1.015, 1.015, 1.016, 1.019, 1.021, 1.023, 1.025, 1.027, 1.031, 1.033, 1.037, 1.041, 1.048, 1.051, 1.057, 1.062, + 1.038, 1.032, 1.026, 1.021, 1.018, 1.017, 1.016, 1.015, 1.014, 1.014, 1.013, 1.012, 1.012, 1.012, 1.012, 1.012, 1.013, 1.014, 1.016, 1.017, 1.019, 1.022, 1.023, 1.026, 1.029, 1.033, 1.037, 1.041, 1.048, 1.052, 1.058, 1.063, + 1.037, 1.031, 1.026, 1.021, 1.018, 1.016, 1.015, 1.014, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.011, 1.011, 1.012, 1.014, 1.015, 1.016, 1.018, 1.021, 1.023, 1.026, 1.028, 1.032, 1.037, 1.042, 1.048, 1.053, 1.059, 1.066, + 1.037, 1.029, 1.025, 1.021, 1.018, 1.016, 1.014, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.009, 1.011, 1.011, 1.012, 1.013, 1.014, 1.016, 1.018, 1.021, 1.023, 1.026, 1.028, 1.033, 1.037, 1.042, 1.049, 1.053, 1.061, 1.066, + 1.035, 1.029, 1.024, 1.019, 1.017, 1.015, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.009, 1.009, 1.009, 1.011, 1.012, 1.013, 1.014, 1.016, 1.018, 1.021, 1.023, 1.026, 1.028, 1.034, 1.037, 1.042, 1.049, 1.054, 1.061, 1.067, + 1.034, 1.029, 1.023, 1.019, 1.017, 1.014, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.009, 1.009, 1.009, 1.011, 1.011, 1.012, 1.014, 1.016, 1.019, 1.021, 1.024, 1.027, 1.029, 1.034, 1.039, 1.042, 1.049, 1.054, 1.062, 1.067, + 1.034, 1.029, 1.023, 1.019, 1.017, 1.014, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.009, 1.009, 1.009, 1.009, 1.011, 1.012, 1.015, 1.016, 1.018, 1.021, 1.024, 1.027, 1.029, 1.034, 1.038, 1.043, 1.049, 1.055, 1.062, 1.067, + 1.035, 1.029, 1.023, 1.019, 1.018, 1.015, 1.013, 1.013, 1.012, 1.012, 1.011, 1.011, 1.011, 1.009, 1.009, 1.011, 1.012, 1.013, 1.015, 1.017, 1.019, 1.021, 1.024, 1.028, 1.031, 1.034, 1.039, 1.043, 1.051, 1.056, 1.064, 1.069, + 1.035, 1.031, 1.023, 1.019, 1.018, 1.017, 1.014, 1.013, 1.012, 1.012, 1.012, 1.011, 1.009, 1.009, 1.009, 1.011, 1.012, 1.013, 1.015, 1.017, 1.019, 1.021, 1.024, 1.027, 1.031, 1.035, 1.041, 1.044, 1.051, 1.057, 1.064, 1.071, + 1.036, 1.032, 1.023, 1.021, 1.019, 1.017, 1.015, 1.014, 1.013, 1.013, 1.012, 1.011, 1.011, 1.011, 1.011, 1.011, 1.012, 1.013, 1.016, 1.017, 1.019, 1.021, 1.023, 1.027, 1.031, 1.036, 1.041, 1.045, 1.051, 1.057, 1.066, 1.072, + 1.037, 1.032, 1.025, 1.021, 1.019, 1.018, 1.017, 1.014, 1.013, 1.013, 1.013, 1.012, 1.011, 1.011, 1.011, 1.012, 1.013, 1.014, 1.016, 1.017, 1.019, 1.022, 1.024, 1.027, 1.031, 1.036, 1.041, 1.045, 1.051, 1.057, 1.066, 1.073, + 1.038, 1.032, 1.025, 1.023, 1.021, 1.019, 1.017, 1.015, 1.014, 1.014, 1.013, 1.013, 1.012, 1.011, 1.012, 1.012, 1.014, 1.014, 1.016, 1.019, 1.021, 1.022, 1.025, 1.027, 1.031, 1.036, 1.041, 1.047, 1.052, 1.058, 1.066, 1.074, + 1.039, 1.033, 1.026, 1.024, 1.022, 1.021, 1.018, 1.016, 1.015, 1.015, 1.014, 1.014, 1.013, 1.013, 1.013, 1.014, 1.014, 1.015, 1.017, 1.021, 1.022, 1.023, 1.026, 1.028, 1.032, 1.036, 1.041, 1.047, 1.053, 1.059, 1.067, 1.076, + 1.041, 1.034, 1.026, 1.025, 1.023, 1.021, 1.019, 1.017, 1.016, 1.015, 1.015, 1.014, 1.014, 1.014, 1.014, 1.014, 1.015, 1.016, 1.018, 1.021, 1.023, 1.026, 1.027, 1.029, 1.033, 1.038, 1.041, 1.048, 1.054, 1.061, 1.069, 1.077, + 1.041, 1.034, 1.027, 1.025, 1.024, 1.022, 1.021, 1.018, 1.016, 1.016, 1.015, 1.015, 1.014, 1.015, 1.015, 1.016, 1.017, 1.017, 1.021, 1.021, 1.024, 1.027, 1.029, 1.032, 1.034, 1.039, 1.043, 1.049, 1.056, 1.062, 1.071, 1.077, + 1.041, 1.034, 1.028, 1.025, 1.024, 1.022, 1.021, 1.019, 1.018, 1.017, 1.017, 1.016, 1.016, 1.016, 1.016, 1.017, 1.018, 1.019, 1.021, 1.023, 1.026, 1.029, 1.031, 1.034, 1.036, 1.041, 1.046, 1.051, 1.058, 1.063, 1.071, 1.078, + 1.042, 1.034, 1.029, 1.026, 1.025, 1.024, 1.021, 1.019, 1.019, 1.018, 1.018, 1.017, 1.017, 1.017, 1.018, 1.019, 1.019, 1.021, 1.022, 1.024, 1.027, 1.029, 1.033, 1.035, 1.038, 1.042, 1.047, 1.054, 1.061, 1.067, 1.074, 1.079, + 1.043, 1.036, 1.031, 1.027, 1.026, 1.025, 1.023, 1.022, 1.021, 1.021, 1.019, 1.019, 1.019, 1.018, 1.019, 1.021, 1.021, 1.022, 1.024, 1.026, 1.029, 1.033, 1.035, 1.037, 1.041, 1.044, 1.049, 1.056, 1.062, 1.067, 1.077, 1.081, + 1.045, 1.038, 1.034, 1.029, 1.028, 1.026, 1.025, 1.023, 1.023, 1.022, 1.021, 1.021, 1.019, 1.019, 1.021, 1.022, 1.023, 1.024, 1.026, 1.028, 1.032, 1.035, 1.037, 1.039, 1.042, 1.047, 1.052, 1.057, 1.064, 1.069, 1.078, 1.083, + 1.047, 1.041, 1.036, 1.032, 1.029, 1.028, 1.028, 1.026, 1.026, 1.025, 1.024, 1.023, 1.022, 1.022, 1.023, 1.023, 1.026, 1.027, 1.028, 1.031, 1.035, 1.037, 1.039, 1.043, 1.046, 1.051, 1.055, 1.062, 1.067, 1.074, 1.082, 1.085, + 1.049, 1.043, 1.038, 1.035, 1.032, 1.031, 1.029, 1.028, 1.027, 1.027, 1.027, 1.026, 1.026, 1.026, 1.026, 1.027, 1.028, 1.029, 1.032, 1.034, 1.037, 1.039, 1.042, 1.046, 1.048, 1.053, 1.059, 1.064, 1.071, 1.078, 1.085, 1.088, + 1.052, 1.048, 1.041, 1.037, 1.035, 1.033, 1.033, 1.032, 1.031, 1.031, 1.029, 1.028, 1.028, 1.028, 1.029, 1.029, 1.031, 1.032, 1.035, 1.036, 1.039, 1.042, 1.046, 1.048, 1.052, 1.055, 1.061, 1.067, 1.075, 1.082, 1.088, 1.091, + 1.056, 1.051, 1.043, 1.038, 1.037, 1.035, 1.034, 1.034, 1.034, 1.033, 1.032, 1.031, 1.029, 1.031, 1.031, 1.031, 1.033, 1.035, 1.036, 1.039, 1.042, 1.045, 1.048, 1.051, 1.054, 1.059, 1.065, 1.069, 1.081, 1.085, 1.092, 1.097, + 1.059, 1.053, 1.047, 1.041, 1.038, 1.036, 1.035, 1.035, 1.035, 1.035, 1.034, 1.033, 1.032, 1.032, 1.032, 1.033, 1.035, 1.037, 1.039, 1.042, 1.043, 1.047, 1.049, 1.053, 1.056, 1.061, 1.067, 1.072, 1.083, 1.088, 1.095, 1.101, + 1.062, 1.056, 1.048, 1.043, 1.039, 1.037, 1.037, 1.037, 1.037, 1.037, 1.036, 1.035, 1.034, 1.034, 1.035, 1.036, 1.038, 1.039, 1.041, 1.043, 1.046, 1.049, 1.051, 1.054, 1.059, 1.065, 1.071, 1.076, 1.085, 1.091, 1.097, 1.105, + 1.064, 1.062, 1.053, 1.046, 1.041, 1.039, 1.038, 1.038, 1.039, 1.039, 1.038, 1.037, 1.038, 1.036, 1.036, 1.038, 1.039, 1.041, 1.042, 1.044, 1.048, 1.049, 1.053, 1.057, 1.061, 1.065, 1.073, 1.081, 1.087, 1.093, 1.101, 1.106 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 1.006, 1.006, 1.001, 1.001, 1.007, 1.014, 1.021, 1.026, 1.029, 1.031, 1.035, 1.038, 1.042, 1.043, 1.043, 1.045, 1.045, 1.046, 1.046, 1.048, 1.048, 1.048, 1.047, 1.046, 1.045, 1.044, 1.043, 1.042, 1.044, 1.053, 1.065, 1.071, + 1.026, 1.019, 1.018, 1.018, 1.021, 1.025, 1.033, 1.039, 1.046, 1.051, 1.053, 1.056, 1.059, 1.061, 1.063, 1.065, 1.067, 1.068, 1.069, 1.069, 1.069, 1.069, 1.068, 1.067, 1.066, 1.065, 1.062, 1.062, 1.063, 1.065, 1.072, 1.079, + 1.029, 1.026, 1.023, 1.023, 1.026, 1.033, 1.039, 1.046, 1.051, 1.054, 1.056, 1.059, 1.061, 1.064, 1.066, 1.068, 1.071, 1.071, 1.072, 1.073, 1.074, 1.074, 1.072, 1.071, 1.069, 1.068, 1.067, 1.065, 1.066, 1.069, 1.078, 1.089, + 1.032, 1.029, 1.026, 1.026, 1.029, 1.037, 1.042, 1.049, 1.053, 1.055, 1.059, 1.061, 1.062, 1.065, 1.067, 1.069, 1.072, 1.074, 1.075, 1.076, 1.077, 1.078, 1.078, 1.076, 1.074, 1.072, 1.069, 1.068, 1.068, 1.071, 1.079, 1.089, + 1.035, 1.032, 1.029, 1.029, 1.033, 1.039, 1.046, 1.052, 1.055, 1.057, 1.061, 1.062, 1.064, 1.066, 1.068, 1.071, 1.075, 1.075, 1.077, 1.079, 1.081, 1.082, 1.082, 1.081, 1.077, 1.075, 1.073, 1.071, 1.069, 1.072, 1.081, 1.092, + 1.035, 1.033, 1.029, 1.029, 1.035, 1.039, 1.047, 1.053, 1.056, 1.059, 1.062, 1.064, 1.065, 1.067, 1.069, 1.072, 1.075, 1.077, 1.079, 1.081, 1.082, 1.084, 1.084, 1.083, 1.081, 1.077, 1.075, 1.074, 1.073, 1.075, 1.081, 1.093, + 1.033, 1.032, 1.031, 1.032, 1.036, 1.041, 1.049, 1.053, 1.058, 1.059, 1.062, 1.064, 1.066, 1.068, 1.071, 1.072, 1.074, 1.077, 1.079, 1.082, 1.084, 1.085, 1.086, 1.085, 1.084, 1.081, 1.077, 1.076, 1.075, 1.077, 1.082, 1.093, + 1.034, 1.033, 1.032, 1.034, 1.038, 1.043, 1.048, 1.054, 1.058, 1.061, 1.063, 1.065, 1.067, 1.069, 1.071, 1.073, 1.074, 1.077, 1.079, 1.082, 1.084, 1.087, 1.087, 1.087, 1.086, 1.083, 1.081, 1.079, 1.078, 1.079, 1.084, 1.095, + 1.034, 1.033, 1.033, 1.034, 1.039, 1.044, 1.049, 1.053, 1.058, 1.061, 1.063, 1.065, 1.067, 1.069, 1.071, 1.073, 1.074, 1.076, 1.079, 1.083, 1.086, 1.088, 1.089, 1.089, 1.087, 1.085, 1.083, 1.081, 1.081, 1.082, 1.088, 1.096, + 1.035, 1.035, 1.034, 1.035, 1.039, 1.045, 1.049, 1.053, 1.058, 1.061, 1.062, 1.065, 1.067, 1.069, 1.071, 1.072, 1.074, 1.076, 1.079, 1.083, 1.087, 1.089, 1.091, 1.089, 1.089, 1.086, 1.084, 1.083, 1.082, 1.084, 1.089, 1.098, + 1.036, 1.034, 1.034, 1.036, 1.039, 1.046, 1.051, 1.054, 1.058, 1.061, 1.063, 1.065, 1.067, 1.069, 1.071, 1.073, 1.075, 1.077, 1.079, 1.085, 1.089, 1.092, 1.092, 1.091, 1.091, 1.087, 1.085, 1.083, 1.083, 1.085, 1.089, 1.099, + 1.039, 1.035, 1.034, 1.036, 1.041, 1.047, 1.049, 1.054, 1.059, 1.062, 1.063, 1.065, 1.067, 1.069, 1.072, 1.074, 1.076, 1.079, 1.081, 1.086, 1.091, 1.093, 1.093, 1.092, 1.091, 1.089, 1.087, 1.085, 1.085, 1.086, 1.091, 1.101, + 1.039, 1.037, 1.035, 1.035, 1.042, 1.047, 1.049, 1.055, 1.059, 1.063, 1.065, 1.066, 1.067, 1.069, 1.074, 1.076, 1.078, 1.079, 1.083, 1.086, 1.091, 1.094, 1.095, 1.095, 1.092, 1.091, 1.089, 1.087, 1.086, 1.087, 1.091, 1.103, + 1.041, 1.038, 1.036, 1.036, 1.044, 1.049, 1.052, 1.055, 1.059, 1.064, 1.066, 1.067, 1.069, 1.072, 1.074, 1.077, 1.079, 1.081, 1.083, 1.087, 1.091, 1.095, 1.097, 1.097, 1.095, 1.092, 1.091, 1.088, 1.087, 1.089, 1.094, 1.104, + 1.044, 1.039, 1.039, 1.039, 1.046, 1.049, 1.053, 1.056, 1.061, 1.065, 1.067, 1.068, 1.071, 1.074, 1.076, 1.078, 1.079, 1.081, 1.083, 1.087, 1.092, 1.096, 1.098, 1.099, 1.097, 1.095, 1.093, 1.091, 1.088, 1.089, 1.097, 1.106, + 1.046, 1.043, 1.039, 1.041, 1.046, 1.052, 1.053, 1.056, 1.061, 1.065, 1.068, 1.069, 1.072, 1.075, 1.077, 1.079, 1.079, 1.081, 1.083, 1.088, 1.092, 1.097, 1.099, 1.099, 1.098, 1.096, 1.094, 1.092, 1.089, 1.091, 1.099, 1.107, + 1.048, 1.045, 1.042, 1.042, 1.046, 1.052, 1.055, 1.058, 1.061, 1.065, 1.069, 1.071, 1.073, 1.076, 1.078, 1.079, 1.082, 1.083, 1.085, 1.089, 1.093, 1.098, 1.099, 1.099, 1.098, 1.097, 1.095, 1.092, 1.091, 1.092, 1.101, 1.109, + 1.049, 1.046, 1.043, 1.044, 1.047, 1.053, 1.055, 1.059, 1.063, 1.065, 1.069, 1.071, 1.073, 1.077, 1.079, 1.082, 1.083, 1.085, 1.086, 1.091, 1.094, 1.098, 1.101, 1.101, 1.098, 1.097, 1.095, 1.093, 1.093, 1.095, 1.102, 1.111, + 1.054, 1.048, 1.046, 1.046, 1.047, 1.054, 1.058, 1.062, 1.064, 1.066, 1.071, 1.073, 1.075, 1.078, 1.082, 1.084, 1.085, 1.086, 1.089, 1.092, 1.096, 1.099, 1.101, 1.102, 1.101, 1.099, 1.096, 1.094, 1.094, 1.097, 1.104, 1.115, + 1.053, 1.049, 1.047, 1.046, 1.049, 1.056, 1.061, 1.065, 1.066, 1.069, 1.072, 1.075, 1.077, 1.081, 1.083, 1.084, 1.086, 1.088, 1.091, 1.094, 1.098, 1.101, 1.103, 1.103, 1.103, 1.101, 1.099, 1.096, 1.096, 1.099, 1.106, 1.117, + 1.053, 1.051, 1.047, 1.047, 1.051, 1.058, 1.065, 1.066, 1.069, 1.071, 1.074, 1.076, 1.078, 1.082, 1.084, 1.085, 1.088, 1.091, 1.094, 1.097, 1.101, 1.102, 1.104, 1.104, 1.103, 1.102, 1.101, 1.098, 1.098, 1.099, 1.108, 1.118, + 1.053, 1.051, 1.048, 1.048, 1.051, 1.058, 1.065, 1.068, 1.071, 1.073, 1.075, 1.076, 1.079, 1.083, 1.084, 1.087, 1.089, 1.092, 1.096, 1.099, 1.102, 1.104, 1.105, 1.106, 1.105, 1.104, 1.101, 1.099, 1.099, 1.103, 1.109, 1.118, + 1.055, 1.051, 1.049, 1.048, 1.051, 1.057, 1.065, 1.069, 1.071, 1.074, 1.076, 1.078, 1.081, 1.084, 1.087, 1.088, 1.092, 1.095, 1.098, 1.101, 1.104, 1.106, 1.107, 1.108, 1.107, 1.106, 1.104, 1.103, 1.103, 1.106, 1.113, 1.121, + 1.058, 1.051, 1.049, 1.049, 1.051, 1.057, 1.064, 1.069, 1.073, 1.076, 1.078, 1.081, 1.084, 1.086, 1.088, 1.092, 1.095, 1.098, 1.099, 1.103, 1.106, 1.108, 1.111, 1.111, 1.111, 1.109, 1.107, 1.106, 1.106, 1.111, 1.115, 1.125, + 1.061, 1.053, 1.051, 1.051, 1.053, 1.059, 1.065, 1.071, 1.074, 1.078, 1.081, 1.084, 1.086, 1.088, 1.092, 1.096, 1.098, 1.099, 1.102, 1.105, 1.107, 1.109, 1.111, 1.112, 1.113, 1.112, 1.109, 1.109, 1.109, 1.112, 1.121, 1.134, + 1.064, 1.055, 1.052, 1.052, 1.054, 1.061, 1.065, 1.072, 1.077, 1.081, 1.084, 1.086, 1.088, 1.091, 1.096, 1.097, 1.099, 1.102, 1.104, 1.107, 1.109, 1.111, 1.112, 1.113, 1.114, 1.114, 1.114, 1.113, 1.112, 1.116, 1.125, 1.137, + 1.064, 1.059, 1.054, 1.054, 1.057, 1.062, 1.067, 1.073, 1.079, 1.081, 1.085, 1.087, 1.089, 1.093, 1.097, 1.099, 1.103, 1.104, 1.106, 1.109, 1.111, 1.113, 1.113, 1.114, 1.114, 1.116, 1.116, 1.116, 1.117, 1.122, 1.133, 1.143, + 1.069, 1.062, 1.057, 1.057, 1.059, 1.063, 1.068, 1.074, 1.079, 1.082, 1.086, 1.089, 1.091, 1.095, 1.098, 1.102, 1.104, 1.106, 1.109, 1.111, 1.113, 1.114, 1.114, 1.115, 1.116, 1.118, 1.119, 1.121, 1.123, 1.129, 1.142, 1.151, + 1.071, 1.065, 1.059, 1.058, 1.061, 1.065, 1.069, 1.074, 1.079, 1.083, 1.087, 1.091, 1.092, 1.095, 1.099, 1.103, 1.105, 1.108, 1.109, 1.111, 1.113, 1.114, 1.115, 1.116, 1.118, 1.119, 1.122, 1.124, 1.129, 1.134, 1.148, 1.161, + 1.075, 1.066, 1.059, 1.059, 1.061, 1.065, 1.069, 1.073, 1.081, 1.085, 1.089, 1.092, 1.094, 1.097, 1.101, 1.104, 1.106, 1.109, 1.111, 1.112, 1.113, 1.115, 1.117, 1.118, 1.119, 1.121, 1.124, 1.127, 1.132, 1.138, 1.156, 1.171, + 1.076, 1.069, 1.061, 1.059, 1.061, 1.066, 1.069, 1.073, 1.081, 1.085, 1.089, 1.094, 1.095, 1.098, 1.102, 1.105, 1.108, 1.109, 1.111, 1.112, 1.115, 1.116, 1.118, 1.119, 1.121, 1.123, 1.126, 1.129, 1.135, 1.144, 1.161, 1.173, + 1.082, 1.075, 1.064, 1.059, 1.061, 1.066, 1.069, 1.074, 1.079, 1.087, 1.092, 1.094, 1.096, 1.099, 1.103, 1.105, 1.108, 1.109, 1.112, 1.114, 1.115, 1.117, 1.119, 1.119, 1.121, 1.124, 1.129, 1.131, 1.137, 1.148, 1.165, 1.182 + ] + }, + { + "ct": 5000, + "table": + [ + 1.002, 1.002, 1.001, 1.002, 1.004, 1.007, 1.009, 1.013, 1.014, 1.015, 1.017, 1.019, 1.021, 1.021, 1.022, 1.022, 1.022, 1.022, 1.022, 1.022, 1.022, 1.022, 1.022, 1.021, 1.019, 1.018, 1.017, 1.014, 1.015, 1.015, 1.015, 1.014, + 1.021, 1.018, 1.018, 1.017, 1.019, 1.021, 1.024, 1.026, 1.029, 1.031, 1.033, 1.035, 1.037, 1.038, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.041, 1.039, 1.038, 1.037, 1.036, 1.034, 1.033, 1.032, 1.033, 1.033, 1.034, + 1.022, 1.021, 1.019, 1.019, 1.021, 1.024, 1.026, 1.028, 1.032, 1.033, 1.035, 1.037, 1.038, 1.039, 1.041, 1.041, 1.042, 1.042, 1.042, 1.043, 1.042, 1.042, 1.042, 1.041, 1.039, 1.038, 1.037, 1.035, 1.034, 1.034, 1.035, 1.037, + 1.023, 1.022, 1.021, 1.021, 1.023, 1.026, 1.028, 1.031, 1.033, 1.035, 1.037, 1.038, 1.039, 1.041, 1.042, 1.043, 1.044, 1.044, 1.045, 1.044, 1.044, 1.044, 1.044, 1.043, 1.041, 1.039, 1.039, 1.036, 1.035, 1.035, 1.035, 1.038, + 1.024, 1.023, 1.023, 1.023, 1.025, 1.027, 1.029, 1.033, 1.034, 1.037, 1.039, 1.039, 1.041, 1.042, 1.044, 1.045, 1.045, 1.045, 1.046, 1.047, 1.047, 1.047, 1.046, 1.045, 1.043, 1.041, 1.039, 1.037, 1.035, 1.035, 1.037, 1.039, + 1.025, 1.024, 1.024, 1.025, 1.027, 1.028, 1.031, 1.033, 1.036, 1.038, 1.039, 1.041, 1.043, 1.044, 1.046, 1.046, 1.046, 1.047, 1.048, 1.049, 1.049, 1.049, 1.048, 1.047, 1.046, 1.043, 1.041, 1.038, 1.036, 1.036, 1.037, 1.041, + 1.024, 1.025, 1.025, 1.026, 1.027, 1.029, 1.031, 1.033, 1.036, 1.039, 1.041, 1.043, 1.044, 1.045, 1.047, 1.048, 1.048, 1.049, 1.049, 1.051, 1.051, 1.049, 1.049, 1.049, 1.047, 1.045, 1.042, 1.039, 1.038, 1.037, 1.038, 1.039, + 1.025, 1.025, 1.026, 1.027, 1.028, 1.029, 1.031, 1.033, 1.037, 1.039, 1.042, 1.043, 1.045, 1.046, 1.048, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.051, 1.051, 1.049, 1.048, 1.046, 1.043, 1.041, 1.038, 1.037, 1.038, 1.041, + 1.024, 1.025, 1.027, 1.027, 1.028, 1.029, 1.032, 1.034, 1.036, 1.039, 1.042, 1.044, 1.045, 1.047, 1.048, 1.049, 1.049, 1.051, 1.051, 1.051, 1.052, 1.052, 1.052, 1.049, 1.048, 1.046, 1.044, 1.042, 1.041, 1.038, 1.038, 1.041, + 1.024, 1.025, 1.027, 1.028, 1.029, 1.031, 1.032, 1.034, 1.038, 1.041, 1.043, 1.045, 1.046, 1.047, 1.049, 1.049, 1.049, 1.051, 1.051, 1.052, 1.054, 1.053, 1.053, 1.051, 1.049, 1.047, 1.044, 1.042, 1.041, 1.039, 1.039, 1.042, + 1.025, 1.025, 1.027, 1.028, 1.029, 1.031, 1.032, 1.035, 1.039, 1.041, 1.044, 1.045, 1.046, 1.048, 1.049, 1.051, 1.051, 1.051, 1.052, 1.054, 1.054, 1.055, 1.053, 1.052, 1.049, 1.047, 1.044, 1.042, 1.041, 1.039, 1.041, 1.042, + 1.026, 1.026, 1.026, 1.028, 1.029, 1.031, 1.033, 1.036, 1.039, 1.042, 1.044, 1.045, 1.047, 1.048, 1.051, 1.051, 1.052, 1.052, 1.053, 1.054, 1.055, 1.055, 1.054, 1.052, 1.051, 1.049, 1.045, 1.042, 1.039, 1.039, 1.041, 1.042, + 1.027, 1.026, 1.026, 1.028, 1.029, 1.032, 1.033, 1.036, 1.039, 1.042, 1.045, 1.046, 1.047, 1.049, 1.052, 1.053, 1.053, 1.054, 1.054, 1.054, 1.055, 1.055, 1.055, 1.053, 1.051, 1.049, 1.046, 1.043, 1.039, 1.039, 1.041, 1.043, + 1.027, 1.027, 1.027, 1.028, 1.029, 1.032, 1.034, 1.036, 1.039, 1.043, 1.045, 1.047, 1.048, 1.051, 1.052, 1.053, 1.054, 1.054, 1.054, 1.055, 1.055, 1.056, 1.056, 1.055, 1.052, 1.051, 1.047, 1.044, 1.041, 1.039, 1.041, 1.043, + 1.028, 1.028, 1.028, 1.028, 1.029, 1.032, 1.033, 1.036, 1.038, 1.044, 1.046, 1.048, 1.049, 1.052, 1.053, 1.053, 1.054, 1.054, 1.055, 1.055, 1.056, 1.056, 1.057, 1.056, 1.053, 1.051, 1.048, 1.044, 1.042, 1.041, 1.042, 1.043, + 1.028, 1.028, 1.028, 1.028, 1.031, 1.032, 1.033, 1.035, 1.038, 1.043, 1.046, 1.048, 1.051, 1.053, 1.053, 1.054, 1.055, 1.055, 1.055, 1.055, 1.056, 1.057, 1.058, 1.056, 1.054, 1.052, 1.049, 1.045, 1.043, 1.042, 1.042, 1.044, + 1.029, 1.028, 1.027, 1.028, 1.031, 1.032, 1.033, 1.036, 1.038, 1.043, 1.046, 1.049, 1.051, 1.053, 1.054, 1.055, 1.055, 1.055, 1.055, 1.055, 1.056, 1.057, 1.057, 1.056, 1.053, 1.052, 1.049, 1.045, 1.042, 1.042, 1.043, 1.045, + 1.029, 1.028, 1.028, 1.029, 1.031, 1.033, 1.034, 1.036, 1.039, 1.042, 1.047, 1.048, 1.051, 1.053, 1.055, 1.056, 1.056, 1.055, 1.055, 1.055, 1.057, 1.057, 1.057, 1.057, 1.055, 1.052, 1.049, 1.046, 1.043, 1.043, 1.044, 1.045, + 1.031, 1.029, 1.028, 1.029, 1.031, 1.033, 1.035, 1.037, 1.039, 1.042, 1.046, 1.048, 1.051, 1.052, 1.055, 1.056, 1.056, 1.056, 1.056, 1.056, 1.057, 1.057, 1.057, 1.057, 1.055, 1.053, 1.049, 1.046, 1.044, 1.043, 1.044, 1.047, + 1.031, 1.029, 1.029, 1.029, 1.031, 1.033, 1.036, 1.037, 1.039, 1.042, 1.046, 1.048, 1.051, 1.052, 1.054, 1.055, 1.056, 1.056, 1.056, 1.057, 1.058, 1.058, 1.058, 1.057, 1.056, 1.054, 1.051, 1.047, 1.045, 1.044, 1.045, 1.047, + 1.031, 1.029, 1.029, 1.029, 1.031, 1.033, 1.036, 1.037, 1.039, 1.042, 1.045, 1.047, 1.051, 1.052, 1.054, 1.055, 1.056, 1.056, 1.057, 1.057, 1.058, 1.057, 1.058, 1.056, 1.055, 1.053, 1.051, 1.047, 1.045, 1.044, 1.044, 1.047, + 1.031, 1.029, 1.029, 1.031, 1.031, 1.032, 1.035, 1.038, 1.039, 1.042, 1.045, 1.048, 1.051, 1.052, 1.053, 1.055, 1.056, 1.056, 1.057, 1.057, 1.058, 1.058, 1.057, 1.056, 1.055, 1.053, 1.051, 1.048, 1.045, 1.044, 1.045, 1.048, + 1.031, 1.029, 1.029, 1.029, 1.031, 1.032, 1.034, 1.039, 1.041, 1.042, 1.045, 1.047, 1.049, 1.052, 1.053, 1.055, 1.055, 1.056, 1.057, 1.057, 1.058, 1.058, 1.057, 1.056, 1.055, 1.054, 1.051, 1.048, 1.047, 1.045, 1.046, 1.048, + 1.031, 1.029, 1.028, 1.029, 1.031, 1.032, 1.034, 1.037, 1.041, 1.043, 1.045, 1.047, 1.049, 1.052, 1.054, 1.055, 1.056, 1.056, 1.057, 1.057, 1.058, 1.058, 1.058, 1.057, 1.056, 1.054, 1.051, 1.049, 1.047, 1.046, 1.047, 1.048, + 1.029, 1.029, 1.028, 1.029, 1.031, 1.032, 1.035, 1.037, 1.039, 1.042, 1.045, 1.048, 1.049, 1.052, 1.054, 1.055, 1.055, 1.056, 1.056, 1.057, 1.058, 1.058, 1.058, 1.057, 1.056, 1.055, 1.052, 1.049, 1.047, 1.047, 1.047, 1.048, + 1.031, 1.029, 1.028, 1.029, 1.031, 1.032, 1.035, 1.037, 1.039, 1.042, 1.045, 1.047, 1.049, 1.052, 1.054, 1.055, 1.056, 1.056, 1.056, 1.057, 1.058, 1.058, 1.058, 1.058, 1.057, 1.055, 1.054, 1.051, 1.049, 1.048, 1.049, 1.051, + 1.031, 1.029, 1.029, 1.029, 1.031, 1.032, 1.035, 1.037, 1.041, 1.042, 1.045, 1.047, 1.049, 1.051, 1.053, 1.055, 1.056, 1.056, 1.056, 1.057, 1.057, 1.058, 1.058, 1.057, 1.056, 1.055, 1.054, 1.052, 1.051, 1.049, 1.051, 1.051, + 1.032, 1.031, 1.029, 1.029, 1.031, 1.032, 1.035, 1.037, 1.041, 1.042, 1.044, 1.046, 1.048, 1.051, 1.053, 1.054, 1.055, 1.056, 1.056, 1.056, 1.056, 1.056, 1.057, 1.056, 1.056, 1.055, 1.054, 1.052, 1.052, 1.051, 1.052, 1.054, + 1.034, 1.031, 1.031, 1.029, 1.031, 1.032, 1.034, 1.036, 1.041, 1.042, 1.043, 1.045, 1.047, 1.049, 1.052, 1.053, 1.055, 1.055, 1.056, 1.056, 1.056, 1.056, 1.056, 1.056, 1.056, 1.055, 1.054, 1.053, 1.053, 1.053, 1.053, 1.054, + 1.034, 1.033, 1.029, 1.029, 1.031, 1.032, 1.034, 1.036, 1.039, 1.042, 1.044, 1.045, 1.047, 1.049, 1.051, 1.052, 1.054, 1.055, 1.055, 1.055, 1.056, 1.056, 1.056, 1.056, 1.055, 1.055, 1.055, 1.055, 1.054, 1.054, 1.055, 1.056, + 1.034, 1.033, 1.029, 1.029, 1.031, 1.032, 1.033, 1.036, 1.039, 1.042, 1.044, 1.046, 1.047, 1.049, 1.051, 1.053, 1.054, 1.055, 1.055, 1.056, 1.056, 1.056, 1.056, 1.056, 1.055, 1.055, 1.054, 1.055, 1.055, 1.054, 1.055, 1.057, + 1.034, 1.032, 1.031, 1.029, 1.029, 1.032, 1.034, 1.037, 1.039, 1.042, 1.045, 1.046, 1.047, 1.049, 1.052, 1.054, 1.054, 1.055, 1.056, 1.056, 1.056, 1.056, 1.056, 1.055, 1.054, 1.054, 1.054, 1.055, 1.054, 1.055, 1.056, 1.057 + ] + } + ], + "luminance_lut": + [ + 2.367, 2.251, 2.061, 1.909, 1.781, 1.674, 1.582, 1.506, 1.441, 1.382, 1.329, 1.287, 1.266, 1.256, 1.255, 1.255, 1.255, 1.259, 1.273, 1.309, 1.355, 1.411, 1.469, 1.541, 1.625, 1.723, 1.838, 1.975, 2.139, 2.338, 2.589, 2.717, + 2.305, 2.152, 1.974, 1.831, 1.706, 1.604, 1.518, 1.443, 1.387, 1.343, 1.296, 1.255, 1.231, 1.213, 1.203, 1.203, 1.206, 1.221, 1.244, 1.279, 1.325, 1.364, 1.411, 1.481, 1.559, 1.653, 1.763, 1.895, 2.051, 2.238, 2.473, 2.641, + 2.249, 2.099, 1.925, 1.784, 1.665, 1.564, 1.479, 1.407, 1.347, 1.296, 1.255, 1.225, 1.199, 1.182, 1.172, 1.172, 1.178, 1.191, 1.215, 1.244, 1.279, 1.325, 1.378, 1.445, 1.522, 1.614, 1.719, 1.847, 1.998, 2.181, 2.406, 2.573, + 2.201, 2.048, 1.881, 1.743, 1.628, 1.529, 1.446, 1.376, 1.316, 1.266, 1.225, 1.198, 1.171, 1.152, 1.143, 1.143, 1.153, 1.165, 1.191, 1.215, 1.252, 1.297, 1.351, 1.413, 1.489, 1.577, 1.682, 1.804, 1.951, 2.129, 2.347, 2.513, + 2.156, 2.004, 1.842, 1.706, 1.594, 1.497, 1.416, 1.346, 1.287, 1.238, 1.199, 1.171, 1.143, 1.125, 1.117, 1.117, 1.127, 1.139, 1.165, 1.191, 1.227, 1.272, 1.324, 1.387, 1.459, 1.545, 1.646, 1.766, 1.909, 2.081, 2.294, 2.461, + 2.116, 1.963, 1.807, 1.674, 1.562, 1.467, 1.388, 1.321, 1.262, 1.214, 1.174, 1.143, 1.119, 1.099, 1.092, 1.092, 1.103, 1.116, 1.139, 1.167, 1.205, 1.249, 1.301, 1.362, 1.432, 1.515, 1.614, 1.731, 1.869, 2.039, 2.246, 2.413, + 2.082, 1.929, 1.774, 1.644, 1.535, 1.442, 1.364, 1.298, 1.241, 1.192, 1.152, 1.119, 1.099, 1.078, 1.069, 1.069, 1.078, 1.098, 1.116, 1.146, 1.184, 1.229, 1.281, 1.341, 1.409, 1.489, 1.585, 1.699, 1.834, 2.001, 2.205, 2.374, + 2.054, 1.898, 1.745, 1.619, 1.513, 1.422, 1.344, 1.277, 1.221, 1.172, 1.132, 1.099, 1.078, 1.059, 1.051, 1.051, 1.058, 1.078, 1.098, 1.128, 1.167, 1.212, 1.264, 1.323, 1.389, 1.467, 1.562, 1.674, 1.805, 1.969, 2.171, 2.339, + 2.031, 1.872, 1.722, 1.598, 1.493, 1.403, 1.327, 1.261, 1.203, 1.156, 1.114, 1.082, 1.059, 1.044, 1.034, 1.034, 1.044, 1.058, 1.081, 1.113, 1.151, 1.198, 1.249, 1.308, 1.373, 1.449, 1.543, 1.655, 1.799, 1.942, 2.141, 2.312, + 2.011, 1.849, 1.704, 1.581, 1.476, 1.387, 1.311, 1.245, 1.189, 1.141, 1.101, 1.068, 1.044, 1.031, 1.019, 1.019, 1.032, 1.044, 1.068, 1.099, 1.138, 1.184, 1.238, 1.295, 1.359, 1.435, 1.526, 1.639, 1.782, 1.924, 2.115, 2.287, + 1.996, 1.833, 1.687, 1.565, 1.462, 1.374, 1.299, 1.233, 1.177, 1.128, 1.087, 1.055, 1.031, 1.019, 1.008, 1.009, 1.019, 1.033, 1.057, 1.089, 1.128, 1.174, 1.227, 1.283, 1.347, 1.422, 1.512, 1.621, 1.764, 1.905, 2.091, 2.269, + 1.985, 1.819, 1.675, 1.553, 1.451, 1.363, 1.289, 1.223, 1.167, 1.119, 1.078, 1.046, 1.022, 1.007, 1.003, 1.007, 1.009, 1.024, 1.049, 1.081, 1.119, 1.166, 1.218, 1.275, 1.339, 1.413, 1.502, 1.606, 1.742, 1.885, 2.075, 2.258, + 1.979, 1.809, 1.666, 1.546, 1.442, 1.356, 1.281, 1.215, 1.159, 1.112, 1.071, 1.039, 1.015, 1.001, 1.001, 1.003, 1.009, 1.019, 1.043, 1.075, 1.114, 1.159, 1.212, 1.268, 1.332, 1.407, 1.496, 1.599, 1.722, 1.875, 2.064, 2.249, + 1.976, 1.803, 1.661, 1.541, 1.437, 1.351, 1.276, 1.211, 1.156, 1.107, 1.067, 1.035, 1.011, 1.001, 1.001, 1.003, 1.009, 1.018, 1.039, 1.071, 1.111, 1.156, 1.207, 1.265, 1.329, 1.404, 1.492, 1.594, 1.717, 1.869, 2.059, 2.247, + 1.976, 1.801, 1.659, 1.538, 1.436, 1.348, 1.274, 1.209, 1.153, 1.106, 1.066, 1.034, 1.011, 1.001, 1.001, 1.004, 1.011, 1.018, 1.039, 1.071, 1.109, 1.155, 1.207, 1.264, 1.329, 1.404, 1.492, 1.593, 1.716, 1.868, 2.059, 2.247, + 1.976, 1.801, 1.659, 1.538, 1.436, 1.348, 1.274, 1.209, 1.153, 1.106, 1.066, 1.034, 1.011, 1.003, 1.003, 1.005, 1.011, 1.019, 1.041, 1.071, 1.109, 1.155, 1.207, 1.264, 1.329, 1.404, 1.493, 1.594, 1.716, 1.869, 2.059, 2.247, + 1.989, 1.809, 1.663, 1.542, 1.439, 1.351, 1.277, 1.212, 1.156, 1.108, 1.069, 1.038, 1.014, 1.006, 1.005, 1.008, 1.014, 1.023, 1.044, 1.075, 1.113, 1.158, 1.209, 1.267, 1.333, 1.408, 1.497, 1.599, 1.722, 1.875, 2.066, 2.251, + 1.997, 1.818, 1.669, 1.547, 1.444, 1.356, 1.282, 1.215, 1.161, 1.113, 1.075, 1.043, 1.019, 1.009, 1.008, 1.011, 1.016, 1.027, 1.049, 1.079, 1.117, 1.163, 1.215, 1.273, 1.338, 1.414, 1.503, 1.607, 1.731, 1.884, 2.073, 2.258, + 2.003, 1.827, 1.681, 1.558, 1.453, 1.365, 1.289, 1.225, 1.169, 1.122, 1.083, 1.051, 1.028, 1.018, 1.013, 1.016, 1.021, 1.035, 1.057, 1.087, 1.126, 1.171, 1.224, 1.281, 1.347, 1.424, 1.514, 1.617, 1.743, 1.896, 2.087, 2.268, + 2.011, 1.843, 1.696, 1.571, 1.465, 1.377, 1.302, 1.236, 1.179, 1.132, 1.093, 1.062, 1.039, 1.028, 1.021, 1.021, 1.032, 1.044, 1.067, 1.098, 1.136, 1.181, 1.233, 1.291, 1.359, 1.436, 1.526, 1.632, 1.758, 1.913, 2.104, 2.283, + 2.021, 1.863, 1.715, 1.588, 1.481, 1.392, 1.316, 1.249, 1.193, 1.145, 1.106, 1.075, 1.053, 1.039, 1.032, 1.032, 1.044, 1.057, 1.081, 1.111, 1.149, 1.194, 1.245, 1.304, 1.372, 1.452, 1.541, 1.649, 1.777, 1.932, 2.126, 2.299, + 2.042, 1.887, 1.736, 1.608, 1.501, 1.409, 1.333, 1.266, 1.209, 1.162, 1.123, 1.091, 1.071, 1.053, 1.049, 1.049, 1.057, 1.075, 1.095, 1.125, 1.163, 1.209, 1.259, 1.319, 1.389, 1.469, 1.561, 1.671, 1.799, 1.957, 2.156, 2.324, + 2.072, 1.916, 1.762, 1.631, 1.522, 1.429, 1.352, 1.285, 1.228, 1.179, 1.141, 1.109, 1.091, 1.071, 1.067, 1.067, 1.075, 1.095, 1.112, 1.143, 1.181, 1.227, 1.276, 1.338, 1.408, 1.489, 1.583, 1.694, 1.827, 1.987, 2.187, 2.354, + 2.104, 1.949, 1.791, 1.659, 1.548, 1.453, 1.375, 1.308, 1.248, 1.201, 1.162, 1.132, 1.109, 1.092, 1.088, 1.088, 1.096, 1.112, 1.134, 1.164, 1.202, 1.247, 1.297, 1.359, 1.432, 1.515, 1.611, 1.724, 1.858, 2.022, 2.225, 2.391, + 2.138, 1.985, 1.824, 1.689, 1.577, 1.482, 1.401, 1.332, 1.274, 1.225, 1.185, 1.158, 1.132, 1.116, 1.112, 1.112, 1.119, 1.134, 1.161, 1.186, 1.225, 1.271, 1.319, 1.384, 1.458, 1.542, 1.641, 1.757, 1.896, 2.064, 2.271, 2.436, + 2.179, 2.028, 1.862, 1.724, 1.611, 1.512, 1.429, 1.361, 1.301, 1.251, 1.212, 1.185, 1.158, 1.143, 1.138, 1.138, 1.145, 1.161, 1.186, 1.212, 1.251, 1.295, 1.346, 1.411, 1.487, 1.574, 1.675, 1.793, 1.936, 2.109, 2.321, 2.488, + 2.229, 2.075, 1.904, 1.764, 1.646, 1.547, 1.462, 1.391, 1.331, 1.281, 1.241, 1.212, 1.186, 1.172, 1.167, 1.167, 1.174, 1.189, 1.212, 1.242, 1.278, 1.323, 1.375, 1.443, 1.521, 1.609, 1.713, 1.836, 1.982, 2.159, 2.379, 2.546, + 2.283, 2.131, 1.954, 1.808, 1.687, 1.584, 1.499, 1.425, 1.364, 1.313, 1.276, 1.241, 1.218, 1.202, 1.197, 1.197, 1.205, 1.221, 1.242, 1.276, 1.308, 1.354, 1.408, 1.478, 1.558, 1.649, 1.756, 1.884, 2.034, 2.216, 2.446, 2.612, + 2.346, 2.191, 2.011, 1.859, 1.733, 1.628, 1.539, 1.465, 1.401, 1.349, 1.313, 1.276, 1.253, 1.237, 1.232, 1.232, 1.239, 1.254, 1.276, 1.308, 1.344, 1.389, 1.447, 1.517, 1.598, 1.695, 1.805, 1.937, 2.094, 2.282, 2.521, 2.688, + 2.417, 2.263, 2.071, 1.915, 1.785, 1.676, 1.584, 1.508, 1.443, 1.391, 1.349, 1.314, 1.291, 1.275, 1.269, 1.269, 1.276, 1.291, 1.313, 1.344, 1.384, 1.429, 1.489, 1.562, 1.646, 1.743, 1.859, 1.996, 2.157, 2.355, 2.607, 2.772, + 2.497, 2.338, 2.139, 1.976, 1.842, 1.729, 1.635, 1.555, 1.488, 1.433, 1.389, 1.355, 1.331, 1.316, 1.309, 1.309, 1.316, 1.331, 1.353, 1.383, 1.424, 1.473, 1.535, 1.609, 1.696, 1.798, 1.918, 2.061, 2.228, 2.436, 2.699, 2.868, + 2.585, 2.391, 2.186, 2.019, 1.881, 1.765, 1.669, 1.588, 1.519, 1.464, 1.421, 1.385, 1.355, 1.336, 1.335, 1.335, 1.336, 1.353, 1.382, 1.413, 1.454, 1.504, 1.567, 1.644, 1.733, 1.836, 1.958, 2.106, 2.278, 2.496, 2.765, 2.979 + ], + "sigma": 0.00094, + "sigma_Cb": 0.00164 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2698, + "ccm": + [ + 1.57242, -0.32752, -0.24489, + -0.61284, 1.70498, -0.09215, + -0.43411, 0.48072, 0.95338 + ] + }, + { + "ct": 2930, + "ccm": + [ + 1.69569, -0.52958, -0.16612, + -0.67108, 1.78452, -0.11343, + -0.41759, 0.54607, 0.87152 + ] + }, + { + "ct": 3643, + "ccm": + [ + 1.72576, -0.72422, -0.00153, + -0.45949, 1.40534, 0.05415, + -0.14492, -0.79825, 1.94317 + ] + }, + { + "ct": 4605, + "ccm": + [ + 1.49944, -0.41939, -0.08005, + -0.39248, 1.69456, -0.30208, + 0.01559, -0.88541, 1.86981 + ] + }, + { + "ct": 5658, + "ccm": + [ + 1.38807, -0.23217, -0.15589, + -0.37464, 1.70299, -0.32835, + -0.01316, -0.72039, 1.73355 + ] + } + ] + } + }, + { + "rpi.cac": { } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "night": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +} diff --git a/src/ipa/rpi/pisp/data/imx462.json b/src/ipa/rpi/pisp/data/imx462.json new file mode 100644 index 00000000..20ca1a66 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx462.json @@ -0,0 +1,342 @@ +{ + "version": 2.0, + "target": "pisp", + "description": "This is an interim tuning only. Please consider doing a more formal tuning for your application.", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 6813, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 890, + "reference_Y": 12900 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.67 + } + }, + { + "rpi.geq": + { + "offset": 187, + "slope": 0.00842 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "speed": 0.2, + "metering_modes": + { + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 10, 30000, 60000 ], + "gain": [ 1.0, 2.0, 8.0 ] + }, + "short": + { + "shutter": [ 10, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.16 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.7, + "luminance_lut": + [ + 2.844, 2.604, 2.365, 2.2, 2.039, 1.916, 1.799, 1.707, 1.622, 1.552, 1.487, 1.435, 1.389, 1.356, 1.332, 1.317, 1.31, 1.308, 1.313, 1.324, 1.344, 1.37, 1.41, 1.454, 1.508, 1.567, 1.641, 1.719, 1.82, 1.925, 2.073, 2.221, + 2.749, 2.521, 2.294, 2.134, 1.979, 1.861, 1.749, 1.661, 1.578, 1.511, 1.448, 1.398, 1.354, 1.322, 1.3, 1.285, 1.278, 1.277, 1.281, 1.292, 1.311, 1.336, 1.374, 1.416, 1.469, 1.526, 1.596, 1.671, 1.77, 1.872, 2.019, 2.166, + 2.654, 2.438, 2.223, 2.069, 1.919, 1.807, 1.7, 1.614, 1.534, 1.469, 1.409, 1.361, 1.318, 1.288, 1.267, 1.254, 1.247, 1.245, 1.25, 1.259, 1.277, 1.302, 1.338, 1.379, 1.43, 1.485, 1.552, 1.623, 1.719, 1.819, 1.965, 2.112, + 2.563, 2.359, 2.155, 2.007, 1.863, 1.755, 1.653, 1.571, 1.493, 1.43, 1.372, 1.325, 1.284, 1.256, 1.236, 1.223, 1.217, 1.216, 1.219, 1.229, 1.246, 1.269, 1.305, 1.344, 1.393, 1.446, 1.51, 1.578, 1.672, 1.77, 1.914, 2.059, + 2.494, 2.299, 2.103, 1.961, 1.822, 1.718, 1.619, 1.538, 1.461, 1.399, 1.343, 1.298, 1.259, 1.232, 1.213, 1.2, 1.194, 1.193, 1.196, 1.205, 1.222, 1.245, 1.279, 1.318, 1.365, 1.416, 1.481, 1.549, 1.641, 1.735, 1.875, 2.015, + 2.425, 2.238, 2.05, 1.914, 1.782, 1.681, 1.585, 1.505, 1.429, 1.369, 1.314, 1.271, 1.234, 1.208, 1.189, 1.177, 1.171, 1.169, 1.173, 1.182, 1.198, 1.221, 1.254, 1.292, 1.338, 1.387, 1.452, 1.519, 1.609, 1.701, 1.836, 1.971, + 2.363, 2.183, 2.003, 1.873, 1.746, 1.648, 1.555, 1.477, 1.401, 1.342, 1.289, 1.247, 1.212, 1.187, 1.168, 1.156, 1.149, 1.148, 1.152, 1.16, 1.177, 1.198, 1.231, 1.267, 1.312, 1.36, 1.425, 1.492, 1.58, 1.671, 1.802, 1.932, + 2.314, 2.14, 1.965, 1.839, 1.716, 1.622, 1.532, 1.454, 1.38, 1.322, 1.27, 1.229, 1.195, 1.169, 1.149, 1.137, 1.129, 1.128, 1.132, 1.142, 1.158, 1.18, 1.21, 1.245, 1.289, 1.336, 1.401, 1.469, 1.557, 1.649, 1.776, 1.903, + 2.264, 2.096, 1.927, 1.805, 1.687, 1.596, 1.509, 1.432, 1.358, 1.301, 1.251, 1.211, 1.177, 1.151, 1.131, 1.117, 1.109, 1.108, 1.113, 1.123, 1.14, 1.161, 1.19, 1.222, 1.265, 1.313, 1.378, 1.445, 1.534, 1.626, 1.75, 1.874, + 2.225, 2.061, 1.897, 1.778, 1.663, 1.574, 1.489, 1.414, 1.341, 1.285, 1.235, 1.196, 1.163, 1.136, 1.115, 1.1, 1.091, 1.089, 1.095, 1.106, 1.124, 1.145, 1.174, 1.205, 1.248, 1.294, 1.359, 1.427, 1.516, 1.606, 1.728, 1.849, + 2.193, 2.033, 1.872, 1.756, 1.642, 1.556, 1.473, 1.399, 1.327, 1.272, 1.224, 1.185, 1.15, 1.123, 1.1, 1.084, 1.074, 1.072, 1.078, 1.09, 1.11, 1.133, 1.161, 1.193, 1.234, 1.28, 1.345, 1.413, 1.501, 1.59, 1.709, 1.828, + 2.161, 2.004, 1.848, 1.734, 1.622, 1.537, 1.457, 1.384, 1.313, 1.26, 1.212, 1.173, 1.138, 1.11, 1.085, 1.068, 1.057, 1.055, 1.062, 1.075, 1.096, 1.12, 1.148, 1.18, 1.221, 1.266, 1.331, 1.399, 1.486, 1.574, 1.69, 1.807, + 2.14, 1.986, 1.832, 1.719, 1.609, 1.525, 1.445, 1.373, 1.304, 1.251, 1.204, 1.165, 1.129, 1.1, 1.074, 1.055, 1.043, 1.041, 1.049, 1.063, 1.086, 1.11, 1.14, 1.172, 1.212, 1.258, 1.323, 1.39, 1.477, 1.566, 1.679, 1.792, + 2.123, 1.971, 1.819, 1.707, 1.598, 1.514, 1.434, 1.364, 1.296, 1.243, 1.197, 1.158, 1.122, 1.091, 1.064, 1.044, 1.031, 1.027, 1.036, 1.052, 1.076, 1.102, 1.132, 1.165, 1.206, 1.251, 1.316, 1.383, 1.471, 1.56, 1.67, 1.78, + 2.106, 1.956, 1.806, 1.695, 1.587, 1.504, 1.424, 1.354, 1.288, 1.236, 1.19, 1.15, 1.114, 1.083, 1.055, 1.033, 1.018, 1.014, 1.024, 1.04, 1.066, 1.094, 1.124, 1.158, 1.199, 1.245, 1.309, 1.376, 1.465, 1.555, 1.661, 1.767, + 2.104, 1.955, 1.805, 1.694, 1.586, 1.502, 1.422, 1.352, 1.285, 1.234, 1.188, 1.149, 1.113, 1.081, 1.053, 1.031, 1.014, 1.011, 1.021, 1.038, 1.064, 1.091, 1.122, 1.156, 1.198, 1.244, 1.308, 1.376, 1.465, 1.555, 1.66, 1.766, + 2.104, 1.955, 1.806, 1.695, 1.586, 1.502, 1.421, 1.351, 1.284, 1.232, 1.187, 1.148, 1.112, 1.08, 1.051, 1.029, 1.012, 1.008, 1.02, 1.036, 1.062, 1.089, 1.12, 1.155, 1.197, 1.244, 1.308, 1.375, 1.465, 1.555, 1.661, 1.766, + 2.105, 1.956, 1.807, 1.696, 1.587, 1.502, 1.42, 1.35, 1.282, 1.231, 1.186, 1.148, 1.112, 1.08, 1.051, 1.028, 1.011, 1.007, 1.019, 1.036, 1.061, 1.088, 1.119, 1.154, 1.197, 1.244, 1.308, 1.376, 1.466, 1.557, 1.662, 1.767, + 2.121, 1.97, 1.818, 1.705, 1.595, 1.508, 1.424, 1.353, 1.286, 1.236, 1.191, 1.153, 1.118, 1.087, 1.059, 1.038, 1.022, 1.018, 1.028, 1.044, 1.067, 1.093, 1.124, 1.158, 1.201, 1.248, 1.314, 1.383, 1.474, 1.567, 1.672, 1.777, + 2.137, 1.983, 1.829, 1.715, 1.603, 1.514, 1.428, 1.357, 1.291, 1.24, 1.196, 1.158, 1.123, 1.094, 1.068, 1.047, 1.033, 1.029, 1.038, 1.052, 1.074, 1.098, 1.128, 1.162, 1.205, 1.253, 1.32, 1.39, 1.483, 1.577, 1.682, 1.788, + 2.154, 1.998, 1.843, 1.726, 1.613, 1.522, 1.435, 1.364, 1.297, 1.246, 1.202, 1.164, 1.131, 1.102, 1.078, 1.059, 1.045, 1.041, 1.048, 1.061, 1.082, 1.105, 1.134, 1.167, 1.211, 1.259, 1.327, 1.399, 1.494, 1.588, 1.694, 1.8, + 2.176, 2.019, 1.862, 1.744, 1.628, 1.537, 1.449, 1.377, 1.309, 1.258, 1.213, 1.176, 1.143, 1.116, 1.092, 1.074, 1.061, 1.057, 1.063, 1.075, 1.094, 1.117, 1.146, 1.178, 1.222, 1.27, 1.34, 1.414, 1.509, 1.604, 1.711, 1.818, + 2.199, 2.04, 1.881, 1.761, 1.644, 1.552, 1.464, 1.391, 1.321, 1.269, 1.223, 1.187, 1.155, 1.129, 1.106, 1.09, 1.078, 1.074, 1.078, 1.088, 1.107, 1.128, 1.157, 1.189, 1.233, 1.281, 1.353, 1.428, 1.524, 1.62, 1.728, 1.836, + 2.228, 2.066, 1.904, 1.782, 1.662, 1.57, 1.482, 1.408, 1.337, 1.284, 1.237, 1.201, 1.17, 1.145, 1.123, 1.107, 1.096, 1.092, 1.095, 1.104, 1.121, 1.142, 1.17, 1.203, 1.247, 1.297, 1.37, 1.446, 1.542, 1.639, 1.75, 1.86, + 2.267, 2.099, 1.932, 1.807, 1.684, 1.592, 1.504, 1.428, 1.356, 1.302, 1.255, 1.219, 1.189, 1.164, 1.141, 1.125, 1.115, 1.111, 1.114, 1.123, 1.138, 1.158, 1.186, 1.22, 1.266, 1.318, 1.391, 1.467, 1.563, 1.661, 1.776, 1.891, + 2.305, 2.132, 1.96, 1.832, 1.707, 1.614, 1.526, 1.449, 1.375, 1.32, 1.272, 1.237, 1.208, 1.182, 1.16, 1.144, 1.135, 1.131, 1.134, 1.141, 1.155, 1.174, 1.203, 1.236, 1.285, 1.338, 1.412, 1.489, 1.585, 1.682, 1.802, 1.922, + 2.351, 2.173, 1.996, 1.864, 1.736, 1.641, 1.552, 1.474, 1.4, 1.344, 1.294, 1.258, 1.228, 1.203, 1.181, 1.165, 1.156, 1.152, 1.155, 1.162, 1.176, 1.195, 1.224, 1.259, 1.309, 1.365, 1.439, 1.516, 1.613, 1.711, 1.835, 1.96, + 2.4, 2.218, 2.036, 1.901, 1.768, 1.671, 1.58, 1.502, 1.428, 1.37, 1.319, 1.281, 1.249, 1.224, 1.203, 1.188, 1.178, 1.174, 1.177, 1.184, 1.197, 1.217, 1.248, 1.285, 1.337, 1.394, 1.469, 1.547, 1.644, 1.743, 1.873, 2.002, + 2.45, 2.264, 2.077, 1.938, 1.801, 1.702, 1.608, 1.53, 1.456, 1.397, 1.344, 1.304, 1.271, 1.245, 1.224, 1.21, 1.2, 1.196, 1.199, 1.206, 1.219, 1.239, 1.272, 1.311, 1.365, 1.424, 1.5, 1.578, 1.676, 1.776, 1.91, 2.044, + 2.513, 2.318, 2.124, 1.984, 1.848, 1.747, 1.652, 1.572, 1.496, 1.436, 1.383, 1.341, 1.303, 1.274, 1.253, 1.238, 1.228, 1.225, 1.228, 1.235, 1.248, 1.269, 1.303, 1.343, 1.4, 1.46, 1.537, 1.617, 1.718, 1.82, 1.962, 2.103, + 2.579, 2.376, 2.172, 2.032, 1.897, 1.796, 1.7, 1.617, 1.538, 1.479, 1.426, 1.38, 1.337, 1.306, 1.283, 1.267, 1.258, 1.254, 1.257, 1.265, 1.279, 1.3, 1.336, 1.377, 1.435, 1.497, 1.576, 1.658, 1.761, 1.867, 2.016, 2.165, + 2.645, 2.433, 2.22, 2.08, 1.946, 1.844, 1.747, 1.663, 1.581, 1.521, 1.468, 1.419, 1.371, 1.337, 1.313, 1.296, 1.287, 1.284, 1.287, 1.295, 1.309, 1.331, 1.368, 1.411, 1.471, 1.535, 1.615, 1.699, 1.805, 1.914, 2.071, 2.227 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.sharpen": { } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 3900, + "ccm": + [ + 1.54659, -0.17707, -0.36953, + -0.51471, 1.72733, -0.21262, + 0.06667, -0.92279, 1.85612 + ] + } + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx477.json b/src/ipa/rpi/pisp/data/imx477.json new file mode 100644 index 00000000..2fe04c21 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx477.json @@ -0,0 +1,1186 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 12000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 740, + "reference_Y": 15051 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.809 + } + }, + { + "rpi.geq": + { + "offset": 204, + "slope": 0.0061 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2850.0, 0.4307, 0.3957, + 2960.0, 0.4159, 0.4313, + 3580.0, 0.3771, 0.5176, + 4559.0, 0.3031, 0.6573, + 5881.0, 0.2809, 0.6942, + 7600.0, 0.2263, 0.7762 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02634, + "transverse_neg": 0.02255 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.359, 2.354, 2.351, 2.351, 2.343, 2.337, 2.331, 2.325, 2.323, 2.321, 2.317, 2.315, 2.313, 2.313, 2.311, 2.312, 2.312, 2.313, 2.315, 2.315, 2.316, 2.317, 2.319, 2.323, 2.326, 2.329, 2.332, 2.332, 2.335, 2.337, 2.352, 2.363, + 2.352, 2.351, 2.349, 2.346, 2.342, 2.334, 2.328, 2.324, 2.321, 2.317, 2.315, 2.314, 2.312, 2.311, 2.311, 2.311, 2.311, 2.311, 2.312, 2.314, 2.315, 2.316, 2.317, 2.319, 2.324, 2.326, 2.328, 2.329, 2.331, 2.337, 2.348, 2.355, + 2.346, 2.346, 2.345, 2.344, 2.338, 2.329, 2.325, 2.319, 2.316, 2.314, 2.311, 2.309, 2.308, 2.306, 2.304, 2.304, 2.305, 2.307, 2.308, 2.309, 2.311, 2.311, 2.313, 2.316, 2.319, 2.322, 2.325, 2.326, 2.328, 2.335, 2.343, 2.349, + 2.342, 2.342, 2.341, 2.338, 2.332, 2.326, 2.319, 2.316, 2.312, 2.309, 2.308, 2.305, 2.303, 2.302, 2.301, 2.301, 2.302, 2.303, 2.304, 2.305, 2.305, 2.307, 2.311, 2.313, 2.315, 2.319, 2.321, 2.325, 2.328, 2.333, 2.338, 2.348, + 2.337, 2.337, 2.337, 2.336, 2.331, 2.322, 2.317, 2.312, 2.309, 2.307, 2.304, 2.302, 2.299, 2.299, 2.298, 2.298, 2.299, 2.299, 2.301, 2.302, 2.302, 2.304, 2.305, 2.309, 2.314, 2.316, 2.321, 2.324, 2.326, 2.329, 2.335, 2.343, + 2.335, 2.334, 2.333, 2.333, 2.326, 2.318, 2.313, 2.309, 2.306, 2.302, 2.299, 2.297, 2.297, 2.296, 2.295, 2.295, 2.294, 2.295, 2.296, 2.298, 2.298, 2.301, 2.303, 2.305, 2.311, 2.315, 2.319, 2.323, 2.325, 2.329, 2.333, 2.339, + 2.329, 2.331, 2.329, 2.329, 2.325, 2.315, 2.309, 2.306, 2.302, 2.299, 2.297, 2.295, 2.293, 2.292, 2.291, 2.291, 2.291, 2.291, 2.293, 2.294, 2.296, 2.298, 2.301, 2.304, 2.307, 2.313, 2.317, 2.319, 2.323, 2.327, 2.331, 2.339, + 2.329, 2.328, 2.328, 2.328, 2.321, 2.313, 2.307, 2.303, 2.299, 2.295, 2.294, 2.292, 2.289, 2.289, 2.288, 2.288, 2.288, 2.289, 2.289, 2.292, 2.294, 2.295, 2.297, 2.301, 2.306, 2.311, 2.315, 2.318, 2.319, 2.323, 2.329, 2.335, + 2.326, 2.327, 2.325, 2.325, 2.319, 2.311, 2.305, 2.299, 2.296, 2.293, 2.291, 2.289, 2.288, 2.287, 2.285, 2.285, 2.286, 2.288, 2.288, 2.289, 2.291, 2.294, 2.295, 2.298, 2.304, 2.308, 2.313, 2.315, 2.317, 2.319, 2.327, 2.335, + 2.325, 2.325, 2.323, 2.323, 2.317, 2.309, 2.303, 2.298, 2.294, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.284, 2.284, 2.285, 2.287, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.305, 2.309, 2.313, 2.315, 2.317, 2.325, 2.334, + 2.322, 2.324, 2.322, 2.322, 2.316, 2.306, 2.301, 2.296, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.283, 2.283, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.293, 2.296, 2.301, 2.304, 2.308, 2.311, 2.312, 2.315, 2.323, 2.333, + 2.321, 2.323, 2.322, 2.322, 2.314, 2.306, 2.299, 2.294, 2.291, 2.288, 2.286, 2.285, 2.284, 2.282, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.306, 2.308, 2.311, 2.312, 2.322, 2.332, + 2.319, 2.321, 2.321, 2.321, 2.314, 2.305, 2.297, 2.293, 2.289, 2.287, 2.285, 2.284, 2.283, 2.281, 2.281, 2.281, 2.282, 2.283, 2.283, 2.285, 2.287, 2.289, 2.291, 2.292, 2.297, 2.301, 2.305, 2.307, 2.309, 2.312, 2.321, 2.333, + 2.319, 2.321, 2.319, 2.319, 2.314, 2.303, 2.296, 2.293, 2.289, 2.286, 2.285, 2.283, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.296, 2.301, 2.305, 2.307, 2.308, 2.312, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.296, 2.291, 2.289, 2.286, 2.284, 2.282, 2.281, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.287, 2.288, 2.291, 2.295, 2.299, 2.304, 2.306, 2.307, 2.311, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.287, 2.285, 2.282, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.284, 2.285, 2.286, 2.288, 2.291, 2.295, 2.299, 2.303, 2.306, 2.307, 2.312, 2.321, 2.331, + 2.318, 2.319, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.286, 2.285, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.285, 2.286, 2.287, 2.291, 2.294, 2.298, 2.303, 2.306, 2.307, 2.311, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.302, 2.297, 2.292, 2.289, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.287, 2.289, 2.294, 2.297, 2.303, 2.305, 2.308, 2.313, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.303, 2.299, 2.293, 2.291, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.288, 2.291, 2.294, 2.298, 2.304, 2.306, 2.308, 2.312, 2.322, 2.331, + 2.319, 2.321, 2.321, 2.321, 2.315, 2.305, 2.301, 2.295, 2.292, 2.289, 2.286, 2.285, 2.283, 2.282, 2.282, 2.282, 2.284, 2.283, 2.284, 2.284, 2.285, 2.287, 2.288, 2.291, 2.294, 2.299, 2.304, 2.306, 2.309, 2.313, 2.322, 2.334, + 2.321, 2.322, 2.322, 2.322, 2.317, 2.307, 2.301, 2.296, 2.292, 2.291, 2.288, 2.286, 2.285, 2.284, 2.283, 2.284, 2.285, 2.284, 2.285, 2.285, 2.287, 2.288, 2.289, 2.293, 2.297, 2.301, 2.305, 2.308, 2.311, 2.314, 2.323, 2.335, + 2.322, 2.324, 2.324, 2.324, 2.319, 2.309, 2.303, 2.297, 2.295, 2.292, 2.291, 2.288, 2.286, 2.286, 2.285, 2.286, 2.286, 2.286, 2.287, 2.288, 2.289, 2.289, 2.291, 2.294, 2.299, 2.302, 2.307, 2.311, 2.312, 2.316, 2.325, 2.335, + 2.324, 2.326, 2.325, 2.326, 2.321, 2.311, 2.305, 2.301, 2.297, 2.295, 2.293, 2.291, 2.289, 2.289, 2.288, 2.288, 2.287, 2.288, 2.289, 2.291, 2.292, 2.292, 2.295, 2.299, 2.301, 2.304, 2.309, 2.312, 2.315, 2.319, 2.327, 2.337, + 2.329, 2.329, 2.328, 2.328, 2.323, 2.315, 2.308, 2.304, 2.301, 2.298, 2.296, 2.294, 2.291, 2.291, 2.289, 2.291, 2.291, 2.291, 2.292, 2.293, 2.294, 2.295, 2.297, 2.299, 2.303, 2.308, 2.312, 2.315, 2.318, 2.321, 2.329, 2.339, + 2.329, 2.331, 2.332, 2.332, 2.326, 2.318, 2.311, 2.306, 2.304, 2.301, 2.299, 2.297, 2.295, 2.293, 2.292, 2.292, 2.292, 2.293, 2.294, 2.294, 2.296, 2.297, 2.299, 2.302, 2.306, 2.311, 2.315, 2.318, 2.319, 2.324, 2.332, 2.342, + 2.331, 2.333, 2.334, 2.334, 2.328, 2.321, 2.313, 2.308, 2.305, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.295, 2.294, 2.296, 2.296, 2.297, 2.298, 2.299, 2.302, 2.305, 2.308, 2.314, 2.317, 2.321, 2.323, 2.327, 2.334, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.309, 2.306, 2.304, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.309, 2.315, 2.319, 2.321, 2.324, 2.328, 2.337, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.311, 2.306, 2.304, 2.303, 2.302, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.311, 2.314, 2.319, 2.323, 2.325, 2.329, 2.339, 2.348, + 2.329, 2.329, 2.329, 2.331, 2.326, 2.319, 2.312, 2.309, 2.304, 2.303, 2.302, 2.301, 2.298, 2.295, 2.294, 2.294, 2.295, 2.295, 2.296, 2.297, 2.299, 2.301, 2.302, 2.304, 2.308, 2.313, 2.319, 2.322, 2.325, 2.329, 2.339, 2.351, + 2.329, 2.329, 2.329, 2.329, 2.326, 2.317, 2.311, 2.308, 2.303, 2.302, 2.301, 2.298, 2.296, 2.295, 2.294, 2.294, 2.294, 2.294, 2.296, 2.297, 2.298, 2.299, 2.301, 2.304, 2.307, 2.312, 2.318, 2.322, 2.326, 2.331, 2.341, 2.355, + 2.339, 2.332, 2.331, 2.331, 2.327, 2.323, 2.316, 2.309, 2.306, 2.302, 2.301, 2.299, 2.297, 2.296, 2.295, 2.294, 2.294, 2.296, 2.297, 2.297, 2.299, 2.301, 2.303, 2.306, 2.308, 2.317, 2.322, 2.325, 2.329, 2.341, 2.353, 2.361, + 2.347, 2.347, 2.345, 2.343, 2.338, 2.332, 2.326, 2.322, 2.321, 2.318, 2.316, 2.315, 2.313, 2.312, 2.311, 2.311, 2.311, 2.311, 2.312, 2.315, 2.317, 2.318, 2.319, 2.323, 2.324, 2.329, 2.334, 2.337, 2.344, 2.347, 2.361, 2.364 + ] + }, + { + "ct": 5000, + "table": + [ + 3.869, 3.852, 3.844, 3.842, 3.836, 3.821, 3.807, 3.796, 3.789, 3.784, 3.778, 3.775, 3.769, 3.768, 3.765, 3.765, 3.767, 3.769, 3.772, 3.774, 3.773, 3.775, 3.779, 3.787, 3.793, 3.801, 3.806, 3.804, 3.813, 3.819, 3.855, 3.879, + 3.854, 3.844, 3.837, 3.836, 3.824, 3.811, 3.797, 3.789, 3.784, 3.777, 3.774, 3.769, 3.764, 3.758, 3.757, 3.758, 3.758, 3.761, 3.763, 3.764, 3.765, 3.766, 3.772, 3.778, 3.787, 3.792, 3.794, 3.798, 3.802, 3.815, 3.839, 3.873, + 3.838, 3.831, 3.826, 3.823, 3.813, 3.799, 3.787, 3.781, 3.773, 3.768, 3.763, 3.759, 3.753, 3.749, 3.745, 3.745, 3.745, 3.752, 3.754, 3.757, 3.757, 3.759, 3.763, 3.769, 3.773, 3.781, 3.786, 3.792, 3.798, 3.811, 3.831, 3.861, + 3.833, 3.822, 3.817, 3.816, 3.804, 3.788, 3.779, 3.772, 3.766, 3.759, 3.755, 3.749, 3.744, 3.741, 3.738, 3.739, 3.739, 3.741, 3.743, 3.747, 3.749, 3.751, 3.756, 3.764, 3.769, 3.776, 3.783, 3.789, 3.798, 3.809, 3.821, 3.855, + 3.824, 3.818, 3.808, 3.808, 3.797, 3.781, 3.772, 3.764, 3.757, 3.752, 3.747, 3.743, 3.737, 3.735, 3.733, 3.733, 3.733, 3.735, 3.737, 3.738, 3.741, 3.746, 3.749, 3.755, 3.766, 3.771, 3.781, 3.789, 3.794, 3.806, 3.818, 3.849, + 3.815, 3.808, 3.799, 3.801, 3.787, 3.775, 3.767, 3.757, 3.751, 3.745, 3.738, 3.734, 3.732, 3.727, 3.725, 3.723, 3.722, 3.722, 3.726, 3.729, 3.734, 3.738, 3.744, 3.749, 3.759, 3.769, 3.781, 3.788, 3.792, 3.799, 3.811, 3.841, + 3.804, 3.799, 3.793, 3.793, 3.783, 3.771, 3.759, 3.751, 3.744, 3.735, 3.732, 3.727, 3.723, 3.721, 3.719, 3.716, 3.716, 3.716, 3.718, 3.722, 3.727, 3.731, 3.737, 3.746, 3.756, 3.767, 3.776, 3.782, 3.788, 3.795, 3.808, 3.831, + 3.802, 3.797, 3.787, 3.787, 3.779, 3.762, 3.753, 3.744, 3.734, 3.727, 3.725, 3.721, 3.716, 3.714, 3.709, 3.709, 3.711, 3.711, 3.712, 3.717, 3.722, 3.725, 3.731, 3.739, 3.752, 3.762, 3.772, 3.778, 3.779, 3.789, 3.798, 3.826, + 3.791, 3.789, 3.784, 3.784, 3.775, 3.759, 3.746, 3.735, 3.729, 3.724, 3.718, 3.714, 3.712, 3.707, 3.704, 3.704, 3.706, 3.708, 3.709, 3.711, 3.716, 3.722, 3.726, 3.735, 3.746, 3.754, 3.767, 3.774, 3.777, 3.781, 3.794, 3.824, + 3.789, 3.784, 3.779, 3.781, 3.771, 3.753, 3.741, 3.732, 3.725, 3.719, 3.715, 3.711, 3.707, 3.704, 3.701, 3.701, 3.702, 3.704, 3.708, 3.709, 3.713, 3.718, 3.724, 3.731, 3.742, 3.749, 3.761, 3.768, 3.772, 3.778, 3.791, 3.822, + 3.789, 3.781, 3.777, 3.777, 3.764, 3.749, 3.739, 3.729, 3.722, 3.718, 3.711, 3.708, 3.705, 3.701, 3.699, 3.699, 3.699, 3.701, 3.705, 3.707, 3.711, 3.715, 3.721, 3.727, 3.738, 3.746, 3.757, 3.763, 3.765, 3.773, 3.788, 3.821, + 3.785, 3.779, 3.774, 3.774, 3.764, 3.747, 3.736, 3.726, 3.719, 3.711, 3.709, 3.706, 3.701, 3.698, 3.696, 3.695, 3.695, 3.698, 3.702, 3.704, 3.707, 3.712, 3.718, 3.725, 3.734, 3.741, 3.753, 3.756, 3.759, 3.764, 3.784, 3.818, + 3.779, 3.776, 3.773, 3.773, 3.759, 3.744, 3.733, 3.724, 3.714, 3.709, 3.706, 3.704, 3.699, 3.696, 3.694, 3.694, 3.694, 3.697, 3.701, 3.703, 3.706, 3.709, 3.714, 3.721, 3.731, 3.737, 3.749, 3.753, 3.758, 3.762, 3.783, 3.819, + 3.779, 3.776, 3.769, 3.769, 3.757, 3.741, 3.729, 3.721, 3.712, 3.708, 3.705, 3.701, 3.697, 3.695, 3.694, 3.694, 3.695, 3.696, 3.698, 3.702, 3.705, 3.709, 3.712, 3.717, 3.728, 3.736, 3.749, 3.752, 3.756, 3.761, 3.781, 3.815, + 3.779, 3.773, 3.768, 3.768, 3.756, 3.738, 3.731, 3.719, 3.711, 3.707, 3.703, 3.698, 3.695, 3.694, 3.694, 3.695, 3.695, 3.695, 3.696, 3.702, 3.705, 3.708, 3.712, 3.717, 3.728, 3.736, 3.747, 3.751, 3.754, 3.761, 3.781, 3.815, + 3.782, 3.773, 3.767, 3.767, 3.755, 3.738, 3.728, 3.721, 3.711, 3.707, 3.701, 3.698, 3.695, 3.693, 3.694, 3.696, 3.695, 3.695, 3.695, 3.701, 3.703, 3.706, 3.711, 3.715, 3.726, 3.735, 3.745, 3.751, 3.754, 3.763, 3.779, 3.815, + 3.781, 3.771, 3.767, 3.767, 3.754, 3.739, 3.726, 3.721, 3.712, 3.706, 3.701, 3.698, 3.695, 3.693, 3.693, 3.695, 3.695, 3.695, 3.696, 3.698, 3.703, 3.705, 3.709, 3.715, 3.725, 3.734, 3.745, 3.751, 3.755, 3.762, 3.783, 3.818, + 3.781, 3.774, 3.767, 3.767, 3.755, 3.741, 3.729, 3.722, 3.712, 3.708, 3.701, 3.699, 3.695, 3.693, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.725, 3.732, 3.746, 3.751, 3.756, 3.763, 3.783, 3.821, + 3.781, 3.774, 3.769, 3.769, 3.756, 3.741, 3.731, 3.724, 3.713, 3.711, 3.707, 3.699, 3.697, 3.694, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.724, 3.734, 3.747, 3.751, 3.756, 3.765, 3.784, 3.821, + 3.784, 3.776, 3.773, 3.773, 3.759, 3.742, 3.733, 3.726, 3.719, 3.711, 3.709, 3.703, 3.698, 3.695, 3.694, 3.695, 3.697, 3.696, 3.698, 3.699, 3.703, 3.706, 3.711, 3.714, 3.727, 3.735, 3.746, 3.751, 3.757, 3.766, 3.787, 3.822, + 3.786, 3.783, 3.774, 3.774, 3.766, 3.747, 3.737, 3.727, 3.722, 3.716, 3.711, 3.706, 3.702, 3.698, 3.697, 3.698, 3.699, 3.699, 3.701, 3.703, 3.706, 3.711, 3.713, 3.719, 3.731, 3.739, 3.748, 3.753, 3.761, 3.769, 3.789, 3.826, + 3.786, 3.784, 3.779, 3.779, 3.769, 3.751, 3.742, 3.732, 3.725, 3.719, 3.715, 3.711, 3.706, 3.704, 3.701, 3.701, 3.702, 3.702, 3.705, 3.707, 3.712, 3.714, 3.717, 3.724, 3.733, 3.743, 3.749, 3.758, 3.764, 3.769, 3.791, 3.826, + 3.793, 3.787, 3.782, 3.782, 3.774, 3.756, 3.747, 3.737, 3.729, 3.725, 3.719, 3.715, 3.712, 3.708, 3.707, 3.706, 3.707, 3.708, 3.709, 3.713, 3.714, 3.717, 3.723, 3.729, 3.736, 3.747, 3.757, 3.764, 3.768, 3.774, 3.794, 3.829, + 3.794, 3.791, 3.786, 3.786, 3.779, 3.762, 3.751, 3.742, 3.735, 3.729, 3.725, 3.719, 3.716, 3.711, 3.709, 3.709, 3.709, 3.711, 3.716, 3.717, 3.721, 3.723, 3.726, 3.732, 3.741, 3.752, 3.761, 3.767, 3.773, 3.779, 3.801, 3.829, + 3.802, 3.798, 3.793, 3.793, 3.779, 3.766, 3.754, 3.746, 3.741, 3.736, 3.731, 3.726, 3.719, 3.717, 3.716, 3.715, 3.716, 3.717, 3.719, 3.721, 3.724, 3.726, 3.731, 3.737, 3.744, 3.756, 3.766, 3.772, 3.776, 3.784, 3.807, 3.839, + 3.805, 3.799, 3.795, 3.795, 3.784, 3.767, 3.757, 3.749, 3.744, 3.739, 3.736, 3.731, 3.726, 3.722, 3.719, 3.719, 3.719, 3.721, 3.723, 3.725, 3.727, 3.732, 3.738, 3.742, 3.751, 3.761, 3.771, 3.775, 3.782, 3.789, 3.811, 3.841, + 3.804, 3.801, 3.799, 3.799, 3.787, 3.772, 3.761, 3.752, 3.746, 3.742, 3.739, 3.735, 3.729, 3.726, 3.723, 3.724, 3.725, 3.726, 3.727, 3.728, 3.732, 3.736, 3.739, 3.745, 3.754, 3.765, 3.775, 3.779, 3.785, 3.795, 3.816, 3.844, + 3.801, 3.799, 3.796, 3.796, 3.787, 3.773, 3.761, 3.753, 3.746, 3.743, 3.739, 3.735, 3.731, 3.726, 3.725, 3.725, 3.725, 3.726, 3.727, 3.729, 3.733, 3.736, 3.741, 3.745, 3.755, 3.766, 3.776, 3.783, 3.786, 3.797, 3.819, 3.851, + 3.799, 3.795, 3.788, 3.788, 3.783, 3.772, 3.759, 3.749, 3.744, 3.738, 3.735, 3.733, 3.726, 3.724, 3.722, 3.722, 3.723, 3.724, 3.725, 3.727, 3.729, 3.733, 3.736, 3.742, 3.754, 3.762, 3.772, 3.779, 3.784, 3.796, 3.821, 3.859, + 3.799, 3.789, 3.787, 3.788, 3.779, 3.766, 3.755, 3.749, 3.742, 3.736, 3.733, 3.727, 3.723, 3.722, 3.721, 3.719, 3.719, 3.721, 3.725, 3.726, 3.728, 3.732, 3.734, 3.741, 3.747, 3.758, 3.771, 3.778, 3.785, 3.796, 3.825, 3.862, + 3.824, 3.799, 3.789, 3.789, 3.788, 3.777, 3.761, 3.751, 3.743, 3.739, 3.736, 3.728, 3.726, 3.725, 3.721, 3.719, 3.721, 3.723, 3.727, 3.728, 3.729, 3.733, 3.737, 3.744, 3.755, 3.769, 3.776, 3.784, 3.793, 3.819, 3.863, 3.877, + 3.833, 3.833, 3.833, 3.842, 3.825, 3.815, 3.807, 3.799, 3.792, 3.788, 3.785, 3.782, 3.778, 3.777, 3.773, 3.772, 3.772, 3.774, 3.778, 3.779, 3.779, 3.785, 3.792, 3.798, 3.803, 3.811, 3.822, 3.834, 3.843, 3.846, 3.877, 3.886 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.616, 2.616, 2.618, 2.621, 2.619, 2.618, 2.615, 2.615, 2.613, 2.611, 2.609, 2.609, 2.609, 2.611, 2.611, 2.611, 2.611, 2.609, 2.608, 2.608, 2.611, 2.613, 2.613, 2.614, 2.614, 2.615, 2.615, 2.622, 2.624, 2.621, 2.624, 2.641, + 2.616, 2.618, 2.621, 2.623, 2.623, 2.619, 2.618, 2.616, 2.616, 2.613, 2.611, 2.611, 2.611, 2.611, 2.612, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.612, 2.613, 2.612, 2.613, 2.615, 2.617, 2.621, 2.621, 2.619, 2.621, 2.641, + 2.621, 2.624, 2.627, 2.627, 2.625, 2.623, 2.621, 2.619, 2.618, 2.618, 2.618, 2.617, 2.616, 2.616, 2.615, 2.613, 2.612, 2.613, 2.613, 2.614, 2.614, 2.613, 2.614, 2.613, 2.614, 2.617, 2.619, 2.621, 2.621, 2.619, 2.623, 2.643, + 2.626, 2.627, 2.628, 2.629, 2.628, 2.625, 2.622, 2.621, 2.621, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.616, 2.616, 2.616, 2.618, 2.618, 2.617, 2.617, 2.618, 2.619, 2.621, 2.623, 2.624, 2.626, 2.625, 2.624, 2.625, 2.654, + 2.627, 2.628, 2.628, 2.628, 2.626, 2.623, 2.622, 2.622, 2.622, 2.622, 2.621, 2.621, 2.619, 2.617, 2.617, 2.616, 2.617, 2.617, 2.618, 2.619, 2.618, 2.618, 2.618, 2.621, 2.622, 2.624, 2.626, 2.627, 2.627, 2.626, 2.628, 2.655, + 2.625, 2.626, 2.627, 2.626, 2.625, 2.623, 2.622, 2.621, 2.622, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.616, 2.616, 2.616, 2.616, 2.616, 2.617, 2.618, 2.619, 2.621, 2.622, 2.624, 2.626, 2.628, 2.628, 2.629, 2.629, 2.655, + 2.626, 2.625, 2.626, 2.625, 2.625, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.616, 2.614, 2.613, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.618, 2.619, 2.621, 2.623, 2.624, 2.627, 2.629, 2.631, 2.629, 2.631, 2.651, + 2.625, 2.625, 2.625, 2.624, 2.623, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.614, 2.613, 2.612, 2.611, 2.611, 2.612, 2.612, 2.613, 2.616, 2.618, 2.619, 2.622, 2.624, 2.626, 2.628, 2.631, 2.631, 2.631, 2.631, 2.651, + 2.625, 2.625, 2.624, 2.623, 2.622, 2.622, 2.622, 2.622, 2.622, 2.621, 2.617, 2.615, 2.613, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.613, 2.615, 2.618, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.624, 2.624, 2.622, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.618, 2.616, 2.614, 2.612, 2.611, 2.609, 2.609, 2.608, 2.609, 2.611, 2.611, 2.615, 2.617, 2.619, 2.621, 2.625, 2.628, 2.631, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.622, 2.623, 2.622, 2.622, 2.621, 2.619, 2.619, 2.619, 2.618, 2.616, 2.614, 2.613, 2.611, 2.609, 2.608, 2.606, 2.607, 2.607, 2.609, 2.611, 2.615, 2.617, 2.619, 2.622, 2.626, 2.629, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.622, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.614, 2.613, 2.611, 2.611, 2.607, 2.606, 2.605, 2.604, 2.605, 2.607, 2.609, 2.613, 2.616, 2.619, 2.622, 2.627, 2.631, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.613, 2.609, 2.607, 2.604, 2.602, 2.601, 2.602, 2.603, 2.605, 2.609, 2.612, 2.616, 2.619, 2.624, 2.628, 2.631, 2.632, 2.633, 2.629, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.615, 2.614, 2.612, 2.608, 2.603, 2.601, 2.598, 2.597, 2.599, 2.602, 2.605, 2.608, 2.611, 2.615, 2.622, 2.625, 2.629, 2.631, 2.631, 2.633, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.616, 2.614, 2.611, 2.606, 2.601, 2.598, 2.595, 2.595, 2.597, 2.601, 2.604, 2.608, 2.612, 2.615, 2.623, 2.627, 2.629, 2.631, 2.631, 2.632, 2.631, 2.628, 2.628, 2.651, + 2.622, 2.623, 2.624, 2.624, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.609, 2.606, 2.601, 2.596, 2.594, 2.594, 2.596, 2.599, 2.603, 2.609, 2.613, 2.617, 2.623, 2.627, 2.629, 2.631, 2.632, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.623, 2.625, 2.625, 2.624, 2.621, 2.621, 2.619, 2.617, 2.616, 2.613, 2.608, 2.605, 2.601, 2.595, 2.593, 2.593, 2.595, 2.598, 2.604, 2.609, 2.615, 2.619, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.632, 2.629, 2.631, 2.651, + 2.624, 2.626, 2.626, 2.623, 2.621, 2.619, 2.618, 2.617, 2.615, 2.612, 2.608, 2.605, 2.601, 2.597, 2.595, 2.595, 2.596, 2.598, 2.605, 2.609, 2.616, 2.621, 2.626, 2.627, 2.629, 2.631, 2.633, 2.633, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.618, 2.617, 2.614, 2.612, 2.609, 2.606, 2.602, 2.599, 2.598, 2.597, 2.598, 2.602, 2.607, 2.612, 2.619, 2.621, 2.626, 2.628, 2.629, 2.632, 2.633, 2.634, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.621, 2.618, 2.617, 2.614, 2.612, 2.611, 2.608, 2.604, 2.602, 2.599, 2.599, 2.603, 2.606, 2.611, 2.616, 2.621, 2.624, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.634, 2.633, 2.633, 2.656, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.611, 2.611, 2.607, 2.604, 2.604, 2.604, 2.606, 2.609, 2.613, 2.619, 2.622, 2.625, 2.628, 2.631, 2.632, 2.633, 2.633, 2.636, 2.636, 2.634, 2.634, 2.658, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.619, 2.618, 2.616, 2.614, 2.613, 2.612, 2.611, 2.609, 2.608, 2.607, 2.608, 2.609, 2.613, 2.617, 2.621, 2.623, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.635, 2.636, 2.636, 2.636, 2.661, + 2.623, 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.616, 2.615, 2.614, 2.613, 2.612, 2.612, 2.611, 2.611, 2.611, 2.614, 2.615, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.633, 2.635, 2.635, 2.637, 2.637, 2.636, 2.637, 2.661, + 2.623, 2.624, 2.625, 2.626, 2.624, 2.621, 2.619, 2.617, 2.616, 2.615, 2.615, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.619, 2.621, 2.623, 2.626, 2.628, 2.631, 2.632, 2.634, 2.635, 2.636, 2.637, 2.638, 2.637, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.627, 2.626, 2.623, 2.619, 2.619, 2.618, 2.618, 2.618, 2.617, 2.617, 2.616, 2.616, 2.616, 2.619, 2.622, 2.623, 2.625, 2.628, 2.628, 2.631, 2.632, 2.634, 2.636, 2.638, 2.639, 2.639, 2.638, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.628, 2.626, 2.623, 2.621, 2.619, 2.619, 2.619, 2.619, 2.619, 2.619, 2.618, 2.618, 2.619, 2.623, 2.624, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.635, 2.638, 2.639, 2.639, 2.639, 2.636, 2.636, 2.662, + 2.625, 2.627, 2.628, 2.628, 2.626, 2.624, 2.623, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.624, 2.624, 2.625, 2.627, 2.628, 2.631, 2.631, 2.632, 2.634, 2.636, 2.639, 2.639, 2.641, 2.639, 2.635, 2.635, 2.663, + 2.625, 2.626, 2.628, 2.628, 2.627, 2.625, 2.624, 2.623, 2.623, 2.622, 2.623, 2.624, 2.624, 2.625, 2.625, 2.625, 2.625, 2.626, 2.627, 2.629, 2.631, 2.632, 2.633, 2.635, 2.638, 2.641, 2.642, 2.643, 2.642, 2.636, 2.636, 2.665, + 2.624, 2.626, 2.628, 2.628, 2.628, 2.626, 2.624, 2.624, 2.623, 2.623, 2.623, 2.625, 2.627, 2.627, 2.626, 2.626, 2.626, 2.627, 2.628, 2.629, 2.632, 2.633, 2.635, 2.637, 2.639, 2.642, 2.644, 2.644, 2.642, 2.638, 2.638, 2.665, + 2.623, 2.625, 2.626, 2.627, 2.626, 2.626, 2.624, 2.623, 2.623, 2.623, 2.623, 2.623, 2.626, 2.627, 2.626, 2.626, 2.626, 2.626, 2.628, 2.628, 2.629, 2.631, 2.634, 2.636, 2.639, 2.642, 2.644, 2.643, 2.641, 2.637, 2.638, 2.659, + 2.623, 2.627, 2.627, 2.627, 2.627, 2.628, 2.627, 2.624, 2.624, 2.623, 2.624, 2.624, 2.628, 2.628, 2.627, 2.628, 2.628, 2.628, 2.629, 2.629, 2.631, 2.635, 2.637, 2.639, 2.641, 2.643, 2.646, 2.645, 2.643, 2.641, 2.654, 2.659, + 2.642, 2.641, 2.643, 2.645, 2.645, 2.644, 2.644, 2.643, 2.643, 2.642, 2.642, 2.642, 2.643, 2.644, 2.644, 2.644, 2.646, 2.646, 2.647, 2.649, 2.651, 2.652, 2.654, 2.656, 2.658, 2.661, 2.661, 2.661, 2.659, 2.654, 2.659, 2.659 + ] + }, + { + "ct": 5000, + "table": + [ + 1.391, 1.394, 1.395, 1.396, 1.398, 1.398, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.398, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.399, 1.397, 1.399, 1.402, + 1.393, 1.395, 1.396, 1.398, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.401, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.399, 1.401, 1.401, 1.399, 1.398, 1.399, 1.402, + 1.398, 1.401, 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.401, 1.401, 1.399, 1.399, 1.401, 1.401, 1.401, 1.401, 1.399, 1.401, 1.406, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.402, 1.403, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.405, 1.404, 1.404, 1.404, 1.404, 1.405, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.406, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.408, 1.408, 1.407, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.403, 1.402, 1.402, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.408, 1.407, 1.405, 1.405, 1.414, + 1.402, 1.402, 1.402, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.403, 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.407, 1.409, 1.409, 1.409, 1.409, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.403, 1.404, 1.405, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.408, 1.405, 1.405, 1.413, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.402, 1.401, 1.399, 1.398, 1.398, 1.399, 1.399, 1.401, 1.403, 1.404, 1.405, 1.407, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.403, 1.404, 1.405, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.399, 1.401, 1.403, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.398, 1.401, 1.403, 1.406, 1.407, 1.409, 1.409, 1.411, 1.409, 1.409, 1.409, 1.408, 1.407, 1.407, 1.413, + 1.403, 1.404, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.404, 1.404, 1.403, 1.402, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.402, 1.404, 1.406, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.414, + 1.403, 1.403, 1.404, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.402, 1.401, 1.399, 1.398, 1.398, 1.398, 1.401, 1.403, 1.404, 1.408, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.402, 1.404, 1.406, 1.407, 1.408, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.409, 1.408, 1.408, 1.415, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.406, 1.408, 1.408, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.405, 1.406, 1.407, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.407, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.416, + 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.406, 1.407, 1.407, 1.407, 1.408, 1.409, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.417, + 1.402, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.415, + 1.402, 1.402, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.405, 1.406, 1.406, 1.406, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.412, 1.412, 1.413, 1.413, 1.411, 1.408, 1.411, 1.413, + 1.406, 1.406, 1.408, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.414, 1.414, 1.414, 1.414, 1.415, 1.415, 1.415, 1.415, 1.416, 1.416, 1.416, 1.417, 1.418, 1.418, 1.417, 1.417, 1.414, 1.411, 1.413, 1.413 + ] + } + ], + "luminance_lut": + [ + 1.554, 1.522, 1.466, 1.422, 1.385, 1.351, 1.322, 1.294, 1.269, 1.246, 1.228, 1.214, 1.207, 1.202, 1.199, 1.199, 1.199, 1.199, 1.202, 1.207, 1.218, 1.235, 1.255, 1.279, 1.305, 1.333, 1.365, 1.402, 1.447, 1.508, 1.602, 1.638, + 1.522, 1.478, 1.431, 1.391, 1.355, 1.323, 1.298, 1.271, 1.247, 1.228, 1.212, 1.199, 1.187, 1.179, 1.173, 1.172, 1.172, 1.174, 1.179, 1.189, 1.201, 1.216, 1.235, 1.256, 1.282, 1.308, 1.335, 1.368, 1.411, 1.461, 1.535, 1.602, + 1.479, 1.449, 1.407, 1.367, 1.332, 1.301, 1.271, 1.247, 1.226, 1.208, 1.191, 1.178, 1.166, 1.158, 1.153, 1.151, 1.151, 1.153, 1.159, 1.168, 1.179, 1.194, 1.212, 1.234, 1.256, 1.282, 1.311, 1.343, 1.382, 1.427, 1.489, 1.535, + 1.454, 1.423, 1.383, 1.345, 1.309, 1.278, 1.249, 1.226, 1.206, 1.187, 1.171, 1.158, 1.146, 1.138, 1.132, 1.129, 1.129, 1.133, 1.139, 1.147, 1.159, 1.173, 1.191, 1.212, 1.234, 1.261, 1.288, 1.321, 1.357, 1.401, 1.455, 1.489, + 1.433, 1.401, 1.362, 1.325, 1.289, 1.258, 1.231, 1.206, 1.187, 1.169, 1.153, 1.138, 1.129, 1.121, 1.115, 1.112, 1.112, 1.114, 1.121, 1.129, 1.141, 1.155, 1.172, 1.191, 1.214, 1.241, 1.269, 1.301, 1.337, 1.377, 1.428, 1.457, + 1.415, 1.382, 1.343, 1.306, 1.273, 1.241, 1.213, 1.189, 1.169, 1.153, 1.137, 1.123, 1.112, 1.105, 1.097, 1.095, 1.095, 1.098, 1.103, 1.112, 1.124, 1.139, 1.155, 1.173, 1.197, 1.222, 1.252, 1.282, 1.317, 1.356, 1.405, 1.434, + 1.398, 1.363, 1.325, 1.289, 1.256, 1.224, 1.198, 1.175, 1.155, 1.137, 1.123, 1.108, 1.097, 1.089, 1.083, 1.079, 1.079, 1.083, 1.088, 1.097, 1.109, 1.124, 1.139, 1.158, 1.181, 1.206, 1.234, 1.266, 1.299, 1.339, 1.384, 1.415, + 1.382, 1.347, 1.309, 1.274, 1.242, 1.211, 1.185, 1.162, 1.142, 1.124, 1.108, 1.095, 1.083, 1.075, 1.069, 1.066, 1.066, 1.068, 1.074, 1.083, 1.096, 1.109, 1.125, 1.145, 1.166, 1.191, 1.219, 1.251, 1.285, 1.324, 1.367, 1.399, + 1.369, 1.334, 1.296, 1.261, 1.228, 1.199, 1.173, 1.151, 1.131, 1.112, 1.095, 1.083, 1.071, 1.062, 1.056, 1.053, 1.053, 1.055, 1.061, 1.069, 1.083, 1.096, 1.112, 1.132, 1.153, 1.178, 1.206, 1.237, 1.271, 1.309, 1.353, 1.385, + 1.359, 1.321, 1.284, 1.251, 1.217, 1.189, 1.164, 1.141, 1.121, 1.102, 1.086, 1.071, 1.061, 1.049, 1.045, 1.042, 1.042, 1.043, 1.051, 1.061, 1.069, 1.085, 1.101, 1.121, 1.143, 1.167, 1.195, 1.225, 1.259, 1.298, 1.341, 1.375, + 1.351, 1.312, 1.275, 1.241, 1.209, 1.181, 1.155, 1.133, 1.112, 1.092, 1.076, 1.061, 1.049, 1.041, 1.034, 1.032, 1.032, 1.035, 1.041, 1.051, 1.061, 1.075, 1.092, 1.112, 1.133, 1.158, 1.185, 1.216, 1.249, 1.288, 1.331, 1.364, + 1.344, 1.303, 1.267, 1.233, 1.201, 1.173, 1.147, 1.124, 1.104, 1.085, 1.067, 1.053, 1.041, 1.033, 1.024, 1.022, 1.022, 1.025, 1.034, 1.041, 1.053, 1.066, 1.083, 1.103, 1.126, 1.149, 1.177, 1.207, 1.241, 1.279, 1.321, 1.357, + 1.339, 1.297, 1.261, 1.226, 1.194, 1.166, 1.142, 1.119, 1.098, 1.078, 1.061, 1.046, 1.034, 1.024, 1.017, 1.014, 1.014, 1.017, 1.025, 1.034, 1.046, 1.059, 1.077, 1.096, 1.118, 1.143, 1.169, 1.201, 1.235, 1.273, 1.314, 1.352, + 1.337, 1.293, 1.256, 1.223, 1.191, 1.163, 1.136, 1.114, 1.093, 1.074, 1.056, 1.041, 1.027, 1.017, 1.012, 1.006, 1.006, 1.013, 1.017, 1.028, 1.041, 1.055, 1.072, 1.092, 1.114, 1.138, 1.165, 1.195, 1.229, 1.268, 1.309, 1.348, + 1.337, 1.291, 1.253, 1.219, 1.187, 1.159, 1.133, 1.109, 1.089, 1.071, 1.053, 1.037, 1.023, 1.012, 1.006, 1.002, 1.003, 1.006, 1.013, 1.023, 1.038, 1.052, 1.069, 1.089, 1.111, 1.135, 1.161, 1.192, 1.226, 1.264, 1.306, 1.348, + 1.337, 1.291, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.035, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.341, 1.292, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.034, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.348, 1.298, 1.255, 1.219, 1.188, 1.159, 1.134, 1.111, 1.088, 1.069, 1.051, 1.035, 1.021, 1.009, 1.003, 1.001, 1.002, 1.004, 1.011, 1.022, 1.036, 1.053, 1.071, 1.089, 1.111, 1.135, 1.162, 1.191, 1.226, 1.264, 1.306, 1.347, + 1.354, 1.306, 1.258, 1.222, 1.191, 1.162, 1.135, 1.113, 1.092, 1.073, 1.054, 1.038, 1.024, 1.014, 1.008, 1.003, 1.004, 1.008, 1.014, 1.026, 1.039, 1.056, 1.073, 1.093, 1.115, 1.139, 1.165, 1.195, 1.229, 1.267, 1.309, 1.349, + 1.358, 1.312, 1.263, 1.227, 1.195, 1.167, 1.141, 1.117, 1.097, 1.078, 1.061, 1.043, 1.029, 1.021, 1.014, 1.008, 1.008, 1.014, 1.021, 1.032, 1.045, 1.061, 1.078, 1.097, 1.119, 1.144, 1.169, 1.201, 1.234, 1.272, 1.315, 1.353, + 1.364, 1.319, 1.269, 1.234, 1.201, 1.174, 1.148, 1.124, 1.103, 1.084, 1.067, 1.052, 1.038, 1.029, 1.021, 1.016, 1.016, 1.021, 1.029, 1.038, 1.051, 1.067, 1.084, 1.103, 1.126, 1.151, 1.176, 1.207, 1.241, 1.279, 1.321, 1.358, + 1.371, 1.326, 1.277, 1.242, 1.209, 1.181, 1.155, 1.132, 1.111, 1.092, 1.075, 1.061, 1.049, 1.038, 1.029, 1.027, 1.027, 1.029, 1.038, 1.047, 1.061, 1.075, 1.092, 1.111, 1.133, 1.157, 1.185, 1.213, 1.247, 1.286, 1.329, 1.365, + 1.379, 1.334, 1.287, 1.251, 1.219, 1.191, 1.164, 1.141, 1.119, 1.101, 1.085, 1.071, 1.061, 1.049, 1.041, 1.038, 1.038, 1.041, 1.047, 1.059, 1.071, 1.084, 1.101, 1.119, 1.141, 1.165, 1.193, 1.223, 1.257, 1.295, 1.338, 1.374, + 1.389, 1.343, 1.298, 1.262, 1.231, 1.201, 1.174, 1.151, 1.131, 1.111, 1.095, 1.083, 1.071, 1.061, 1.054, 1.051, 1.051, 1.054, 1.059, 1.071, 1.081, 1.094, 1.111, 1.129, 1.152, 1.176, 1.203, 1.235, 1.269, 1.307, 1.351, 1.384, + 1.401, 1.351, 1.311, 1.274, 1.242, 1.214, 1.187, 1.164, 1.142, 1.124, 1.108, 1.095, 1.083, 1.074, 1.068, 1.066, 1.066, 1.068, 1.073, 1.081, 1.094, 1.108, 1.123, 1.141, 1.164, 1.188, 1.215, 1.247, 1.281, 1.321, 1.364, 1.396, + 1.412, 1.366, 1.327, 1.289, 1.257, 1.227, 1.201, 1.176, 1.156, 1.137, 1.122, 1.108, 1.096, 1.088, 1.083, 1.081, 1.081, 1.082, 1.087, 1.095, 1.108, 1.122, 1.136, 1.154, 1.177, 1.201, 1.229, 1.261, 1.296, 1.337, 1.382, 1.409, + 1.421, 1.383, 1.343, 1.306, 1.273, 1.243, 1.216, 1.192, 1.169, 1.152, 1.137, 1.122, 1.111, 1.103, 1.098, 1.095, 1.095, 1.097, 1.102, 1.111, 1.123, 1.136, 1.152, 1.169, 1.191, 1.217, 1.246, 1.278, 1.314, 1.354, 1.399, 1.429, + 1.434, 1.402, 1.362, 1.324, 1.291, 1.261, 1.232, 1.208, 1.187, 1.168, 1.152, 1.138, 1.127, 1.119, 1.114, 1.112, 1.112, 1.115, 1.121, 1.128, 1.139, 1.152, 1.169, 1.186, 1.209, 1.234, 1.262, 1.295, 1.332, 1.372, 1.419, 1.451, + 1.453, 1.422, 1.382, 1.344, 1.309, 1.278, 1.249, 1.226, 1.204, 1.187, 1.168, 1.155, 1.144, 1.135, 1.131, 1.131, 1.131, 1.133, 1.138, 1.146, 1.157, 1.171, 1.186, 1.206, 1.227, 1.252, 1.281, 1.314, 1.351, 1.393, 1.442, 1.473, + 1.475, 1.446, 1.404, 1.366, 1.329, 1.298, 1.269, 1.245, 1.224, 1.204, 1.188, 1.174, 1.163, 1.154, 1.149, 1.148, 1.148, 1.152, 1.156, 1.164, 1.176, 1.189, 1.206, 1.226, 1.247, 1.274, 1.303, 1.336, 1.374, 1.417, 1.471, 1.505, + 1.503, 1.472, 1.428, 1.389, 1.353, 1.321, 1.291, 1.266, 1.245, 1.224, 1.207, 1.192, 1.183, 1.174, 1.169, 1.167, 1.168, 1.169, 1.175, 1.183, 1.195, 1.209, 1.226, 1.247, 1.267, 1.294, 1.325, 1.359, 1.397, 1.445, 1.505, 1.548, + 1.534, 1.503, 1.455, 1.413, 1.378, 1.344, 1.315, 1.289, 1.265, 1.243, 1.224, 1.207, 1.196, 1.192, 1.189, 1.189, 1.189, 1.189, 1.192, 1.198, 1.209, 1.226, 1.244, 1.266, 1.291, 1.318, 1.349, 1.383, 1.425, 1.475, 1.548, 1.591 + ], + "sigma": 0.00095, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2850, + "ccm": + [ + 1.97469, -0.71439, -0.26031, + -0.43521, 2.09769, -0.66248, + -0.04826, -0.84642, 1.89468 + ] + }, + { + "ct": 2960, + "ccm": + [ + 2.12952, -0.91185, -0.21768, + -0.38018, 1.90789, -0.52771, + 0.03988, -1.10079, 2.06092 + ] + }, + { + "ct": 3580, + "ccm": + [ + 2.03422, -0.80048, -0.23374, + -0.39089, 1.97221, -0.58132, + -0.08969, -0.61439, 1.70408 + ] + }, + { + "ct": 4559, + "ccm": + [ + 2.15423, -0.98143, -0.17279, + -0.38131, 2.14763, -0.76632, + -0.10069, -0.54383, 1.64452 + ] + }, + { + "ct": 5881, + "ccm": + [ + 2.18464, -0.95493, -0.22971, + -0.36826, 2.00298, -0.63471, + -0.15219, -0.38055, 1.53274 + ] + }, + { + "ct": 7600, + "ccm": + [ + 2.30687, -0.97295, -0.33392, + -0.30872, 2.32779, -1.01908, + -0.17761, -0.55891, 1.73651 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx477_16mm.json b/src/ipa/rpi/pisp/data/imx477_16mm.json new file mode 100644 index 00000000..f4e65c92 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx477_16mm.json @@ -0,0 +1,1240 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 12000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 740, + "reference_Y": 15051 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.809 + } + }, + { + "rpi.geq": + { + "offset": 204, + "slope": 0.0061 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2850.0, 0.4307, 0.3957, + 2960.0, 0.4159, 0.4313, + 3580.0, 0.3771, 0.5176, + 4559.0, 0.3031, 0.6573, + 5881.0, 0.2809, 0.6942, + 7600.0, 0.2263, 0.7762 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02634, + "transverse_neg": 0.02255 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.359, 2.354, 2.351, 2.351, 2.343, 2.337, 2.331, 2.325, 2.323, 2.321, 2.317, 2.315, 2.313, 2.313, 2.311, 2.312, 2.312, 2.313, 2.315, 2.315, 2.316, 2.317, 2.319, 2.323, 2.326, 2.329, 2.332, 2.332, 2.335, 2.337, 2.352, 2.363, + 2.352, 2.351, 2.349, 2.346, 2.342, 2.334, 2.328, 2.324, 2.321, 2.317, 2.315, 2.314, 2.312, 2.311, 2.311, 2.311, 2.311, 2.311, 2.312, 2.314, 2.315, 2.316, 2.317, 2.319, 2.324, 2.326, 2.328, 2.329, 2.331, 2.337, 2.348, 2.355, + 2.346, 2.346, 2.345, 2.344, 2.338, 2.329, 2.325, 2.319, 2.316, 2.314, 2.311, 2.309, 2.308, 2.306, 2.304, 2.304, 2.305, 2.307, 2.308, 2.309, 2.311, 2.311, 2.313, 2.316, 2.319, 2.322, 2.325, 2.326, 2.328, 2.335, 2.343, 2.349, + 2.342, 2.342, 2.341, 2.338, 2.332, 2.326, 2.319, 2.316, 2.312, 2.309, 2.308, 2.305, 2.303, 2.302, 2.301, 2.301, 2.302, 2.303, 2.304, 2.305, 2.305, 2.307, 2.311, 2.313, 2.315, 2.319, 2.321, 2.325, 2.328, 2.333, 2.338, 2.348, + 2.337, 2.337, 2.337, 2.336, 2.331, 2.322, 2.317, 2.312, 2.309, 2.307, 2.304, 2.302, 2.299, 2.299, 2.298, 2.298, 2.299, 2.299, 2.301, 2.302, 2.302, 2.304, 2.305, 2.309, 2.314, 2.316, 2.321, 2.324, 2.326, 2.329, 2.335, 2.343, + 2.335, 2.334, 2.333, 2.333, 2.326, 2.318, 2.313, 2.309, 2.306, 2.302, 2.299, 2.297, 2.297, 2.296, 2.295, 2.295, 2.294, 2.295, 2.296, 2.298, 2.298, 2.301, 2.303, 2.305, 2.311, 2.315, 2.319, 2.323, 2.325, 2.329, 2.333, 2.339, + 2.329, 2.331, 2.329, 2.329, 2.325, 2.315, 2.309, 2.306, 2.302, 2.299, 2.297, 2.295, 2.293, 2.292, 2.291, 2.291, 2.291, 2.291, 2.293, 2.294, 2.296, 2.298, 2.301, 2.304, 2.307, 2.313, 2.317, 2.319, 2.323, 2.327, 2.331, 2.339, + 2.329, 2.328, 2.328, 2.328, 2.321, 2.313, 2.307, 2.303, 2.299, 2.295, 2.294, 2.292, 2.289, 2.289, 2.288, 2.288, 2.288, 2.289, 2.289, 2.292, 2.294, 2.295, 2.297, 2.301, 2.306, 2.311, 2.315, 2.318, 2.319, 2.323, 2.329, 2.335, + 2.326, 2.327, 2.325, 2.325, 2.319, 2.311, 2.305, 2.299, 2.296, 2.293, 2.291, 2.289, 2.288, 2.287, 2.285, 2.285, 2.286, 2.288, 2.288, 2.289, 2.291, 2.294, 2.295, 2.298, 2.304, 2.308, 2.313, 2.315, 2.317, 2.319, 2.327, 2.335, + 2.325, 2.325, 2.323, 2.323, 2.317, 2.309, 2.303, 2.298, 2.294, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.284, 2.284, 2.285, 2.287, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.305, 2.309, 2.313, 2.315, 2.317, 2.325, 2.334, + 2.322, 2.324, 2.322, 2.322, 2.316, 2.306, 2.301, 2.296, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.283, 2.283, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.293, 2.296, 2.301, 2.304, 2.308, 2.311, 2.312, 2.315, 2.323, 2.333, + 2.321, 2.323, 2.322, 2.322, 2.314, 2.306, 2.299, 2.294, 2.291, 2.288, 2.286, 2.285, 2.284, 2.282, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.306, 2.308, 2.311, 2.312, 2.322, 2.332, + 2.319, 2.321, 2.321, 2.321, 2.314, 2.305, 2.297, 2.293, 2.289, 2.287, 2.285, 2.284, 2.283, 2.281, 2.281, 2.281, 2.282, 2.283, 2.283, 2.285, 2.287, 2.289, 2.291, 2.292, 2.297, 2.301, 2.305, 2.307, 2.309, 2.312, 2.321, 2.333, + 2.319, 2.321, 2.319, 2.319, 2.314, 2.303, 2.296, 2.293, 2.289, 2.286, 2.285, 2.283, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.296, 2.301, 2.305, 2.307, 2.308, 2.312, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.296, 2.291, 2.289, 2.286, 2.284, 2.282, 2.281, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.287, 2.288, 2.291, 2.295, 2.299, 2.304, 2.306, 2.307, 2.311, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.287, 2.285, 2.282, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.284, 2.285, 2.286, 2.288, 2.291, 2.295, 2.299, 2.303, 2.306, 2.307, 2.312, 2.321, 2.331, + 2.318, 2.319, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.286, 2.285, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.285, 2.286, 2.287, 2.291, 2.294, 2.298, 2.303, 2.306, 2.307, 2.311, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.302, 2.297, 2.292, 2.289, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.287, 2.289, 2.294, 2.297, 2.303, 2.305, 2.308, 2.313, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.303, 2.299, 2.293, 2.291, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.288, 2.291, 2.294, 2.298, 2.304, 2.306, 2.308, 2.312, 2.322, 2.331, + 2.319, 2.321, 2.321, 2.321, 2.315, 2.305, 2.301, 2.295, 2.292, 2.289, 2.286, 2.285, 2.283, 2.282, 2.282, 2.282, 2.284, 2.283, 2.284, 2.284, 2.285, 2.287, 2.288, 2.291, 2.294, 2.299, 2.304, 2.306, 2.309, 2.313, 2.322, 2.334, + 2.321, 2.322, 2.322, 2.322, 2.317, 2.307, 2.301, 2.296, 2.292, 2.291, 2.288, 2.286, 2.285, 2.284, 2.283, 2.284, 2.285, 2.284, 2.285, 2.285, 2.287, 2.288, 2.289, 2.293, 2.297, 2.301, 2.305, 2.308, 2.311, 2.314, 2.323, 2.335, + 2.322, 2.324, 2.324, 2.324, 2.319, 2.309, 2.303, 2.297, 2.295, 2.292, 2.291, 2.288, 2.286, 2.286, 2.285, 2.286, 2.286, 2.286, 2.287, 2.288, 2.289, 2.289, 2.291, 2.294, 2.299, 2.302, 2.307, 2.311, 2.312, 2.316, 2.325, 2.335, + 2.324, 2.326, 2.325, 2.326, 2.321, 2.311, 2.305, 2.301, 2.297, 2.295, 2.293, 2.291, 2.289, 2.289, 2.288, 2.288, 2.287, 2.288, 2.289, 2.291, 2.292, 2.292, 2.295, 2.299, 2.301, 2.304, 2.309, 2.312, 2.315, 2.319, 2.327, 2.337, + 2.329, 2.329, 2.328, 2.328, 2.323, 2.315, 2.308, 2.304, 2.301, 2.298, 2.296, 2.294, 2.291, 2.291, 2.289, 2.291, 2.291, 2.291, 2.292, 2.293, 2.294, 2.295, 2.297, 2.299, 2.303, 2.308, 2.312, 2.315, 2.318, 2.321, 2.329, 2.339, + 2.329, 2.331, 2.332, 2.332, 2.326, 2.318, 2.311, 2.306, 2.304, 2.301, 2.299, 2.297, 2.295, 2.293, 2.292, 2.292, 2.292, 2.293, 2.294, 2.294, 2.296, 2.297, 2.299, 2.302, 2.306, 2.311, 2.315, 2.318, 2.319, 2.324, 2.332, 2.342, + 2.331, 2.333, 2.334, 2.334, 2.328, 2.321, 2.313, 2.308, 2.305, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.295, 2.294, 2.296, 2.296, 2.297, 2.298, 2.299, 2.302, 2.305, 2.308, 2.314, 2.317, 2.321, 2.323, 2.327, 2.334, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.309, 2.306, 2.304, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.309, 2.315, 2.319, 2.321, 2.324, 2.328, 2.337, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.311, 2.306, 2.304, 2.303, 2.302, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.311, 2.314, 2.319, 2.323, 2.325, 2.329, 2.339, 2.348, + 2.329, 2.329, 2.329, 2.331, 2.326, 2.319, 2.312, 2.309, 2.304, 2.303, 2.302, 2.301, 2.298, 2.295, 2.294, 2.294, 2.295, 2.295, 2.296, 2.297, 2.299, 2.301, 2.302, 2.304, 2.308, 2.313, 2.319, 2.322, 2.325, 2.329, 2.339, 2.351, + 2.329, 2.329, 2.329, 2.329, 2.326, 2.317, 2.311, 2.308, 2.303, 2.302, 2.301, 2.298, 2.296, 2.295, 2.294, 2.294, 2.294, 2.294, 2.296, 2.297, 2.298, 2.299, 2.301, 2.304, 2.307, 2.312, 2.318, 2.322, 2.326, 2.331, 2.341, 2.355, + 2.339, 2.332, 2.331, 2.331, 2.327, 2.323, 2.316, 2.309, 2.306, 2.302, 2.301, 2.299, 2.297, 2.296, 2.295, 2.294, 2.294, 2.296, 2.297, 2.297, 2.299, 2.301, 2.303, 2.306, 2.308, 2.317, 2.322, 2.325, 2.329, 2.341, 2.353, 2.361, + 2.347, 2.347, 2.345, 2.343, 2.338, 2.332, 2.326, 2.322, 2.321, 2.318, 2.316, 2.315, 2.313, 2.312, 2.311, 2.311, 2.311, 2.311, 2.312, 2.315, 2.317, 2.318, 2.319, 2.323, 2.324, 2.329, 2.334, 2.337, 2.344, 2.347, 2.361, 2.364 + ] + }, + { + "ct": 5000, + "table": + [ + 3.869, 3.852, 3.844, 3.842, 3.836, 3.821, 3.807, 3.796, 3.789, 3.784, 3.778, 3.775, 3.769, 3.768, 3.765, 3.765, 3.767, 3.769, 3.772, 3.774, 3.773, 3.775, 3.779, 3.787, 3.793, 3.801, 3.806, 3.804, 3.813, 3.819, 3.855, 3.879, + 3.854, 3.844, 3.837, 3.836, 3.824, 3.811, 3.797, 3.789, 3.784, 3.777, 3.774, 3.769, 3.764, 3.758, 3.757, 3.758, 3.758, 3.761, 3.763, 3.764, 3.765, 3.766, 3.772, 3.778, 3.787, 3.792, 3.794, 3.798, 3.802, 3.815, 3.839, 3.873, + 3.838, 3.831, 3.826, 3.823, 3.813, 3.799, 3.787, 3.781, 3.773, 3.768, 3.763, 3.759, 3.753, 3.749, 3.745, 3.745, 3.745, 3.752, 3.754, 3.757, 3.757, 3.759, 3.763, 3.769, 3.773, 3.781, 3.786, 3.792, 3.798, 3.811, 3.831, 3.861, + 3.833, 3.822, 3.817, 3.816, 3.804, 3.788, 3.779, 3.772, 3.766, 3.759, 3.755, 3.749, 3.744, 3.741, 3.738, 3.739, 3.739, 3.741, 3.743, 3.747, 3.749, 3.751, 3.756, 3.764, 3.769, 3.776, 3.783, 3.789, 3.798, 3.809, 3.821, 3.855, + 3.824, 3.818, 3.808, 3.808, 3.797, 3.781, 3.772, 3.764, 3.757, 3.752, 3.747, 3.743, 3.737, 3.735, 3.733, 3.733, 3.733, 3.735, 3.737, 3.738, 3.741, 3.746, 3.749, 3.755, 3.766, 3.771, 3.781, 3.789, 3.794, 3.806, 3.818, 3.849, + 3.815, 3.808, 3.799, 3.801, 3.787, 3.775, 3.767, 3.757, 3.751, 3.745, 3.738, 3.734, 3.732, 3.727, 3.725, 3.723, 3.722, 3.722, 3.726, 3.729, 3.734, 3.738, 3.744, 3.749, 3.759, 3.769, 3.781, 3.788, 3.792, 3.799, 3.811, 3.841, + 3.804, 3.799, 3.793, 3.793, 3.783, 3.771, 3.759, 3.751, 3.744, 3.735, 3.732, 3.727, 3.723, 3.721, 3.719, 3.716, 3.716, 3.716, 3.718, 3.722, 3.727, 3.731, 3.737, 3.746, 3.756, 3.767, 3.776, 3.782, 3.788, 3.795, 3.808, 3.831, + 3.802, 3.797, 3.787, 3.787, 3.779, 3.762, 3.753, 3.744, 3.734, 3.727, 3.725, 3.721, 3.716, 3.714, 3.709, 3.709, 3.711, 3.711, 3.712, 3.717, 3.722, 3.725, 3.731, 3.739, 3.752, 3.762, 3.772, 3.778, 3.779, 3.789, 3.798, 3.826, + 3.791, 3.789, 3.784, 3.784, 3.775, 3.759, 3.746, 3.735, 3.729, 3.724, 3.718, 3.714, 3.712, 3.707, 3.704, 3.704, 3.706, 3.708, 3.709, 3.711, 3.716, 3.722, 3.726, 3.735, 3.746, 3.754, 3.767, 3.774, 3.777, 3.781, 3.794, 3.824, + 3.789, 3.784, 3.779, 3.781, 3.771, 3.753, 3.741, 3.732, 3.725, 3.719, 3.715, 3.711, 3.707, 3.704, 3.701, 3.701, 3.702, 3.704, 3.708, 3.709, 3.713, 3.718, 3.724, 3.731, 3.742, 3.749, 3.761, 3.768, 3.772, 3.778, 3.791, 3.822, + 3.789, 3.781, 3.777, 3.777, 3.764, 3.749, 3.739, 3.729, 3.722, 3.718, 3.711, 3.708, 3.705, 3.701, 3.699, 3.699, 3.699, 3.701, 3.705, 3.707, 3.711, 3.715, 3.721, 3.727, 3.738, 3.746, 3.757, 3.763, 3.765, 3.773, 3.788, 3.821, + 3.785, 3.779, 3.774, 3.774, 3.764, 3.747, 3.736, 3.726, 3.719, 3.711, 3.709, 3.706, 3.701, 3.698, 3.696, 3.695, 3.695, 3.698, 3.702, 3.704, 3.707, 3.712, 3.718, 3.725, 3.734, 3.741, 3.753, 3.756, 3.759, 3.764, 3.784, 3.818, + 3.779, 3.776, 3.773, 3.773, 3.759, 3.744, 3.733, 3.724, 3.714, 3.709, 3.706, 3.704, 3.699, 3.696, 3.694, 3.694, 3.694, 3.697, 3.701, 3.703, 3.706, 3.709, 3.714, 3.721, 3.731, 3.737, 3.749, 3.753, 3.758, 3.762, 3.783, 3.819, + 3.779, 3.776, 3.769, 3.769, 3.757, 3.741, 3.729, 3.721, 3.712, 3.708, 3.705, 3.701, 3.697, 3.695, 3.694, 3.694, 3.695, 3.696, 3.698, 3.702, 3.705, 3.709, 3.712, 3.717, 3.728, 3.736, 3.749, 3.752, 3.756, 3.761, 3.781, 3.815, + 3.779, 3.773, 3.768, 3.768, 3.756, 3.738, 3.731, 3.719, 3.711, 3.707, 3.703, 3.698, 3.695, 3.694, 3.694, 3.695, 3.695, 3.695, 3.696, 3.702, 3.705, 3.708, 3.712, 3.717, 3.728, 3.736, 3.747, 3.751, 3.754, 3.761, 3.781, 3.815, + 3.782, 3.773, 3.767, 3.767, 3.755, 3.738, 3.728, 3.721, 3.711, 3.707, 3.701, 3.698, 3.695, 3.693, 3.694, 3.696, 3.695, 3.695, 3.695, 3.701, 3.703, 3.706, 3.711, 3.715, 3.726, 3.735, 3.745, 3.751, 3.754, 3.763, 3.779, 3.815, + 3.781, 3.771, 3.767, 3.767, 3.754, 3.739, 3.726, 3.721, 3.712, 3.706, 3.701, 3.698, 3.695, 3.693, 3.693, 3.695, 3.695, 3.695, 3.696, 3.698, 3.703, 3.705, 3.709, 3.715, 3.725, 3.734, 3.745, 3.751, 3.755, 3.762, 3.783, 3.818, + 3.781, 3.774, 3.767, 3.767, 3.755, 3.741, 3.729, 3.722, 3.712, 3.708, 3.701, 3.699, 3.695, 3.693, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.725, 3.732, 3.746, 3.751, 3.756, 3.763, 3.783, 3.821, + 3.781, 3.774, 3.769, 3.769, 3.756, 3.741, 3.731, 3.724, 3.713, 3.711, 3.707, 3.699, 3.697, 3.694, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.724, 3.734, 3.747, 3.751, 3.756, 3.765, 3.784, 3.821, + 3.784, 3.776, 3.773, 3.773, 3.759, 3.742, 3.733, 3.726, 3.719, 3.711, 3.709, 3.703, 3.698, 3.695, 3.694, 3.695, 3.697, 3.696, 3.698, 3.699, 3.703, 3.706, 3.711, 3.714, 3.727, 3.735, 3.746, 3.751, 3.757, 3.766, 3.787, 3.822, + 3.786, 3.783, 3.774, 3.774, 3.766, 3.747, 3.737, 3.727, 3.722, 3.716, 3.711, 3.706, 3.702, 3.698, 3.697, 3.698, 3.699, 3.699, 3.701, 3.703, 3.706, 3.711, 3.713, 3.719, 3.731, 3.739, 3.748, 3.753, 3.761, 3.769, 3.789, 3.826, + 3.786, 3.784, 3.779, 3.779, 3.769, 3.751, 3.742, 3.732, 3.725, 3.719, 3.715, 3.711, 3.706, 3.704, 3.701, 3.701, 3.702, 3.702, 3.705, 3.707, 3.712, 3.714, 3.717, 3.724, 3.733, 3.743, 3.749, 3.758, 3.764, 3.769, 3.791, 3.826, + 3.793, 3.787, 3.782, 3.782, 3.774, 3.756, 3.747, 3.737, 3.729, 3.725, 3.719, 3.715, 3.712, 3.708, 3.707, 3.706, 3.707, 3.708, 3.709, 3.713, 3.714, 3.717, 3.723, 3.729, 3.736, 3.747, 3.757, 3.764, 3.768, 3.774, 3.794, 3.829, + 3.794, 3.791, 3.786, 3.786, 3.779, 3.762, 3.751, 3.742, 3.735, 3.729, 3.725, 3.719, 3.716, 3.711, 3.709, 3.709, 3.709, 3.711, 3.716, 3.717, 3.721, 3.723, 3.726, 3.732, 3.741, 3.752, 3.761, 3.767, 3.773, 3.779, 3.801, 3.829, + 3.802, 3.798, 3.793, 3.793, 3.779, 3.766, 3.754, 3.746, 3.741, 3.736, 3.731, 3.726, 3.719, 3.717, 3.716, 3.715, 3.716, 3.717, 3.719, 3.721, 3.724, 3.726, 3.731, 3.737, 3.744, 3.756, 3.766, 3.772, 3.776, 3.784, 3.807, 3.839, + 3.805, 3.799, 3.795, 3.795, 3.784, 3.767, 3.757, 3.749, 3.744, 3.739, 3.736, 3.731, 3.726, 3.722, 3.719, 3.719, 3.719, 3.721, 3.723, 3.725, 3.727, 3.732, 3.738, 3.742, 3.751, 3.761, 3.771, 3.775, 3.782, 3.789, 3.811, 3.841, + 3.804, 3.801, 3.799, 3.799, 3.787, 3.772, 3.761, 3.752, 3.746, 3.742, 3.739, 3.735, 3.729, 3.726, 3.723, 3.724, 3.725, 3.726, 3.727, 3.728, 3.732, 3.736, 3.739, 3.745, 3.754, 3.765, 3.775, 3.779, 3.785, 3.795, 3.816, 3.844, + 3.801, 3.799, 3.796, 3.796, 3.787, 3.773, 3.761, 3.753, 3.746, 3.743, 3.739, 3.735, 3.731, 3.726, 3.725, 3.725, 3.725, 3.726, 3.727, 3.729, 3.733, 3.736, 3.741, 3.745, 3.755, 3.766, 3.776, 3.783, 3.786, 3.797, 3.819, 3.851, + 3.799, 3.795, 3.788, 3.788, 3.783, 3.772, 3.759, 3.749, 3.744, 3.738, 3.735, 3.733, 3.726, 3.724, 3.722, 3.722, 3.723, 3.724, 3.725, 3.727, 3.729, 3.733, 3.736, 3.742, 3.754, 3.762, 3.772, 3.779, 3.784, 3.796, 3.821, 3.859, + 3.799, 3.789, 3.787, 3.788, 3.779, 3.766, 3.755, 3.749, 3.742, 3.736, 3.733, 3.727, 3.723, 3.722, 3.721, 3.719, 3.719, 3.721, 3.725, 3.726, 3.728, 3.732, 3.734, 3.741, 3.747, 3.758, 3.771, 3.778, 3.785, 3.796, 3.825, 3.862, + 3.824, 3.799, 3.789, 3.789, 3.788, 3.777, 3.761, 3.751, 3.743, 3.739, 3.736, 3.728, 3.726, 3.725, 3.721, 3.719, 3.721, 3.723, 3.727, 3.728, 3.729, 3.733, 3.737, 3.744, 3.755, 3.769, 3.776, 3.784, 3.793, 3.819, 3.863, 3.877, + 3.833, 3.833, 3.833, 3.842, 3.825, 3.815, 3.807, 3.799, 3.792, 3.788, 3.785, 3.782, 3.778, 3.777, 3.773, 3.772, 3.772, 3.774, 3.778, 3.779, 3.779, 3.785, 3.792, 3.798, 3.803, 3.811, 3.822, 3.834, 3.843, 3.846, 3.877, 3.886 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.616, 2.616, 2.618, 2.621, 2.619, 2.618, 2.615, 2.615, 2.613, 2.611, 2.609, 2.609, 2.609, 2.611, 2.611, 2.611, 2.611, 2.609, 2.608, 2.608, 2.611, 2.613, 2.613, 2.614, 2.614, 2.615, 2.615, 2.622, 2.624, 2.621, 2.624, 2.641, + 2.616, 2.618, 2.621, 2.623, 2.623, 2.619, 2.618, 2.616, 2.616, 2.613, 2.611, 2.611, 2.611, 2.611, 2.612, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.612, 2.613, 2.612, 2.613, 2.615, 2.617, 2.621, 2.621, 2.619, 2.621, 2.641, + 2.621, 2.624, 2.627, 2.627, 2.625, 2.623, 2.621, 2.619, 2.618, 2.618, 2.618, 2.617, 2.616, 2.616, 2.615, 2.613, 2.612, 2.613, 2.613, 2.614, 2.614, 2.613, 2.614, 2.613, 2.614, 2.617, 2.619, 2.621, 2.621, 2.619, 2.623, 2.643, + 2.626, 2.627, 2.628, 2.629, 2.628, 2.625, 2.622, 2.621, 2.621, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.616, 2.616, 2.616, 2.618, 2.618, 2.617, 2.617, 2.618, 2.619, 2.621, 2.623, 2.624, 2.626, 2.625, 2.624, 2.625, 2.654, + 2.627, 2.628, 2.628, 2.628, 2.626, 2.623, 2.622, 2.622, 2.622, 2.622, 2.621, 2.621, 2.619, 2.617, 2.617, 2.616, 2.617, 2.617, 2.618, 2.619, 2.618, 2.618, 2.618, 2.621, 2.622, 2.624, 2.626, 2.627, 2.627, 2.626, 2.628, 2.655, + 2.625, 2.626, 2.627, 2.626, 2.625, 2.623, 2.622, 2.621, 2.622, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.616, 2.616, 2.616, 2.616, 2.616, 2.617, 2.618, 2.619, 2.621, 2.622, 2.624, 2.626, 2.628, 2.628, 2.629, 2.629, 2.655, + 2.626, 2.625, 2.626, 2.625, 2.625, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.616, 2.614, 2.613, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.618, 2.619, 2.621, 2.623, 2.624, 2.627, 2.629, 2.631, 2.629, 2.631, 2.651, + 2.625, 2.625, 2.625, 2.624, 2.623, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.614, 2.613, 2.612, 2.611, 2.611, 2.612, 2.612, 2.613, 2.616, 2.618, 2.619, 2.622, 2.624, 2.626, 2.628, 2.631, 2.631, 2.631, 2.631, 2.651, + 2.625, 2.625, 2.624, 2.623, 2.622, 2.622, 2.622, 2.622, 2.622, 2.621, 2.617, 2.615, 2.613, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.613, 2.615, 2.618, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.624, 2.624, 2.622, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.618, 2.616, 2.614, 2.612, 2.611, 2.609, 2.609, 2.608, 2.609, 2.611, 2.611, 2.615, 2.617, 2.619, 2.621, 2.625, 2.628, 2.631, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.622, 2.623, 2.622, 2.622, 2.621, 2.619, 2.619, 2.619, 2.618, 2.616, 2.614, 2.613, 2.611, 2.609, 2.608, 2.606, 2.607, 2.607, 2.609, 2.611, 2.615, 2.617, 2.619, 2.622, 2.626, 2.629, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.622, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.614, 2.613, 2.611, 2.611, 2.607, 2.606, 2.605, 2.604, 2.605, 2.607, 2.609, 2.613, 2.616, 2.619, 2.622, 2.627, 2.631, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.613, 2.609, 2.607, 2.604, 2.602, 2.601, 2.602, 2.603, 2.605, 2.609, 2.612, 2.616, 2.619, 2.624, 2.628, 2.631, 2.632, 2.633, 2.629, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.615, 2.614, 2.612, 2.608, 2.603, 2.601, 2.598, 2.597, 2.599, 2.602, 2.605, 2.608, 2.611, 2.615, 2.622, 2.625, 2.629, 2.631, 2.631, 2.633, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.616, 2.614, 2.611, 2.606, 2.601, 2.598, 2.595, 2.595, 2.597, 2.601, 2.604, 2.608, 2.612, 2.615, 2.623, 2.627, 2.629, 2.631, 2.631, 2.632, 2.631, 2.628, 2.628, 2.651, + 2.622, 2.623, 2.624, 2.624, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.609, 2.606, 2.601, 2.596, 2.594, 2.594, 2.596, 2.599, 2.603, 2.609, 2.613, 2.617, 2.623, 2.627, 2.629, 2.631, 2.632, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.623, 2.625, 2.625, 2.624, 2.621, 2.621, 2.619, 2.617, 2.616, 2.613, 2.608, 2.605, 2.601, 2.595, 2.593, 2.593, 2.595, 2.598, 2.604, 2.609, 2.615, 2.619, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.632, 2.629, 2.631, 2.651, + 2.624, 2.626, 2.626, 2.623, 2.621, 2.619, 2.618, 2.617, 2.615, 2.612, 2.608, 2.605, 2.601, 2.597, 2.595, 2.595, 2.596, 2.598, 2.605, 2.609, 2.616, 2.621, 2.626, 2.627, 2.629, 2.631, 2.633, 2.633, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.618, 2.617, 2.614, 2.612, 2.609, 2.606, 2.602, 2.599, 2.598, 2.597, 2.598, 2.602, 2.607, 2.612, 2.619, 2.621, 2.626, 2.628, 2.629, 2.632, 2.633, 2.634, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.621, 2.618, 2.617, 2.614, 2.612, 2.611, 2.608, 2.604, 2.602, 2.599, 2.599, 2.603, 2.606, 2.611, 2.616, 2.621, 2.624, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.634, 2.633, 2.633, 2.656, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.611, 2.611, 2.607, 2.604, 2.604, 2.604, 2.606, 2.609, 2.613, 2.619, 2.622, 2.625, 2.628, 2.631, 2.632, 2.633, 2.633, 2.636, 2.636, 2.634, 2.634, 2.658, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.619, 2.618, 2.616, 2.614, 2.613, 2.612, 2.611, 2.609, 2.608, 2.607, 2.608, 2.609, 2.613, 2.617, 2.621, 2.623, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.635, 2.636, 2.636, 2.636, 2.661, + 2.623, 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.616, 2.615, 2.614, 2.613, 2.612, 2.612, 2.611, 2.611, 2.611, 2.614, 2.615, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.633, 2.635, 2.635, 2.637, 2.637, 2.636, 2.637, 2.661, + 2.623, 2.624, 2.625, 2.626, 2.624, 2.621, 2.619, 2.617, 2.616, 2.615, 2.615, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.619, 2.621, 2.623, 2.626, 2.628, 2.631, 2.632, 2.634, 2.635, 2.636, 2.637, 2.638, 2.637, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.627, 2.626, 2.623, 2.619, 2.619, 2.618, 2.618, 2.618, 2.617, 2.617, 2.616, 2.616, 2.616, 2.619, 2.622, 2.623, 2.625, 2.628, 2.628, 2.631, 2.632, 2.634, 2.636, 2.638, 2.639, 2.639, 2.638, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.628, 2.626, 2.623, 2.621, 2.619, 2.619, 2.619, 2.619, 2.619, 2.619, 2.618, 2.618, 2.619, 2.623, 2.624, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.635, 2.638, 2.639, 2.639, 2.639, 2.636, 2.636, 2.662, + 2.625, 2.627, 2.628, 2.628, 2.626, 2.624, 2.623, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.624, 2.624, 2.625, 2.627, 2.628, 2.631, 2.631, 2.632, 2.634, 2.636, 2.639, 2.639, 2.641, 2.639, 2.635, 2.635, 2.663, + 2.625, 2.626, 2.628, 2.628, 2.627, 2.625, 2.624, 2.623, 2.623, 2.622, 2.623, 2.624, 2.624, 2.625, 2.625, 2.625, 2.625, 2.626, 2.627, 2.629, 2.631, 2.632, 2.633, 2.635, 2.638, 2.641, 2.642, 2.643, 2.642, 2.636, 2.636, 2.665, + 2.624, 2.626, 2.628, 2.628, 2.628, 2.626, 2.624, 2.624, 2.623, 2.623, 2.623, 2.625, 2.627, 2.627, 2.626, 2.626, 2.626, 2.627, 2.628, 2.629, 2.632, 2.633, 2.635, 2.637, 2.639, 2.642, 2.644, 2.644, 2.642, 2.638, 2.638, 2.665, + 2.623, 2.625, 2.626, 2.627, 2.626, 2.626, 2.624, 2.623, 2.623, 2.623, 2.623, 2.623, 2.626, 2.627, 2.626, 2.626, 2.626, 2.626, 2.628, 2.628, 2.629, 2.631, 2.634, 2.636, 2.639, 2.642, 2.644, 2.643, 2.641, 2.637, 2.638, 2.659, + 2.623, 2.627, 2.627, 2.627, 2.627, 2.628, 2.627, 2.624, 2.624, 2.623, 2.624, 2.624, 2.628, 2.628, 2.627, 2.628, 2.628, 2.628, 2.629, 2.629, 2.631, 2.635, 2.637, 2.639, 2.641, 2.643, 2.646, 2.645, 2.643, 2.641, 2.654, 2.659, + 2.642, 2.641, 2.643, 2.645, 2.645, 2.644, 2.644, 2.643, 2.643, 2.642, 2.642, 2.642, 2.643, 2.644, 2.644, 2.644, 2.646, 2.646, 2.647, 2.649, 2.651, 2.652, 2.654, 2.656, 2.658, 2.661, 2.661, 2.661, 2.659, 2.654, 2.659, 2.659 + ] + }, + { + "ct": 5000, + "table": + [ + 1.391, 1.394, 1.395, 1.396, 1.398, 1.398, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.398, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.399, 1.397, 1.399, 1.402, + 1.393, 1.395, 1.396, 1.398, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.401, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.399, 1.401, 1.401, 1.399, 1.398, 1.399, 1.402, + 1.398, 1.401, 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.401, 1.401, 1.399, 1.399, 1.401, 1.401, 1.401, 1.401, 1.399, 1.401, 1.406, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.402, 1.403, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.405, 1.404, 1.404, 1.404, 1.404, 1.405, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.406, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.408, 1.408, 1.407, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.403, 1.402, 1.402, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.408, 1.407, 1.405, 1.405, 1.414, + 1.402, 1.402, 1.402, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.403, 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.407, 1.409, 1.409, 1.409, 1.409, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.403, 1.404, 1.405, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.408, 1.405, 1.405, 1.413, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.402, 1.401, 1.399, 1.398, 1.398, 1.399, 1.399, 1.401, 1.403, 1.404, 1.405, 1.407, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.403, 1.404, 1.405, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.399, 1.401, 1.403, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.398, 1.401, 1.403, 1.406, 1.407, 1.409, 1.409, 1.411, 1.409, 1.409, 1.409, 1.408, 1.407, 1.407, 1.413, + 1.403, 1.404, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.404, 1.404, 1.403, 1.402, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.402, 1.404, 1.406, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.414, + 1.403, 1.403, 1.404, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.402, 1.401, 1.399, 1.398, 1.398, 1.398, 1.401, 1.403, 1.404, 1.408, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.402, 1.404, 1.406, 1.407, 1.408, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.409, 1.408, 1.408, 1.415, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.406, 1.408, 1.408, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.405, 1.406, 1.407, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.407, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.416, + 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.406, 1.407, 1.407, 1.407, 1.408, 1.409, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.417, + 1.402, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.415, + 1.402, 1.402, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.405, 1.406, 1.406, 1.406, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.412, 1.412, 1.413, 1.413, 1.411, 1.408, 1.411, 1.413, + 1.406, 1.406, 1.408, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.414, 1.414, 1.414, 1.414, 1.415, 1.415, 1.415, 1.415, 1.416, 1.416, 1.416, 1.417, 1.418, 1.418, 1.417, 1.417, 1.414, 1.411, 1.413, 1.413 + ] + } + ], + "luminance_lut": + [ + 1.554, 1.522, 1.466, 1.422, 1.385, 1.351, 1.322, 1.294, 1.269, 1.246, 1.228, 1.214, 1.207, 1.202, 1.199, 1.199, 1.199, 1.199, 1.202, 1.207, 1.218, 1.235, 1.255, 1.279, 1.305, 1.333, 1.365, 1.402, 1.447, 1.508, 1.602, 1.638, + 1.522, 1.478, 1.431, 1.391, 1.355, 1.323, 1.298, 1.271, 1.247, 1.228, 1.212, 1.199, 1.187, 1.179, 1.173, 1.172, 1.172, 1.174, 1.179, 1.189, 1.201, 1.216, 1.235, 1.256, 1.282, 1.308, 1.335, 1.368, 1.411, 1.461, 1.535, 1.602, + 1.479, 1.449, 1.407, 1.367, 1.332, 1.301, 1.271, 1.247, 1.226, 1.208, 1.191, 1.178, 1.166, 1.158, 1.153, 1.151, 1.151, 1.153, 1.159, 1.168, 1.179, 1.194, 1.212, 1.234, 1.256, 1.282, 1.311, 1.343, 1.382, 1.427, 1.489, 1.535, + 1.454, 1.423, 1.383, 1.345, 1.309, 1.278, 1.249, 1.226, 1.206, 1.187, 1.171, 1.158, 1.146, 1.138, 1.132, 1.129, 1.129, 1.133, 1.139, 1.147, 1.159, 1.173, 1.191, 1.212, 1.234, 1.261, 1.288, 1.321, 1.357, 1.401, 1.455, 1.489, + 1.433, 1.401, 1.362, 1.325, 1.289, 1.258, 1.231, 1.206, 1.187, 1.169, 1.153, 1.138, 1.129, 1.121, 1.115, 1.112, 1.112, 1.114, 1.121, 1.129, 1.141, 1.155, 1.172, 1.191, 1.214, 1.241, 1.269, 1.301, 1.337, 1.377, 1.428, 1.457, + 1.415, 1.382, 1.343, 1.306, 1.273, 1.241, 1.213, 1.189, 1.169, 1.153, 1.137, 1.123, 1.112, 1.105, 1.097, 1.095, 1.095, 1.098, 1.103, 1.112, 1.124, 1.139, 1.155, 1.173, 1.197, 1.222, 1.252, 1.282, 1.317, 1.356, 1.405, 1.434, + 1.398, 1.363, 1.325, 1.289, 1.256, 1.224, 1.198, 1.175, 1.155, 1.137, 1.123, 1.108, 1.097, 1.089, 1.083, 1.079, 1.079, 1.083, 1.088, 1.097, 1.109, 1.124, 1.139, 1.158, 1.181, 1.206, 1.234, 1.266, 1.299, 1.339, 1.384, 1.415, + 1.382, 1.347, 1.309, 1.274, 1.242, 1.211, 1.185, 1.162, 1.142, 1.124, 1.108, 1.095, 1.083, 1.075, 1.069, 1.066, 1.066, 1.068, 1.074, 1.083, 1.096, 1.109, 1.125, 1.145, 1.166, 1.191, 1.219, 1.251, 1.285, 1.324, 1.367, 1.399, + 1.369, 1.334, 1.296, 1.261, 1.228, 1.199, 1.173, 1.151, 1.131, 1.112, 1.095, 1.083, 1.071, 1.062, 1.056, 1.053, 1.053, 1.055, 1.061, 1.069, 1.083, 1.096, 1.112, 1.132, 1.153, 1.178, 1.206, 1.237, 1.271, 1.309, 1.353, 1.385, + 1.359, 1.321, 1.284, 1.251, 1.217, 1.189, 1.164, 1.141, 1.121, 1.102, 1.086, 1.071, 1.061, 1.049, 1.045, 1.042, 1.042, 1.043, 1.051, 1.061, 1.069, 1.085, 1.101, 1.121, 1.143, 1.167, 1.195, 1.225, 1.259, 1.298, 1.341, 1.375, + 1.351, 1.312, 1.275, 1.241, 1.209, 1.181, 1.155, 1.133, 1.112, 1.092, 1.076, 1.061, 1.049, 1.041, 1.034, 1.032, 1.032, 1.035, 1.041, 1.051, 1.061, 1.075, 1.092, 1.112, 1.133, 1.158, 1.185, 1.216, 1.249, 1.288, 1.331, 1.364, + 1.344, 1.303, 1.267, 1.233, 1.201, 1.173, 1.147, 1.124, 1.104, 1.085, 1.067, 1.053, 1.041, 1.033, 1.024, 1.022, 1.022, 1.025, 1.034, 1.041, 1.053, 1.066, 1.083, 1.103, 1.126, 1.149, 1.177, 1.207, 1.241, 1.279, 1.321, 1.357, + 1.339, 1.297, 1.261, 1.226, 1.194, 1.166, 1.142, 1.119, 1.098, 1.078, 1.061, 1.046, 1.034, 1.024, 1.017, 1.014, 1.014, 1.017, 1.025, 1.034, 1.046, 1.059, 1.077, 1.096, 1.118, 1.143, 1.169, 1.201, 1.235, 1.273, 1.314, 1.352, + 1.337, 1.293, 1.256, 1.223, 1.191, 1.163, 1.136, 1.114, 1.093, 1.074, 1.056, 1.041, 1.027, 1.017, 1.012, 1.006, 1.006, 1.013, 1.017, 1.028, 1.041, 1.055, 1.072, 1.092, 1.114, 1.138, 1.165, 1.195, 1.229, 1.268, 1.309, 1.348, + 1.337, 1.291, 1.253, 1.219, 1.187, 1.159, 1.133, 1.109, 1.089, 1.071, 1.053, 1.037, 1.023, 1.012, 1.006, 1.002, 1.003, 1.006, 1.013, 1.023, 1.038, 1.052, 1.069, 1.089, 1.111, 1.135, 1.161, 1.192, 1.226, 1.264, 1.306, 1.348, + 1.337, 1.291, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.035, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.341, 1.292, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.034, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.348, 1.298, 1.255, 1.219, 1.188, 1.159, 1.134, 1.111, 1.088, 1.069, 1.051, 1.035, 1.021, 1.009, 1.003, 1.001, 1.002, 1.004, 1.011, 1.022, 1.036, 1.053, 1.071, 1.089, 1.111, 1.135, 1.162, 1.191, 1.226, 1.264, 1.306, 1.347, + 1.354, 1.306, 1.258, 1.222, 1.191, 1.162, 1.135, 1.113, 1.092, 1.073, 1.054, 1.038, 1.024, 1.014, 1.008, 1.003, 1.004, 1.008, 1.014, 1.026, 1.039, 1.056, 1.073, 1.093, 1.115, 1.139, 1.165, 1.195, 1.229, 1.267, 1.309, 1.349, + 1.358, 1.312, 1.263, 1.227, 1.195, 1.167, 1.141, 1.117, 1.097, 1.078, 1.061, 1.043, 1.029, 1.021, 1.014, 1.008, 1.008, 1.014, 1.021, 1.032, 1.045, 1.061, 1.078, 1.097, 1.119, 1.144, 1.169, 1.201, 1.234, 1.272, 1.315, 1.353, + 1.364, 1.319, 1.269, 1.234, 1.201, 1.174, 1.148, 1.124, 1.103, 1.084, 1.067, 1.052, 1.038, 1.029, 1.021, 1.016, 1.016, 1.021, 1.029, 1.038, 1.051, 1.067, 1.084, 1.103, 1.126, 1.151, 1.176, 1.207, 1.241, 1.279, 1.321, 1.358, + 1.371, 1.326, 1.277, 1.242, 1.209, 1.181, 1.155, 1.132, 1.111, 1.092, 1.075, 1.061, 1.049, 1.038, 1.029, 1.027, 1.027, 1.029, 1.038, 1.047, 1.061, 1.075, 1.092, 1.111, 1.133, 1.157, 1.185, 1.213, 1.247, 1.286, 1.329, 1.365, + 1.379, 1.334, 1.287, 1.251, 1.219, 1.191, 1.164, 1.141, 1.119, 1.101, 1.085, 1.071, 1.061, 1.049, 1.041, 1.038, 1.038, 1.041, 1.047, 1.059, 1.071, 1.084, 1.101, 1.119, 1.141, 1.165, 1.193, 1.223, 1.257, 1.295, 1.338, 1.374, + 1.389, 1.343, 1.298, 1.262, 1.231, 1.201, 1.174, 1.151, 1.131, 1.111, 1.095, 1.083, 1.071, 1.061, 1.054, 1.051, 1.051, 1.054, 1.059, 1.071, 1.081, 1.094, 1.111, 1.129, 1.152, 1.176, 1.203, 1.235, 1.269, 1.307, 1.351, 1.384, + 1.401, 1.351, 1.311, 1.274, 1.242, 1.214, 1.187, 1.164, 1.142, 1.124, 1.108, 1.095, 1.083, 1.074, 1.068, 1.066, 1.066, 1.068, 1.073, 1.081, 1.094, 1.108, 1.123, 1.141, 1.164, 1.188, 1.215, 1.247, 1.281, 1.321, 1.364, 1.396, + 1.412, 1.366, 1.327, 1.289, 1.257, 1.227, 1.201, 1.176, 1.156, 1.137, 1.122, 1.108, 1.096, 1.088, 1.083, 1.081, 1.081, 1.082, 1.087, 1.095, 1.108, 1.122, 1.136, 1.154, 1.177, 1.201, 1.229, 1.261, 1.296, 1.337, 1.382, 1.409, + 1.421, 1.383, 1.343, 1.306, 1.273, 1.243, 1.216, 1.192, 1.169, 1.152, 1.137, 1.122, 1.111, 1.103, 1.098, 1.095, 1.095, 1.097, 1.102, 1.111, 1.123, 1.136, 1.152, 1.169, 1.191, 1.217, 1.246, 1.278, 1.314, 1.354, 1.399, 1.429, + 1.434, 1.402, 1.362, 1.324, 1.291, 1.261, 1.232, 1.208, 1.187, 1.168, 1.152, 1.138, 1.127, 1.119, 1.114, 1.112, 1.112, 1.115, 1.121, 1.128, 1.139, 1.152, 1.169, 1.186, 1.209, 1.234, 1.262, 1.295, 1.332, 1.372, 1.419, 1.451, + 1.453, 1.422, 1.382, 1.344, 1.309, 1.278, 1.249, 1.226, 1.204, 1.187, 1.168, 1.155, 1.144, 1.135, 1.131, 1.131, 1.131, 1.133, 1.138, 1.146, 1.157, 1.171, 1.186, 1.206, 1.227, 1.252, 1.281, 1.314, 1.351, 1.393, 1.442, 1.473, + 1.475, 1.446, 1.404, 1.366, 1.329, 1.298, 1.269, 1.245, 1.224, 1.204, 1.188, 1.174, 1.163, 1.154, 1.149, 1.148, 1.148, 1.152, 1.156, 1.164, 1.176, 1.189, 1.206, 1.226, 1.247, 1.274, 1.303, 1.336, 1.374, 1.417, 1.471, 1.505, + 1.503, 1.472, 1.428, 1.389, 1.353, 1.321, 1.291, 1.266, 1.245, 1.224, 1.207, 1.192, 1.183, 1.174, 1.169, 1.167, 1.168, 1.169, 1.175, 1.183, 1.195, 1.209, 1.226, 1.247, 1.267, 1.294, 1.325, 1.359, 1.397, 1.445, 1.505, 1.548, + 1.534, 1.503, 1.455, 1.413, 1.378, 1.344, 1.315, 1.289, 1.265, 1.243, 1.224, 1.207, 1.196, 1.192, 1.189, 1.189, 1.189, 1.189, 1.192, 1.198, 1.209, 1.226, 1.244, 1.266, 1.291, 1.318, 1.349, 1.383, 1.425, 1.475, 1.548, 1.591 + ], + "sigma": 0.00095, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2850, + "ccm": + [ + 1.97469, -0.71439, -0.26031, + -0.43521, 2.09769, -0.66248, + -0.04826, -0.84642, 1.89468 + ] + }, + { + "ct": 2960, + "ccm": + [ + 2.12952, -0.91185, -0.21768, + -0.38018, 1.90789, -0.52771, + 0.03988, -1.10079, 2.06092 + ] + }, + { + "ct": 3580, + "ccm": + [ + 2.03422, -0.80048, -0.23374, + -0.39089, 1.97221, -0.58132, + -0.08969, -0.61439, 1.70408 + ] + }, + { + "ct": 4559, + "ccm": + [ + 2.15423, -0.98143, -0.17279, + -0.38131, 2.14763, -0.76632, + -0.10069, -0.54383, 1.64452 + ] + }, + { + "ct": 5881, + "ccm": + [ + 2.18464, -0.95493, -0.22971, + -0.36826, 2.00298, -0.63471, + -0.15219, -0.38055, 1.53274 + ] + }, + { + "ct": 7600, + "ccm": + [ + 2.30687, -0.97295, -0.33392, + -0.30872, 2.32779, -1.01908, + -0.17761, -0.55891, 1.73651 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.cac": + { + "strength": 1.0, + "lut_rx": + [ + -0.34, -0.26, -0.18, -0.1, -0.02, 0.06, 0.13, 0.22, 0.37, + -0.36, -0.28, -0.19, -0.1, -0.02, 0.06, 0.13, 0.21, 0.36, + -0.37, -0.29, -0.19, -0.1, -0.02, 0.06, 0.13, 0.21, 0.36, + -0.38, -0.27, -0.18, -0.09, -0.01, 0.07, 0.14, 0.22, 0.36, + -0.36, -0.27, -0.18, -0.09, -0.01, 0.07, 0.15, 0.23, 0.38, + -0.35, -0.27, -0.18, -0.09, -0.01, 0.08, 0.15, 0.23, 0.39, + -0.34, -0.26, -0.18, -0.1, -0.01, 0.07, 0.15, 0.22, 0.39, + -0.33, -0.24, -0.17, -0.09, -0.01, 0.08, 0.15, 0.22, 0.37, + -0.3, -0.21, -0.14, -0.07, 0.0, 0.09, 0.16, 0.22, 0.36 + ], + "lut_ry": + [ + -0.22, -0.23, -0.26, -0.27, -0.27, -0.26, -0.24, -0.23, -0.22, + -0.18, -0.19, -0.21, -0.21, -0.22, -0.21, -0.19, -0.18, -0.16, + -0.15, -0.14, -0.16, -0.17, -0.18, -0.17, -0.16, -0.14, -0.11, + -0.09, -0.07, -0.08, -0.09, -0.1, -0.09, -0.08, -0.06, -0.06, + -0.02, 0.0, -0.01, -0.02, -0.03, -0.03, -0.01, 0.01, 0.01, + 0.05, 0.06, 0.04, 0.03, 0.03, 0.03, 0.04, 0.06, 0.07, + 0.09, 0.1, 0.09, 0.08, 0.07, 0.07, 0.09, 0.1, 0.12, + 0.14, 0.16, 0.15, 0.15, 0.15, 0.14, 0.15, 0.16, 0.16, + 0.24, 0.26, 0.25, 0.25, 0.26, 0.26, 0.26, 0.26, 0.25 + ], + "lut_bx": + [ + -0.03, -0.02, -0.0, 0.0, 0.02, 0.03, 0.04, 0.06, 0.11, + -0.03, -0.01, -0.0, -0.0, 0.01, 0.02, 0.04, 0.06, 0.11, + -0.03, -0.01, 0.0, 0.01, 0.01, 0.02, 0.04, 0.06, 0.1, + -0.03, -0.01, 0.0, 0.01, 0.01, 0.02, 0.04, 0.07, 0.12, + -0.03, -0.01, -0.0, 0.01, 0.01, 0.02, 0.04, 0.07, 0.13, + -0.03, -0.01, 0.0, 0.01, 0.02, 0.02, 0.04, 0.07, 0.14, + -0.03, -0.01, 0.0, 0.01, 0.02, 0.02, 0.04, 0.08, 0.15, + -0.03, -0.01, 0.0, 0.0, 0.01, 0.02, 0.04, 0.08, 0.16, + -0.02, -0.0, 0.0, -0.0, -0.01, 0.0, 0.03, 0.08, 0.16 + ], + "lut_by": + [ + -0.1, -0.07, -0.05, -0.04, -0.04, -0.04, -0.05, -0.08, -0.13, + -0.08, -0.06, -0.05, -0.04, -0.04, -0.04, -0.04, -0.06, -0.11, + -0.07, -0.04, -0.03, -0.03, -0.02, -0.02, -0.03, -0.04, -0.08, + -0.06, -0.04, -0.02, -0.02, -0.02, -0.02, -0.02, -0.04, -0.06, + -0.04, -0.01, 0.0, 0.02, 0.02, 0.01, 0.0, -0.01, -0.06, + -0.02, 0.01, 0.02, 0.03, 0.04, 0.04, 0.03, 0.01, -0.04, + -0.0, 0.02, 0.04, 0.05, 0.05, 0.05, 0.05, 0.03, -0.02, + 0.0, 0.02, 0.04, 0.05, 0.06, 0.05, 0.04, 0.04, 0.01, + -0.0, 0.02, 0.03, 0.04, 0.05, 0.04, 0.02, 0.03, 0.01 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": [ 0.0, 2.5, 0.01, 2.5, 0.06, 1.0, 1.0, 1.0 ], + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": [ 0.0, 2.5, 0.01, 2.5, 0.06, 1.0, 1.0, 1.0 ], + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx477_6mm.json b/src/ipa/rpi/pisp/data/imx477_6mm.json new file mode 100644 index 00000000..27268c23 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx477_6mm.json @@ -0,0 +1,1240 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 12000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 740, + "reference_Y": 15051 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.809 + } + }, + { + "rpi.geq": + { + "offset": 204, + "slope": 0.0061 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2850.0, 0.4307, 0.3957, + 2960.0, 0.4159, 0.4313, + 3580.0, 0.3771, 0.5176, + 4559.0, 0.3031, 0.6573, + 5881.0, 0.2809, 0.6942, + 7600.0, 0.2263, 0.7762 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02634, + "transverse_neg": 0.02255 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.359, 2.354, 2.351, 2.351, 2.343, 2.337, 2.331, 2.325, 2.323, 2.321, 2.317, 2.315, 2.313, 2.313, 2.311, 2.312, 2.312, 2.313, 2.315, 2.315, 2.316, 2.317, 2.319, 2.323, 2.326, 2.329, 2.332, 2.332, 2.335, 2.337, 2.352, 2.363, + 2.352, 2.351, 2.349, 2.346, 2.342, 2.334, 2.328, 2.324, 2.321, 2.317, 2.315, 2.314, 2.312, 2.311, 2.311, 2.311, 2.311, 2.311, 2.312, 2.314, 2.315, 2.316, 2.317, 2.319, 2.324, 2.326, 2.328, 2.329, 2.331, 2.337, 2.348, 2.355, + 2.346, 2.346, 2.345, 2.344, 2.338, 2.329, 2.325, 2.319, 2.316, 2.314, 2.311, 2.309, 2.308, 2.306, 2.304, 2.304, 2.305, 2.307, 2.308, 2.309, 2.311, 2.311, 2.313, 2.316, 2.319, 2.322, 2.325, 2.326, 2.328, 2.335, 2.343, 2.349, + 2.342, 2.342, 2.341, 2.338, 2.332, 2.326, 2.319, 2.316, 2.312, 2.309, 2.308, 2.305, 2.303, 2.302, 2.301, 2.301, 2.302, 2.303, 2.304, 2.305, 2.305, 2.307, 2.311, 2.313, 2.315, 2.319, 2.321, 2.325, 2.328, 2.333, 2.338, 2.348, + 2.337, 2.337, 2.337, 2.336, 2.331, 2.322, 2.317, 2.312, 2.309, 2.307, 2.304, 2.302, 2.299, 2.299, 2.298, 2.298, 2.299, 2.299, 2.301, 2.302, 2.302, 2.304, 2.305, 2.309, 2.314, 2.316, 2.321, 2.324, 2.326, 2.329, 2.335, 2.343, + 2.335, 2.334, 2.333, 2.333, 2.326, 2.318, 2.313, 2.309, 2.306, 2.302, 2.299, 2.297, 2.297, 2.296, 2.295, 2.295, 2.294, 2.295, 2.296, 2.298, 2.298, 2.301, 2.303, 2.305, 2.311, 2.315, 2.319, 2.323, 2.325, 2.329, 2.333, 2.339, + 2.329, 2.331, 2.329, 2.329, 2.325, 2.315, 2.309, 2.306, 2.302, 2.299, 2.297, 2.295, 2.293, 2.292, 2.291, 2.291, 2.291, 2.291, 2.293, 2.294, 2.296, 2.298, 2.301, 2.304, 2.307, 2.313, 2.317, 2.319, 2.323, 2.327, 2.331, 2.339, + 2.329, 2.328, 2.328, 2.328, 2.321, 2.313, 2.307, 2.303, 2.299, 2.295, 2.294, 2.292, 2.289, 2.289, 2.288, 2.288, 2.288, 2.289, 2.289, 2.292, 2.294, 2.295, 2.297, 2.301, 2.306, 2.311, 2.315, 2.318, 2.319, 2.323, 2.329, 2.335, + 2.326, 2.327, 2.325, 2.325, 2.319, 2.311, 2.305, 2.299, 2.296, 2.293, 2.291, 2.289, 2.288, 2.287, 2.285, 2.285, 2.286, 2.288, 2.288, 2.289, 2.291, 2.294, 2.295, 2.298, 2.304, 2.308, 2.313, 2.315, 2.317, 2.319, 2.327, 2.335, + 2.325, 2.325, 2.323, 2.323, 2.317, 2.309, 2.303, 2.298, 2.294, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.284, 2.284, 2.285, 2.287, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.305, 2.309, 2.313, 2.315, 2.317, 2.325, 2.334, + 2.322, 2.324, 2.322, 2.322, 2.316, 2.306, 2.301, 2.296, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.283, 2.283, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.293, 2.296, 2.301, 2.304, 2.308, 2.311, 2.312, 2.315, 2.323, 2.333, + 2.321, 2.323, 2.322, 2.322, 2.314, 2.306, 2.299, 2.294, 2.291, 2.288, 2.286, 2.285, 2.284, 2.282, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.306, 2.308, 2.311, 2.312, 2.322, 2.332, + 2.319, 2.321, 2.321, 2.321, 2.314, 2.305, 2.297, 2.293, 2.289, 2.287, 2.285, 2.284, 2.283, 2.281, 2.281, 2.281, 2.282, 2.283, 2.283, 2.285, 2.287, 2.289, 2.291, 2.292, 2.297, 2.301, 2.305, 2.307, 2.309, 2.312, 2.321, 2.333, + 2.319, 2.321, 2.319, 2.319, 2.314, 2.303, 2.296, 2.293, 2.289, 2.286, 2.285, 2.283, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.296, 2.301, 2.305, 2.307, 2.308, 2.312, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.296, 2.291, 2.289, 2.286, 2.284, 2.282, 2.281, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.287, 2.288, 2.291, 2.295, 2.299, 2.304, 2.306, 2.307, 2.311, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.287, 2.285, 2.282, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.284, 2.285, 2.286, 2.288, 2.291, 2.295, 2.299, 2.303, 2.306, 2.307, 2.312, 2.321, 2.331, + 2.318, 2.319, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.286, 2.285, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.285, 2.286, 2.287, 2.291, 2.294, 2.298, 2.303, 2.306, 2.307, 2.311, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.302, 2.297, 2.292, 2.289, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.287, 2.289, 2.294, 2.297, 2.303, 2.305, 2.308, 2.313, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.303, 2.299, 2.293, 2.291, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.288, 2.291, 2.294, 2.298, 2.304, 2.306, 2.308, 2.312, 2.322, 2.331, + 2.319, 2.321, 2.321, 2.321, 2.315, 2.305, 2.301, 2.295, 2.292, 2.289, 2.286, 2.285, 2.283, 2.282, 2.282, 2.282, 2.284, 2.283, 2.284, 2.284, 2.285, 2.287, 2.288, 2.291, 2.294, 2.299, 2.304, 2.306, 2.309, 2.313, 2.322, 2.334, + 2.321, 2.322, 2.322, 2.322, 2.317, 2.307, 2.301, 2.296, 2.292, 2.291, 2.288, 2.286, 2.285, 2.284, 2.283, 2.284, 2.285, 2.284, 2.285, 2.285, 2.287, 2.288, 2.289, 2.293, 2.297, 2.301, 2.305, 2.308, 2.311, 2.314, 2.323, 2.335, + 2.322, 2.324, 2.324, 2.324, 2.319, 2.309, 2.303, 2.297, 2.295, 2.292, 2.291, 2.288, 2.286, 2.286, 2.285, 2.286, 2.286, 2.286, 2.287, 2.288, 2.289, 2.289, 2.291, 2.294, 2.299, 2.302, 2.307, 2.311, 2.312, 2.316, 2.325, 2.335, + 2.324, 2.326, 2.325, 2.326, 2.321, 2.311, 2.305, 2.301, 2.297, 2.295, 2.293, 2.291, 2.289, 2.289, 2.288, 2.288, 2.287, 2.288, 2.289, 2.291, 2.292, 2.292, 2.295, 2.299, 2.301, 2.304, 2.309, 2.312, 2.315, 2.319, 2.327, 2.337, + 2.329, 2.329, 2.328, 2.328, 2.323, 2.315, 2.308, 2.304, 2.301, 2.298, 2.296, 2.294, 2.291, 2.291, 2.289, 2.291, 2.291, 2.291, 2.292, 2.293, 2.294, 2.295, 2.297, 2.299, 2.303, 2.308, 2.312, 2.315, 2.318, 2.321, 2.329, 2.339, + 2.329, 2.331, 2.332, 2.332, 2.326, 2.318, 2.311, 2.306, 2.304, 2.301, 2.299, 2.297, 2.295, 2.293, 2.292, 2.292, 2.292, 2.293, 2.294, 2.294, 2.296, 2.297, 2.299, 2.302, 2.306, 2.311, 2.315, 2.318, 2.319, 2.324, 2.332, 2.342, + 2.331, 2.333, 2.334, 2.334, 2.328, 2.321, 2.313, 2.308, 2.305, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.295, 2.294, 2.296, 2.296, 2.297, 2.298, 2.299, 2.302, 2.305, 2.308, 2.314, 2.317, 2.321, 2.323, 2.327, 2.334, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.309, 2.306, 2.304, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.309, 2.315, 2.319, 2.321, 2.324, 2.328, 2.337, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.311, 2.306, 2.304, 2.303, 2.302, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.311, 2.314, 2.319, 2.323, 2.325, 2.329, 2.339, 2.348, + 2.329, 2.329, 2.329, 2.331, 2.326, 2.319, 2.312, 2.309, 2.304, 2.303, 2.302, 2.301, 2.298, 2.295, 2.294, 2.294, 2.295, 2.295, 2.296, 2.297, 2.299, 2.301, 2.302, 2.304, 2.308, 2.313, 2.319, 2.322, 2.325, 2.329, 2.339, 2.351, + 2.329, 2.329, 2.329, 2.329, 2.326, 2.317, 2.311, 2.308, 2.303, 2.302, 2.301, 2.298, 2.296, 2.295, 2.294, 2.294, 2.294, 2.294, 2.296, 2.297, 2.298, 2.299, 2.301, 2.304, 2.307, 2.312, 2.318, 2.322, 2.326, 2.331, 2.341, 2.355, + 2.339, 2.332, 2.331, 2.331, 2.327, 2.323, 2.316, 2.309, 2.306, 2.302, 2.301, 2.299, 2.297, 2.296, 2.295, 2.294, 2.294, 2.296, 2.297, 2.297, 2.299, 2.301, 2.303, 2.306, 2.308, 2.317, 2.322, 2.325, 2.329, 2.341, 2.353, 2.361, + 2.347, 2.347, 2.345, 2.343, 2.338, 2.332, 2.326, 2.322, 2.321, 2.318, 2.316, 2.315, 2.313, 2.312, 2.311, 2.311, 2.311, 2.311, 2.312, 2.315, 2.317, 2.318, 2.319, 2.323, 2.324, 2.329, 2.334, 2.337, 2.344, 2.347, 2.361, 2.364 + ] + }, + { + "ct": 5000, + "table": + [ + 3.869, 3.852, 3.844, 3.842, 3.836, 3.821, 3.807, 3.796, 3.789, 3.784, 3.778, 3.775, 3.769, 3.768, 3.765, 3.765, 3.767, 3.769, 3.772, 3.774, 3.773, 3.775, 3.779, 3.787, 3.793, 3.801, 3.806, 3.804, 3.813, 3.819, 3.855, 3.879, + 3.854, 3.844, 3.837, 3.836, 3.824, 3.811, 3.797, 3.789, 3.784, 3.777, 3.774, 3.769, 3.764, 3.758, 3.757, 3.758, 3.758, 3.761, 3.763, 3.764, 3.765, 3.766, 3.772, 3.778, 3.787, 3.792, 3.794, 3.798, 3.802, 3.815, 3.839, 3.873, + 3.838, 3.831, 3.826, 3.823, 3.813, 3.799, 3.787, 3.781, 3.773, 3.768, 3.763, 3.759, 3.753, 3.749, 3.745, 3.745, 3.745, 3.752, 3.754, 3.757, 3.757, 3.759, 3.763, 3.769, 3.773, 3.781, 3.786, 3.792, 3.798, 3.811, 3.831, 3.861, + 3.833, 3.822, 3.817, 3.816, 3.804, 3.788, 3.779, 3.772, 3.766, 3.759, 3.755, 3.749, 3.744, 3.741, 3.738, 3.739, 3.739, 3.741, 3.743, 3.747, 3.749, 3.751, 3.756, 3.764, 3.769, 3.776, 3.783, 3.789, 3.798, 3.809, 3.821, 3.855, + 3.824, 3.818, 3.808, 3.808, 3.797, 3.781, 3.772, 3.764, 3.757, 3.752, 3.747, 3.743, 3.737, 3.735, 3.733, 3.733, 3.733, 3.735, 3.737, 3.738, 3.741, 3.746, 3.749, 3.755, 3.766, 3.771, 3.781, 3.789, 3.794, 3.806, 3.818, 3.849, + 3.815, 3.808, 3.799, 3.801, 3.787, 3.775, 3.767, 3.757, 3.751, 3.745, 3.738, 3.734, 3.732, 3.727, 3.725, 3.723, 3.722, 3.722, 3.726, 3.729, 3.734, 3.738, 3.744, 3.749, 3.759, 3.769, 3.781, 3.788, 3.792, 3.799, 3.811, 3.841, + 3.804, 3.799, 3.793, 3.793, 3.783, 3.771, 3.759, 3.751, 3.744, 3.735, 3.732, 3.727, 3.723, 3.721, 3.719, 3.716, 3.716, 3.716, 3.718, 3.722, 3.727, 3.731, 3.737, 3.746, 3.756, 3.767, 3.776, 3.782, 3.788, 3.795, 3.808, 3.831, + 3.802, 3.797, 3.787, 3.787, 3.779, 3.762, 3.753, 3.744, 3.734, 3.727, 3.725, 3.721, 3.716, 3.714, 3.709, 3.709, 3.711, 3.711, 3.712, 3.717, 3.722, 3.725, 3.731, 3.739, 3.752, 3.762, 3.772, 3.778, 3.779, 3.789, 3.798, 3.826, + 3.791, 3.789, 3.784, 3.784, 3.775, 3.759, 3.746, 3.735, 3.729, 3.724, 3.718, 3.714, 3.712, 3.707, 3.704, 3.704, 3.706, 3.708, 3.709, 3.711, 3.716, 3.722, 3.726, 3.735, 3.746, 3.754, 3.767, 3.774, 3.777, 3.781, 3.794, 3.824, + 3.789, 3.784, 3.779, 3.781, 3.771, 3.753, 3.741, 3.732, 3.725, 3.719, 3.715, 3.711, 3.707, 3.704, 3.701, 3.701, 3.702, 3.704, 3.708, 3.709, 3.713, 3.718, 3.724, 3.731, 3.742, 3.749, 3.761, 3.768, 3.772, 3.778, 3.791, 3.822, + 3.789, 3.781, 3.777, 3.777, 3.764, 3.749, 3.739, 3.729, 3.722, 3.718, 3.711, 3.708, 3.705, 3.701, 3.699, 3.699, 3.699, 3.701, 3.705, 3.707, 3.711, 3.715, 3.721, 3.727, 3.738, 3.746, 3.757, 3.763, 3.765, 3.773, 3.788, 3.821, + 3.785, 3.779, 3.774, 3.774, 3.764, 3.747, 3.736, 3.726, 3.719, 3.711, 3.709, 3.706, 3.701, 3.698, 3.696, 3.695, 3.695, 3.698, 3.702, 3.704, 3.707, 3.712, 3.718, 3.725, 3.734, 3.741, 3.753, 3.756, 3.759, 3.764, 3.784, 3.818, + 3.779, 3.776, 3.773, 3.773, 3.759, 3.744, 3.733, 3.724, 3.714, 3.709, 3.706, 3.704, 3.699, 3.696, 3.694, 3.694, 3.694, 3.697, 3.701, 3.703, 3.706, 3.709, 3.714, 3.721, 3.731, 3.737, 3.749, 3.753, 3.758, 3.762, 3.783, 3.819, + 3.779, 3.776, 3.769, 3.769, 3.757, 3.741, 3.729, 3.721, 3.712, 3.708, 3.705, 3.701, 3.697, 3.695, 3.694, 3.694, 3.695, 3.696, 3.698, 3.702, 3.705, 3.709, 3.712, 3.717, 3.728, 3.736, 3.749, 3.752, 3.756, 3.761, 3.781, 3.815, + 3.779, 3.773, 3.768, 3.768, 3.756, 3.738, 3.731, 3.719, 3.711, 3.707, 3.703, 3.698, 3.695, 3.694, 3.694, 3.695, 3.695, 3.695, 3.696, 3.702, 3.705, 3.708, 3.712, 3.717, 3.728, 3.736, 3.747, 3.751, 3.754, 3.761, 3.781, 3.815, + 3.782, 3.773, 3.767, 3.767, 3.755, 3.738, 3.728, 3.721, 3.711, 3.707, 3.701, 3.698, 3.695, 3.693, 3.694, 3.696, 3.695, 3.695, 3.695, 3.701, 3.703, 3.706, 3.711, 3.715, 3.726, 3.735, 3.745, 3.751, 3.754, 3.763, 3.779, 3.815, + 3.781, 3.771, 3.767, 3.767, 3.754, 3.739, 3.726, 3.721, 3.712, 3.706, 3.701, 3.698, 3.695, 3.693, 3.693, 3.695, 3.695, 3.695, 3.696, 3.698, 3.703, 3.705, 3.709, 3.715, 3.725, 3.734, 3.745, 3.751, 3.755, 3.762, 3.783, 3.818, + 3.781, 3.774, 3.767, 3.767, 3.755, 3.741, 3.729, 3.722, 3.712, 3.708, 3.701, 3.699, 3.695, 3.693, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.725, 3.732, 3.746, 3.751, 3.756, 3.763, 3.783, 3.821, + 3.781, 3.774, 3.769, 3.769, 3.756, 3.741, 3.731, 3.724, 3.713, 3.711, 3.707, 3.699, 3.697, 3.694, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.724, 3.734, 3.747, 3.751, 3.756, 3.765, 3.784, 3.821, + 3.784, 3.776, 3.773, 3.773, 3.759, 3.742, 3.733, 3.726, 3.719, 3.711, 3.709, 3.703, 3.698, 3.695, 3.694, 3.695, 3.697, 3.696, 3.698, 3.699, 3.703, 3.706, 3.711, 3.714, 3.727, 3.735, 3.746, 3.751, 3.757, 3.766, 3.787, 3.822, + 3.786, 3.783, 3.774, 3.774, 3.766, 3.747, 3.737, 3.727, 3.722, 3.716, 3.711, 3.706, 3.702, 3.698, 3.697, 3.698, 3.699, 3.699, 3.701, 3.703, 3.706, 3.711, 3.713, 3.719, 3.731, 3.739, 3.748, 3.753, 3.761, 3.769, 3.789, 3.826, + 3.786, 3.784, 3.779, 3.779, 3.769, 3.751, 3.742, 3.732, 3.725, 3.719, 3.715, 3.711, 3.706, 3.704, 3.701, 3.701, 3.702, 3.702, 3.705, 3.707, 3.712, 3.714, 3.717, 3.724, 3.733, 3.743, 3.749, 3.758, 3.764, 3.769, 3.791, 3.826, + 3.793, 3.787, 3.782, 3.782, 3.774, 3.756, 3.747, 3.737, 3.729, 3.725, 3.719, 3.715, 3.712, 3.708, 3.707, 3.706, 3.707, 3.708, 3.709, 3.713, 3.714, 3.717, 3.723, 3.729, 3.736, 3.747, 3.757, 3.764, 3.768, 3.774, 3.794, 3.829, + 3.794, 3.791, 3.786, 3.786, 3.779, 3.762, 3.751, 3.742, 3.735, 3.729, 3.725, 3.719, 3.716, 3.711, 3.709, 3.709, 3.709, 3.711, 3.716, 3.717, 3.721, 3.723, 3.726, 3.732, 3.741, 3.752, 3.761, 3.767, 3.773, 3.779, 3.801, 3.829, + 3.802, 3.798, 3.793, 3.793, 3.779, 3.766, 3.754, 3.746, 3.741, 3.736, 3.731, 3.726, 3.719, 3.717, 3.716, 3.715, 3.716, 3.717, 3.719, 3.721, 3.724, 3.726, 3.731, 3.737, 3.744, 3.756, 3.766, 3.772, 3.776, 3.784, 3.807, 3.839, + 3.805, 3.799, 3.795, 3.795, 3.784, 3.767, 3.757, 3.749, 3.744, 3.739, 3.736, 3.731, 3.726, 3.722, 3.719, 3.719, 3.719, 3.721, 3.723, 3.725, 3.727, 3.732, 3.738, 3.742, 3.751, 3.761, 3.771, 3.775, 3.782, 3.789, 3.811, 3.841, + 3.804, 3.801, 3.799, 3.799, 3.787, 3.772, 3.761, 3.752, 3.746, 3.742, 3.739, 3.735, 3.729, 3.726, 3.723, 3.724, 3.725, 3.726, 3.727, 3.728, 3.732, 3.736, 3.739, 3.745, 3.754, 3.765, 3.775, 3.779, 3.785, 3.795, 3.816, 3.844, + 3.801, 3.799, 3.796, 3.796, 3.787, 3.773, 3.761, 3.753, 3.746, 3.743, 3.739, 3.735, 3.731, 3.726, 3.725, 3.725, 3.725, 3.726, 3.727, 3.729, 3.733, 3.736, 3.741, 3.745, 3.755, 3.766, 3.776, 3.783, 3.786, 3.797, 3.819, 3.851, + 3.799, 3.795, 3.788, 3.788, 3.783, 3.772, 3.759, 3.749, 3.744, 3.738, 3.735, 3.733, 3.726, 3.724, 3.722, 3.722, 3.723, 3.724, 3.725, 3.727, 3.729, 3.733, 3.736, 3.742, 3.754, 3.762, 3.772, 3.779, 3.784, 3.796, 3.821, 3.859, + 3.799, 3.789, 3.787, 3.788, 3.779, 3.766, 3.755, 3.749, 3.742, 3.736, 3.733, 3.727, 3.723, 3.722, 3.721, 3.719, 3.719, 3.721, 3.725, 3.726, 3.728, 3.732, 3.734, 3.741, 3.747, 3.758, 3.771, 3.778, 3.785, 3.796, 3.825, 3.862, + 3.824, 3.799, 3.789, 3.789, 3.788, 3.777, 3.761, 3.751, 3.743, 3.739, 3.736, 3.728, 3.726, 3.725, 3.721, 3.719, 3.721, 3.723, 3.727, 3.728, 3.729, 3.733, 3.737, 3.744, 3.755, 3.769, 3.776, 3.784, 3.793, 3.819, 3.863, 3.877, + 3.833, 3.833, 3.833, 3.842, 3.825, 3.815, 3.807, 3.799, 3.792, 3.788, 3.785, 3.782, 3.778, 3.777, 3.773, 3.772, 3.772, 3.774, 3.778, 3.779, 3.779, 3.785, 3.792, 3.798, 3.803, 3.811, 3.822, 3.834, 3.843, 3.846, 3.877, 3.886 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.616, 2.616, 2.618, 2.621, 2.619, 2.618, 2.615, 2.615, 2.613, 2.611, 2.609, 2.609, 2.609, 2.611, 2.611, 2.611, 2.611, 2.609, 2.608, 2.608, 2.611, 2.613, 2.613, 2.614, 2.614, 2.615, 2.615, 2.622, 2.624, 2.621, 2.624, 2.641, + 2.616, 2.618, 2.621, 2.623, 2.623, 2.619, 2.618, 2.616, 2.616, 2.613, 2.611, 2.611, 2.611, 2.611, 2.612, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.612, 2.613, 2.612, 2.613, 2.615, 2.617, 2.621, 2.621, 2.619, 2.621, 2.641, + 2.621, 2.624, 2.627, 2.627, 2.625, 2.623, 2.621, 2.619, 2.618, 2.618, 2.618, 2.617, 2.616, 2.616, 2.615, 2.613, 2.612, 2.613, 2.613, 2.614, 2.614, 2.613, 2.614, 2.613, 2.614, 2.617, 2.619, 2.621, 2.621, 2.619, 2.623, 2.643, + 2.626, 2.627, 2.628, 2.629, 2.628, 2.625, 2.622, 2.621, 2.621, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.616, 2.616, 2.616, 2.618, 2.618, 2.617, 2.617, 2.618, 2.619, 2.621, 2.623, 2.624, 2.626, 2.625, 2.624, 2.625, 2.654, + 2.627, 2.628, 2.628, 2.628, 2.626, 2.623, 2.622, 2.622, 2.622, 2.622, 2.621, 2.621, 2.619, 2.617, 2.617, 2.616, 2.617, 2.617, 2.618, 2.619, 2.618, 2.618, 2.618, 2.621, 2.622, 2.624, 2.626, 2.627, 2.627, 2.626, 2.628, 2.655, + 2.625, 2.626, 2.627, 2.626, 2.625, 2.623, 2.622, 2.621, 2.622, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.616, 2.616, 2.616, 2.616, 2.616, 2.617, 2.618, 2.619, 2.621, 2.622, 2.624, 2.626, 2.628, 2.628, 2.629, 2.629, 2.655, + 2.626, 2.625, 2.626, 2.625, 2.625, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.616, 2.614, 2.613, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.618, 2.619, 2.621, 2.623, 2.624, 2.627, 2.629, 2.631, 2.629, 2.631, 2.651, + 2.625, 2.625, 2.625, 2.624, 2.623, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.614, 2.613, 2.612, 2.611, 2.611, 2.612, 2.612, 2.613, 2.616, 2.618, 2.619, 2.622, 2.624, 2.626, 2.628, 2.631, 2.631, 2.631, 2.631, 2.651, + 2.625, 2.625, 2.624, 2.623, 2.622, 2.622, 2.622, 2.622, 2.622, 2.621, 2.617, 2.615, 2.613, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.613, 2.615, 2.618, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.624, 2.624, 2.622, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.618, 2.616, 2.614, 2.612, 2.611, 2.609, 2.609, 2.608, 2.609, 2.611, 2.611, 2.615, 2.617, 2.619, 2.621, 2.625, 2.628, 2.631, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.622, 2.623, 2.622, 2.622, 2.621, 2.619, 2.619, 2.619, 2.618, 2.616, 2.614, 2.613, 2.611, 2.609, 2.608, 2.606, 2.607, 2.607, 2.609, 2.611, 2.615, 2.617, 2.619, 2.622, 2.626, 2.629, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.622, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.614, 2.613, 2.611, 2.611, 2.607, 2.606, 2.605, 2.604, 2.605, 2.607, 2.609, 2.613, 2.616, 2.619, 2.622, 2.627, 2.631, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.613, 2.609, 2.607, 2.604, 2.602, 2.601, 2.602, 2.603, 2.605, 2.609, 2.612, 2.616, 2.619, 2.624, 2.628, 2.631, 2.632, 2.633, 2.629, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.615, 2.614, 2.612, 2.608, 2.603, 2.601, 2.598, 2.597, 2.599, 2.602, 2.605, 2.608, 2.611, 2.615, 2.622, 2.625, 2.629, 2.631, 2.631, 2.633, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.616, 2.614, 2.611, 2.606, 2.601, 2.598, 2.595, 2.595, 2.597, 2.601, 2.604, 2.608, 2.612, 2.615, 2.623, 2.627, 2.629, 2.631, 2.631, 2.632, 2.631, 2.628, 2.628, 2.651, + 2.622, 2.623, 2.624, 2.624, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.609, 2.606, 2.601, 2.596, 2.594, 2.594, 2.596, 2.599, 2.603, 2.609, 2.613, 2.617, 2.623, 2.627, 2.629, 2.631, 2.632, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.623, 2.625, 2.625, 2.624, 2.621, 2.621, 2.619, 2.617, 2.616, 2.613, 2.608, 2.605, 2.601, 2.595, 2.593, 2.593, 2.595, 2.598, 2.604, 2.609, 2.615, 2.619, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.632, 2.629, 2.631, 2.651, + 2.624, 2.626, 2.626, 2.623, 2.621, 2.619, 2.618, 2.617, 2.615, 2.612, 2.608, 2.605, 2.601, 2.597, 2.595, 2.595, 2.596, 2.598, 2.605, 2.609, 2.616, 2.621, 2.626, 2.627, 2.629, 2.631, 2.633, 2.633, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.618, 2.617, 2.614, 2.612, 2.609, 2.606, 2.602, 2.599, 2.598, 2.597, 2.598, 2.602, 2.607, 2.612, 2.619, 2.621, 2.626, 2.628, 2.629, 2.632, 2.633, 2.634, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.621, 2.618, 2.617, 2.614, 2.612, 2.611, 2.608, 2.604, 2.602, 2.599, 2.599, 2.603, 2.606, 2.611, 2.616, 2.621, 2.624, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.634, 2.633, 2.633, 2.656, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.611, 2.611, 2.607, 2.604, 2.604, 2.604, 2.606, 2.609, 2.613, 2.619, 2.622, 2.625, 2.628, 2.631, 2.632, 2.633, 2.633, 2.636, 2.636, 2.634, 2.634, 2.658, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.619, 2.618, 2.616, 2.614, 2.613, 2.612, 2.611, 2.609, 2.608, 2.607, 2.608, 2.609, 2.613, 2.617, 2.621, 2.623, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.635, 2.636, 2.636, 2.636, 2.661, + 2.623, 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.616, 2.615, 2.614, 2.613, 2.612, 2.612, 2.611, 2.611, 2.611, 2.614, 2.615, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.633, 2.635, 2.635, 2.637, 2.637, 2.636, 2.637, 2.661, + 2.623, 2.624, 2.625, 2.626, 2.624, 2.621, 2.619, 2.617, 2.616, 2.615, 2.615, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.619, 2.621, 2.623, 2.626, 2.628, 2.631, 2.632, 2.634, 2.635, 2.636, 2.637, 2.638, 2.637, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.627, 2.626, 2.623, 2.619, 2.619, 2.618, 2.618, 2.618, 2.617, 2.617, 2.616, 2.616, 2.616, 2.619, 2.622, 2.623, 2.625, 2.628, 2.628, 2.631, 2.632, 2.634, 2.636, 2.638, 2.639, 2.639, 2.638, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.628, 2.626, 2.623, 2.621, 2.619, 2.619, 2.619, 2.619, 2.619, 2.619, 2.618, 2.618, 2.619, 2.623, 2.624, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.635, 2.638, 2.639, 2.639, 2.639, 2.636, 2.636, 2.662, + 2.625, 2.627, 2.628, 2.628, 2.626, 2.624, 2.623, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.624, 2.624, 2.625, 2.627, 2.628, 2.631, 2.631, 2.632, 2.634, 2.636, 2.639, 2.639, 2.641, 2.639, 2.635, 2.635, 2.663, + 2.625, 2.626, 2.628, 2.628, 2.627, 2.625, 2.624, 2.623, 2.623, 2.622, 2.623, 2.624, 2.624, 2.625, 2.625, 2.625, 2.625, 2.626, 2.627, 2.629, 2.631, 2.632, 2.633, 2.635, 2.638, 2.641, 2.642, 2.643, 2.642, 2.636, 2.636, 2.665, + 2.624, 2.626, 2.628, 2.628, 2.628, 2.626, 2.624, 2.624, 2.623, 2.623, 2.623, 2.625, 2.627, 2.627, 2.626, 2.626, 2.626, 2.627, 2.628, 2.629, 2.632, 2.633, 2.635, 2.637, 2.639, 2.642, 2.644, 2.644, 2.642, 2.638, 2.638, 2.665, + 2.623, 2.625, 2.626, 2.627, 2.626, 2.626, 2.624, 2.623, 2.623, 2.623, 2.623, 2.623, 2.626, 2.627, 2.626, 2.626, 2.626, 2.626, 2.628, 2.628, 2.629, 2.631, 2.634, 2.636, 2.639, 2.642, 2.644, 2.643, 2.641, 2.637, 2.638, 2.659, + 2.623, 2.627, 2.627, 2.627, 2.627, 2.628, 2.627, 2.624, 2.624, 2.623, 2.624, 2.624, 2.628, 2.628, 2.627, 2.628, 2.628, 2.628, 2.629, 2.629, 2.631, 2.635, 2.637, 2.639, 2.641, 2.643, 2.646, 2.645, 2.643, 2.641, 2.654, 2.659, + 2.642, 2.641, 2.643, 2.645, 2.645, 2.644, 2.644, 2.643, 2.643, 2.642, 2.642, 2.642, 2.643, 2.644, 2.644, 2.644, 2.646, 2.646, 2.647, 2.649, 2.651, 2.652, 2.654, 2.656, 2.658, 2.661, 2.661, 2.661, 2.659, 2.654, 2.659, 2.659 + ] + }, + { + "ct": 5000, + "table": + [ + 1.391, 1.394, 1.395, 1.396, 1.398, 1.398, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.398, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.399, 1.397, 1.399, 1.402, + 1.393, 1.395, 1.396, 1.398, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.401, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.399, 1.401, 1.401, 1.399, 1.398, 1.399, 1.402, + 1.398, 1.401, 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.401, 1.401, 1.399, 1.399, 1.401, 1.401, 1.401, 1.401, 1.399, 1.401, 1.406, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.402, 1.403, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.405, 1.404, 1.404, 1.404, 1.404, 1.405, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.406, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.408, 1.408, 1.407, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.403, 1.402, 1.402, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.408, 1.407, 1.405, 1.405, 1.414, + 1.402, 1.402, 1.402, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.403, 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.407, 1.409, 1.409, 1.409, 1.409, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.403, 1.404, 1.405, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.408, 1.405, 1.405, 1.413, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.402, 1.401, 1.399, 1.398, 1.398, 1.399, 1.399, 1.401, 1.403, 1.404, 1.405, 1.407, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.403, 1.404, 1.405, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.399, 1.401, 1.403, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.398, 1.401, 1.403, 1.406, 1.407, 1.409, 1.409, 1.411, 1.409, 1.409, 1.409, 1.408, 1.407, 1.407, 1.413, + 1.403, 1.404, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.404, 1.404, 1.403, 1.402, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.402, 1.404, 1.406, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.414, + 1.403, 1.403, 1.404, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.402, 1.401, 1.399, 1.398, 1.398, 1.398, 1.401, 1.403, 1.404, 1.408, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.402, 1.404, 1.406, 1.407, 1.408, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.409, 1.408, 1.408, 1.415, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.406, 1.408, 1.408, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.405, 1.406, 1.407, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.407, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.416, + 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.406, 1.407, 1.407, 1.407, 1.408, 1.409, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.417, + 1.402, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.415, + 1.402, 1.402, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.405, 1.406, 1.406, 1.406, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.412, 1.412, 1.413, 1.413, 1.411, 1.408, 1.411, 1.413, + 1.406, 1.406, 1.408, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.414, 1.414, 1.414, 1.414, 1.415, 1.415, 1.415, 1.415, 1.416, 1.416, 1.416, 1.417, 1.418, 1.418, 1.417, 1.417, 1.414, 1.411, 1.413, 1.413 + ] + } + ], + "luminance_lut": + [ + 1.554, 1.522, 1.466, 1.422, 1.385, 1.351, 1.322, 1.294, 1.269, 1.246, 1.228, 1.214, 1.207, 1.202, 1.199, 1.199, 1.199, 1.199, 1.202, 1.207, 1.218, 1.235, 1.255, 1.279, 1.305, 1.333, 1.365, 1.402, 1.447, 1.508, 1.602, 1.638, + 1.522, 1.478, 1.431, 1.391, 1.355, 1.323, 1.298, 1.271, 1.247, 1.228, 1.212, 1.199, 1.187, 1.179, 1.173, 1.172, 1.172, 1.174, 1.179, 1.189, 1.201, 1.216, 1.235, 1.256, 1.282, 1.308, 1.335, 1.368, 1.411, 1.461, 1.535, 1.602, + 1.479, 1.449, 1.407, 1.367, 1.332, 1.301, 1.271, 1.247, 1.226, 1.208, 1.191, 1.178, 1.166, 1.158, 1.153, 1.151, 1.151, 1.153, 1.159, 1.168, 1.179, 1.194, 1.212, 1.234, 1.256, 1.282, 1.311, 1.343, 1.382, 1.427, 1.489, 1.535, + 1.454, 1.423, 1.383, 1.345, 1.309, 1.278, 1.249, 1.226, 1.206, 1.187, 1.171, 1.158, 1.146, 1.138, 1.132, 1.129, 1.129, 1.133, 1.139, 1.147, 1.159, 1.173, 1.191, 1.212, 1.234, 1.261, 1.288, 1.321, 1.357, 1.401, 1.455, 1.489, + 1.433, 1.401, 1.362, 1.325, 1.289, 1.258, 1.231, 1.206, 1.187, 1.169, 1.153, 1.138, 1.129, 1.121, 1.115, 1.112, 1.112, 1.114, 1.121, 1.129, 1.141, 1.155, 1.172, 1.191, 1.214, 1.241, 1.269, 1.301, 1.337, 1.377, 1.428, 1.457, + 1.415, 1.382, 1.343, 1.306, 1.273, 1.241, 1.213, 1.189, 1.169, 1.153, 1.137, 1.123, 1.112, 1.105, 1.097, 1.095, 1.095, 1.098, 1.103, 1.112, 1.124, 1.139, 1.155, 1.173, 1.197, 1.222, 1.252, 1.282, 1.317, 1.356, 1.405, 1.434, + 1.398, 1.363, 1.325, 1.289, 1.256, 1.224, 1.198, 1.175, 1.155, 1.137, 1.123, 1.108, 1.097, 1.089, 1.083, 1.079, 1.079, 1.083, 1.088, 1.097, 1.109, 1.124, 1.139, 1.158, 1.181, 1.206, 1.234, 1.266, 1.299, 1.339, 1.384, 1.415, + 1.382, 1.347, 1.309, 1.274, 1.242, 1.211, 1.185, 1.162, 1.142, 1.124, 1.108, 1.095, 1.083, 1.075, 1.069, 1.066, 1.066, 1.068, 1.074, 1.083, 1.096, 1.109, 1.125, 1.145, 1.166, 1.191, 1.219, 1.251, 1.285, 1.324, 1.367, 1.399, + 1.369, 1.334, 1.296, 1.261, 1.228, 1.199, 1.173, 1.151, 1.131, 1.112, 1.095, 1.083, 1.071, 1.062, 1.056, 1.053, 1.053, 1.055, 1.061, 1.069, 1.083, 1.096, 1.112, 1.132, 1.153, 1.178, 1.206, 1.237, 1.271, 1.309, 1.353, 1.385, + 1.359, 1.321, 1.284, 1.251, 1.217, 1.189, 1.164, 1.141, 1.121, 1.102, 1.086, 1.071, 1.061, 1.049, 1.045, 1.042, 1.042, 1.043, 1.051, 1.061, 1.069, 1.085, 1.101, 1.121, 1.143, 1.167, 1.195, 1.225, 1.259, 1.298, 1.341, 1.375, + 1.351, 1.312, 1.275, 1.241, 1.209, 1.181, 1.155, 1.133, 1.112, 1.092, 1.076, 1.061, 1.049, 1.041, 1.034, 1.032, 1.032, 1.035, 1.041, 1.051, 1.061, 1.075, 1.092, 1.112, 1.133, 1.158, 1.185, 1.216, 1.249, 1.288, 1.331, 1.364, + 1.344, 1.303, 1.267, 1.233, 1.201, 1.173, 1.147, 1.124, 1.104, 1.085, 1.067, 1.053, 1.041, 1.033, 1.024, 1.022, 1.022, 1.025, 1.034, 1.041, 1.053, 1.066, 1.083, 1.103, 1.126, 1.149, 1.177, 1.207, 1.241, 1.279, 1.321, 1.357, + 1.339, 1.297, 1.261, 1.226, 1.194, 1.166, 1.142, 1.119, 1.098, 1.078, 1.061, 1.046, 1.034, 1.024, 1.017, 1.014, 1.014, 1.017, 1.025, 1.034, 1.046, 1.059, 1.077, 1.096, 1.118, 1.143, 1.169, 1.201, 1.235, 1.273, 1.314, 1.352, + 1.337, 1.293, 1.256, 1.223, 1.191, 1.163, 1.136, 1.114, 1.093, 1.074, 1.056, 1.041, 1.027, 1.017, 1.012, 1.006, 1.006, 1.013, 1.017, 1.028, 1.041, 1.055, 1.072, 1.092, 1.114, 1.138, 1.165, 1.195, 1.229, 1.268, 1.309, 1.348, + 1.337, 1.291, 1.253, 1.219, 1.187, 1.159, 1.133, 1.109, 1.089, 1.071, 1.053, 1.037, 1.023, 1.012, 1.006, 1.002, 1.003, 1.006, 1.013, 1.023, 1.038, 1.052, 1.069, 1.089, 1.111, 1.135, 1.161, 1.192, 1.226, 1.264, 1.306, 1.348, + 1.337, 1.291, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.035, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.341, 1.292, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.034, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.348, 1.298, 1.255, 1.219, 1.188, 1.159, 1.134, 1.111, 1.088, 1.069, 1.051, 1.035, 1.021, 1.009, 1.003, 1.001, 1.002, 1.004, 1.011, 1.022, 1.036, 1.053, 1.071, 1.089, 1.111, 1.135, 1.162, 1.191, 1.226, 1.264, 1.306, 1.347, + 1.354, 1.306, 1.258, 1.222, 1.191, 1.162, 1.135, 1.113, 1.092, 1.073, 1.054, 1.038, 1.024, 1.014, 1.008, 1.003, 1.004, 1.008, 1.014, 1.026, 1.039, 1.056, 1.073, 1.093, 1.115, 1.139, 1.165, 1.195, 1.229, 1.267, 1.309, 1.349, + 1.358, 1.312, 1.263, 1.227, 1.195, 1.167, 1.141, 1.117, 1.097, 1.078, 1.061, 1.043, 1.029, 1.021, 1.014, 1.008, 1.008, 1.014, 1.021, 1.032, 1.045, 1.061, 1.078, 1.097, 1.119, 1.144, 1.169, 1.201, 1.234, 1.272, 1.315, 1.353, + 1.364, 1.319, 1.269, 1.234, 1.201, 1.174, 1.148, 1.124, 1.103, 1.084, 1.067, 1.052, 1.038, 1.029, 1.021, 1.016, 1.016, 1.021, 1.029, 1.038, 1.051, 1.067, 1.084, 1.103, 1.126, 1.151, 1.176, 1.207, 1.241, 1.279, 1.321, 1.358, + 1.371, 1.326, 1.277, 1.242, 1.209, 1.181, 1.155, 1.132, 1.111, 1.092, 1.075, 1.061, 1.049, 1.038, 1.029, 1.027, 1.027, 1.029, 1.038, 1.047, 1.061, 1.075, 1.092, 1.111, 1.133, 1.157, 1.185, 1.213, 1.247, 1.286, 1.329, 1.365, + 1.379, 1.334, 1.287, 1.251, 1.219, 1.191, 1.164, 1.141, 1.119, 1.101, 1.085, 1.071, 1.061, 1.049, 1.041, 1.038, 1.038, 1.041, 1.047, 1.059, 1.071, 1.084, 1.101, 1.119, 1.141, 1.165, 1.193, 1.223, 1.257, 1.295, 1.338, 1.374, + 1.389, 1.343, 1.298, 1.262, 1.231, 1.201, 1.174, 1.151, 1.131, 1.111, 1.095, 1.083, 1.071, 1.061, 1.054, 1.051, 1.051, 1.054, 1.059, 1.071, 1.081, 1.094, 1.111, 1.129, 1.152, 1.176, 1.203, 1.235, 1.269, 1.307, 1.351, 1.384, + 1.401, 1.351, 1.311, 1.274, 1.242, 1.214, 1.187, 1.164, 1.142, 1.124, 1.108, 1.095, 1.083, 1.074, 1.068, 1.066, 1.066, 1.068, 1.073, 1.081, 1.094, 1.108, 1.123, 1.141, 1.164, 1.188, 1.215, 1.247, 1.281, 1.321, 1.364, 1.396, + 1.412, 1.366, 1.327, 1.289, 1.257, 1.227, 1.201, 1.176, 1.156, 1.137, 1.122, 1.108, 1.096, 1.088, 1.083, 1.081, 1.081, 1.082, 1.087, 1.095, 1.108, 1.122, 1.136, 1.154, 1.177, 1.201, 1.229, 1.261, 1.296, 1.337, 1.382, 1.409, + 1.421, 1.383, 1.343, 1.306, 1.273, 1.243, 1.216, 1.192, 1.169, 1.152, 1.137, 1.122, 1.111, 1.103, 1.098, 1.095, 1.095, 1.097, 1.102, 1.111, 1.123, 1.136, 1.152, 1.169, 1.191, 1.217, 1.246, 1.278, 1.314, 1.354, 1.399, 1.429, + 1.434, 1.402, 1.362, 1.324, 1.291, 1.261, 1.232, 1.208, 1.187, 1.168, 1.152, 1.138, 1.127, 1.119, 1.114, 1.112, 1.112, 1.115, 1.121, 1.128, 1.139, 1.152, 1.169, 1.186, 1.209, 1.234, 1.262, 1.295, 1.332, 1.372, 1.419, 1.451, + 1.453, 1.422, 1.382, 1.344, 1.309, 1.278, 1.249, 1.226, 1.204, 1.187, 1.168, 1.155, 1.144, 1.135, 1.131, 1.131, 1.131, 1.133, 1.138, 1.146, 1.157, 1.171, 1.186, 1.206, 1.227, 1.252, 1.281, 1.314, 1.351, 1.393, 1.442, 1.473, + 1.475, 1.446, 1.404, 1.366, 1.329, 1.298, 1.269, 1.245, 1.224, 1.204, 1.188, 1.174, 1.163, 1.154, 1.149, 1.148, 1.148, 1.152, 1.156, 1.164, 1.176, 1.189, 1.206, 1.226, 1.247, 1.274, 1.303, 1.336, 1.374, 1.417, 1.471, 1.505, + 1.503, 1.472, 1.428, 1.389, 1.353, 1.321, 1.291, 1.266, 1.245, 1.224, 1.207, 1.192, 1.183, 1.174, 1.169, 1.167, 1.168, 1.169, 1.175, 1.183, 1.195, 1.209, 1.226, 1.247, 1.267, 1.294, 1.325, 1.359, 1.397, 1.445, 1.505, 1.548, + 1.534, 1.503, 1.455, 1.413, 1.378, 1.344, 1.315, 1.289, 1.265, 1.243, 1.224, 1.207, 1.196, 1.192, 1.189, 1.189, 1.189, 1.189, 1.192, 1.198, 1.209, 1.226, 1.244, 1.266, 1.291, 1.318, 1.349, 1.383, 1.425, 1.475, 1.548, 1.591 + ], + "sigma": 0.00095, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2850, + "ccm": + [ + 1.97469, -0.71439, -0.26031, + -0.43521, 2.09769, -0.66248, + -0.04826, -0.84642, 1.89468 + ] + }, + { + "ct": 2960, + "ccm": + [ + 2.12952, -0.91185, -0.21768, + -0.38018, 1.90789, -0.52771, + 0.03988, -1.10079, 2.06092 + ] + }, + { + "ct": 3580, + "ccm": + [ + 2.03422, -0.80048, -0.23374, + -0.39089, 1.97221, -0.58132, + -0.08969, -0.61439, 1.70408 + ] + }, + { + "ct": 4559, + "ccm": + [ + 2.15423, -0.98143, -0.17279, + -0.38131, 2.14763, -0.76632, + -0.10069, -0.54383, 1.64452 + ] + }, + { + "ct": 5881, + "ccm": + [ + 2.18464, -0.95493, -0.22971, + -0.36826, 2.00298, -0.63471, + -0.15219, -0.38055, 1.53274 + ] + }, + { + "ct": 7600, + "ccm": + [ + 2.30687, -0.97295, -0.33392, + -0.30872, 2.32779, -1.01908, + -0.17761, -0.55891, 1.73651 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.cac": + { + "strength": 1.0, + "lut_rx": + [ + -0.46, -0.31, -0.17, -0.05, 0.04, 0.14, 0.28, 0.36, 0.58, + -0.43, -0.28, -0.16, -0.05, 0.05, 0.14, 0.24, 0.36, 0.58, + -0.42, -0.27, -0.15, -0.05, 0.05, 0.14, 0.23, 0.34, 0.58, + -0.39, -0.25, -0.14, -0.04, 0.05, 0.15, 0.24, 0.35, 0.56, + -0.35, -0.24, -0.14, -0.04, 0.05, 0.14, 0.25, 0.35, 0.56, + -0.33, -0.22, -0.13, -0.04, 0.04, 0.13, 0.24, 0.33, 0.54, + -0.33, -0.22, -0.13, -0.04, 0.04, 0.13, 0.23, 0.31, 0.5, + -0.32, -0.23, -0.13, -0.04, 0.04, 0.12, 0.21, 0.29, 0.46, + -0.33, -0.24, -0.15, -0.05, 0.03, 0.11, 0.19, 0.26, 0.43 + ], + "lut_ry": + [ + -0.39, -0.34, -0.31, -0.29, -0.27, -0.29, -0.32, -0.31, -0.32, + -0.29, -0.24, -0.22, -0.21, -0.22, -0.21, -0.21, -0.22, -0.23, + -0.22, -0.17, -0.17, -0.17, -0.17, -0.17, -0.16, -0.16, -0.18, + -0.13, -0.09, -0.09, -0.1, -0.1, -0.1, -0.09, -0.08, -0.11, + -0.04, -0.02, -0.03, -0.03, -0.03, -0.03, -0.02, -0.01, -0.02, + 0.03, 0.05, 0.03, 0.03, 0.02, 0.03, 0.04, 0.06, 0.05, + 0.11, 0.11, 0.09, 0.08, 0.09, 0.09, 0.1, 0.11, 0.11, + 0.17, 0.16, 0.14, 0.13, 0.13, 0.13, 0.14, 0.15, 0.15, + 0.24, 0.23, 0.21, 0.19, 0.18, 0.2, 0.2, 0.2, 0.21 + ], + "lut_bx": + [ + -0.33, -0.22, -0.13, -0.05, 0.01, 0.09, 0.19, 0.25, 0.39, + -0.3, -0.19, -0.12, -0.04, 0.01, 0.06, 0.15, 0.23, 0.38, + -0.28, -0.18, -0.1, -0.04, 0.01, 0.06, 0.14, 0.22, 0.38, + -0.25, -0.16, -0.09, -0.04, 0.01, 0.06, 0.14, 0.23, 0.39, + -0.22, -0.15, -0.09, -0.03, 0.01, 0.06, 0.14, 0.23, 0.42, + -0.21, -0.14, -0.09, -0.04, 0.0, 0.06, 0.13, 0.21, 0.39, + -0.21, -0.14, -0.08, -0.04, 0.0, 0.06, 0.13, 0.2, 0.34, + -0.21, -0.14, -0.08, -0.04, 0.0, 0.06, 0.12, 0.19, 0.31, + -0.21, -0.15, -0.08, -0.03, 0.0, 0.06, 0.12, 0.17, 0.29 + ], + "lut_by": + [ + -0.3, -0.24, -0.21, -0.19, -0.19, -0.19, -0.23, -0.23, -0.27, + -0.23, -0.17, -0.14, -0.12, -0.12, -0.13, -0.15, -0.17, -0.21, + -0.17, -0.11, -0.09, -0.08, -0.08, -0.08, -0.1, -0.11, -0.16, + -0.09, -0.05, -0.04, -0.03, -0.03, -0.03, -0.04, -0.06, -0.11, + -0.03, -0.01, 0.0, 0.01, 0.01, 0.01, 0.0, -0.0, -0.05, + 0.02, 0.04, 0.04, 0.04, 0.05, 0.05, 0.05, 0.05, 0.01, + 0.07, 0.08, 0.09, 0.09, 0.1, 0.1, 0.1, 0.09, 0.06, + 0.11, 0.12, 0.11, 0.12, 0.12, 0.13, 0.13, 0.13, 0.1, + 0.16, 0.17, 0.16, 0.16, 0.16, 0.17, 0.17, 0.17, 0.15 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx477_noir.json b/src/ipa/rpi/pisp/data/imx477_noir.json new file mode 100644 index 00000000..defc4f4d --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx477_noir.json @@ -0,0 +1,1148 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 12000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 740, + "reference_Y": 15051 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.809 + } + }, + { + "rpi.geq": + { + "offset": 204, + "slope": 0.0061 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 2.359, 2.354, 2.351, 2.351, 2.343, 2.337, 2.331, 2.325, 2.323, 2.321, 2.317, 2.315, 2.313, 2.313, 2.311, 2.312, 2.312, 2.313, 2.315, 2.315, 2.316, 2.317, 2.319, 2.323, 2.326, 2.329, 2.332, 2.332, 2.335, 2.337, 2.352, 2.363, + 2.352, 2.351, 2.349, 2.346, 2.342, 2.334, 2.328, 2.324, 2.321, 2.317, 2.315, 2.314, 2.312, 2.311, 2.311, 2.311, 2.311, 2.311, 2.312, 2.314, 2.315, 2.316, 2.317, 2.319, 2.324, 2.326, 2.328, 2.329, 2.331, 2.337, 2.348, 2.355, + 2.346, 2.346, 2.345, 2.344, 2.338, 2.329, 2.325, 2.319, 2.316, 2.314, 2.311, 2.309, 2.308, 2.306, 2.304, 2.304, 2.305, 2.307, 2.308, 2.309, 2.311, 2.311, 2.313, 2.316, 2.319, 2.322, 2.325, 2.326, 2.328, 2.335, 2.343, 2.349, + 2.342, 2.342, 2.341, 2.338, 2.332, 2.326, 2.319, 2.316, 2.312, 2.309, 2.308, 2.305, 2.303, 2.302, 2.301, 2.301, 2.302, 2.303, 2.304, 2.305, 2.305, 2.307, 2.311, 2.313, 2.315, 2.319, 2.321, 2.325, 2.328, 2.333, 2.338, 2.348, + 2.337, 2.337, 2.337, 2.336, 2.331, 2.322, 2.317, 2.312, 2.309, 2.307, 2.304, 2.302, 2.299, 2.299, 2.298, 2.298, 2.299, 2.299, 2.301, 2.302, 2.302, 2.304, 2.305, 2.309, 2.314, 2.316, 2.321, 2.324, 2.326, 2.329, 2.335, 2.343, + 2.335, 2.334, 2.333, 2.333, 2.326, 2.318, 2.313, 2.309, 2.306, 2.302, 2.299, 2.297, 2.297, 2.296, 2.295, 2.295, 2.294, 2.295, 2.296, 2.298, 2.298, 2.301, 2.303, 2.305, 2.311, 2.315, 2.319, 2.323, 2.325, 2.329, 2.333, 2.339, + 2.329, 2.331, 2.329, 2.329, 2.325, 2.315, 2.309, 2.306, 2.302, 2.299, 2.297, 2.295, 2.293, 2.292, 2.291, 2.291, 2.291, 2.291, 2.293, 2.294, 2.296, 2.298, 2.301, 2.304, 2.307, 2.313, 2.317, 2.319, 2.323, 2.327, 2.331, 2.339, + 2.329, 2.328, 2.328, 2.328, 2.321, 2.313, 2.307, 2.303, 2.299, 2.295, 2.294, 2.292, 2.289, 2.289, 2.288, 2.288, 2.288, 2.289, 2.289, 2.292, 2.294, 2.295, 2.297, 2.301, 2.306, 2.311, 2.315, 2.318, 2.319, 2.323, 2.329, 2.335, + 2.326, 2.327, 2.325, 2.325, 2.319, 2.311, 2.305, 2.299, 2.296, 2.293, 2.291, 2.289, 2.288, 2.287, 2.285, 2.285, 2.286, 2.288, 2.288, 2.289, 2.291, 2.294, 2.295, 2.298, 2.304, 2.308, 2.313, 2.315, 2.317, 2.319, 2.327, 2.335, + 2.325, 2.325, 2.323, 2.323, 2.317, 2.309, 2.303, 2.298, 2.294, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.284, 2.284, 2.285, 2.287, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.305, 2.309, 2.313, 2.315, 2.317, 2.325, 2.334, + 2.322, 2.324, 2.322, 2.322, 2.316, 2.306, 2.301, 2.296, 2.292, 2.289, 2.287, 2.286, 2.285, 2.284, 2.283, 2.283, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.293, 2.296, 2.301, 2.304, 2.308, 2.311, 2.312, 2.315, 2.323, 2.333, + 2.321, 2.323, 2.322, 2.322, 2.314, 2.306, 2.299, 2.294, 2.291, 2.288, 2.286, 2.285, 2.284, 2.282, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.289, 2.291, 2.291, 2.294, 2.297, 2.302, 2.306, 2.308, 2.311, 2.312, 2.322, 2.332, + 2.319, 2.321, 2.321, 2.321, 2.314, 2.305, 2.297, 2.293, 2.289, 2.287, 2.285, 2.284, 2.283, 2.281, 2.281, 2.281, 2.282, 2.283, 2.283, 2.285, 2.287, 2.289, 2.291, 2.292, 2.297, 2.301, 2.305, 2.307, 2.309, 2.312, 2.321, 2.333, + 2.319, 2.321, 2.319, 2.319, 2.314, 2.303, 2.296, 2.293, 2.289, 2.286, 2.285, 2.283, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.288, 2.289, 2.291, 2.296, 2.301, 2.305, 2.307, 2.308, 2.312, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.296, 2.291, 2.289, 2.286, 2.284, 2.282, 2.281, 2.281, 2.281, 2.281, 2.282, 2.282, 2.283, 2.284, 2.286, 2.287, 2.288, 2.291, 2.295, 2.299, 2.304, 2.306, 2.307, 2.311, 2.321, 2.332, + 2.319, 2.321, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.287, 2.285, 2.282, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.284, 2.285, 2.286, 2.288, 2.291, 2.295, 2.299, 2.303, 2.306, 2.307, 2.312, 2.321, 2.331, + 2.318, 2.319, 2.319, 2.319, 2.313, 2.303, 2.297, 2.292, 2.289, 2.286, 2.285, 2.282, 2.281, 2.281, 2.281, 2.282, 2.282, 2.282, 2.282, 2.283, 2.285, 2.286, 2.287, 2.291, 2.294, 2.298, 2.303, 2.306, 2.307, 2.311, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.302, 2.297, 2.292, 2.289, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.287, 2.289, 2.294, 2.297, 2.303, 2.305, 2.308, 2.313, 2.321, 2.331, + 2.319, 2.319, 2.319, 2.319, 2.313, 2.303, 2.299, 2.293, 2.291, 2.287, 2.285, 2.283, 2.282, 2.281, 2.281, 2.282, 2.283, 2.283, 2.283, 2.283, 2.285, 2.286, 2.288, 2.291, 2.294, 2.298, 2.304, 2.306, 2.308, 2.312, 2.322, 2.331, + 2.319, 2.321, 2.321, 2.321, 2.315, 2.305, 2.301, 2.295, 2.292, 2.289, 2.286, 2.285, 2.283, 2.282, 2.282, 2.282, 2.284, 2.283, 2.284, 2.284, 2.285, 2.287, 2.288, 2.291, 2.294, 2.299, 2.304, 2.306, 2.309, 2.313, 2.322, 2.334, + 2.321, 2.322, 2.322, 2.322, 2.317, 2.307, 2.301, 2.296, 2.292, 2.291, 2.288, 2.286, 2.285, 2.284, 2.283, 2.284, 2.285, 2.284, 2.285, 2.285, 2.287, 2.288, 2.289, 2.293, 2.297, 2.301, 2.305, 2.308, 2.311, 2.314, 2.323, 2.335, + 2.322, 2.324, 2.324, 2.324, 2.319, 2.309, 2.303, 2.297, 2.295, 2.292, 2.291, 2.288, 2.286, 2.286, 2.285, 2.286, 2.286, 2.286, 2.287, 2.288, 2.289, 2.289, 2.291, 2.294, 2.299, 2.302, 2.307, 2.311, 2.312, 2.316, 2.325, 2.335, + 2.324, 2.326, 2.325, 2.326, 2.321, 2.311, 2.305, 2.301, 2.297, 2.295, 2.293, 2.291, 2.289, 2.289, 2.288, 2.288, 2.287, 2.288, 2.289, 2.291, 2.292, 2.292, 2.295, 2.299, 2.301, 2.304, 2.309, 2.312, 2.315, 2.319, 2.327, 2.337, + 2.329, 2.329, 2.328, 2.328, 2.323, 2.315, 2.308, 2.304, 2.301, 2.298, 2.296, 2.294, 2.291, 2.291, 2.289, 2.291, 2.291, 2.291, 2.292, 2.293, 2.294, 2.295, 2.297, 2.299, 2.303, 2.308, 2.312, 2.315, 2.318, 2.321, 2.329, 2.339, + 2.329, 2.331, 2.332, 2.332, 2.326, 2.318, 2.311, 2.306, 2.304, 2.301, 2.299, 2.297, 2.295, 2.293, 2.292, 2.292, 2.292, 2.293, 2.294, 2.294, 2.296, 2.297, 2.299, 2.302, 2.306, 2.311, 2.315, 2.318, 2.319, 2.324, 2.332, 2.342, + 2.331, 2.333, 2.334, 2.334, 2.328, 2.321, 2.313, 2.308, 2.305, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.295, 2.294, 2.296, 2.296, 2.297, 2.298, 2.299, 2.302, 2.305, 2.308, 2.314, 2.317, 2.321, 2.323, 2.327, 2.334, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.309, 2.306, 2.304, 2.303, 2.301, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.309, 2.315, 2.319, 2.321, 2.324, 2.328, 2.337, 2.346, + 2.331, 2.332, 2.334, 2.334, 2.329, 2.321, 2.314, 2.311, 2.306, 2.304, 2.303, 2.302, 2.299, 2.297, 2.295, 2.295, 2.296, 2.297, 2.298, 2.298, 2.299, 2.301, 2.303, 2.306, 2.311, 2.314, 2.319, 2.323, 2.325, 2.329, 2.339, 2.348, + 2.329, 2.329, 2.329, 2.331, 2.326, 2.319, 2.312, 2.309, 2.304, 2.303, 2.302, 2.301, 2.298, 2.295, 2.294, 2.294, 2.295, 2.295, 2.296, 2.297, 2.299, 2.301, 2.302, 2.304, 2.308, 2.313, 2.319, 2.322, 2.325, 2.329, 2.339, 2.351, + 2.329, 2.329, 2.329, 2.329, 2.326, 2.317, 2.311, 2.308, 2.303, 2.302, 2.301, 2.298, 2.296, 2.295, 2.294, 2.294, 2.294, 2.294, 2.296, 2.297, 2.298, 2.299, 2.301, 2.304, 2.307, 2.312, 2.318, 2.322, 2.326, 2.331, 2.341, 2.355, + 2.339, 2.332, 2.331, 2.331, 2.327, 2.323, 2.316, 2.309, 2.306, 2.302, 2.301, 2.299, 2.297, 2.296, 2.295, 2.294, 2.294, 2.296, 2.297, 2.297, 2.299, 2.301, 2.303, 2.306, 2.308, 2.317, 2.322, 2.325, 2.329, 2.341, 2.353, 2.361, + 2.347, 2.347, 2.345, 2.343, 2.338, 2.332, 2.326, 2.322, 2.321, 2.318, 2.316, 2.315, 2.313, 2.312, 2.311, 2.311, 2.311, 2.311, 2.312, 2.315, 2.317, 2.318, 2.319, 2.323, 2.324, 2.329, 2.334, 2.337, 2.344, 2.347, 2.361, 2.364 + ] + }, + { + "ct": 5000, + "table": + [ + 3.869, 3.852, 3.844, 3.842, 3.836, 3.821, 3.807, 3.796, 3.789, 3.784, 3.778, 3.775, 3.769, 3.768, 3.765, 3.765, 3.767, 3.769, 3.772, 3.774, 3.773, 3.775, 3.779, 3.787, 3.793, 3.801, 3.806, 3.804, 3.813, 3.819, 3.855, 3.879, + 3.854, 3.844, 3.837, 3.836, 3.824, 3.811, 3.797, 3.789, 3.784, 3.777, 3.774, 3.769, 3.764, 3.758, 3.757, 3.758, 3.758, 3.761, 3.763, 3.764, 3.765, 3.766, 3.772, 3.778, 3.787, 3.792, 3.794, 3.798, 3.802, 3.815, 3.839, 3.873, + 3.838, 3.831, 3.826, 3.823, 3.813, 3.799, 3.787, 3.781, 3.773, 3.768, 3.763, 3.759, 3.753, 3.749, 3.745, 3.745, 3.745, 3.752, 3.754, 3.757, 3.757, 3.759, 3.763, 3.769, 3.773, 3.781, 3.786, 3.792, 3.798, 3.811, 3.831, 3.861, + 3.833, 3.822, 3.817, 3.816, 3.804, 3.788, 3.779, 3.772, 3.766, 3.759, 3.755, 3.749, 3.744, 3.741, 3.738, 3.739, 3.739, 3.741, 3.743, 3.747, 3.749, 3.751, 3.756, 3.764, 3.769, 3.776, 3.783, 3.789, 3.798, 3.809, 3.821, 3.855, + 3.824, 3.818, 3.808, 3.808, 3.797, 3.781, 3.772, 3.764, 3.757, 3.752, 3.747, 3.743, 3.737, 3.735, 3.733, 3.733, 3.733, 3.735, 3.737, 3.738, 3.741, 3.746, 3.749, 3.755, 3.766, 3.771, 3.781, 3.789, 3.794, 3.806, 3.818, 3.849, + 3.815, 3.808, 3.799, 3.801, 3.787, 3.775, 3.767, 3.757, 3.751, 3.745, 3.738, 3.734, 3.732, 3.727, 3.725, 3.723, 3.722, 3.722, 3.726, 3.729, 3.734, 3.738, 3.744, 3.749, 3.759, 3.769, 3.781, 3.788, 3.792, 3.799, 3.811, 3.841, + 3.804, 3.799, 3.793, 3.793, 3.783, 3.771, 3.759, 3.751, 3.744, 3.735, 3.732, 3.727, 3.723, 3.721, 3.719, 3.716, 3.716, 3.716, 3.718, 3.722, 3.727, 3.731, 3.737, 3.746, 3.756, 3.767, 3.776, 3.782, 3.788, 3.795, 3.808, 3.831, + 3.802, 3.797, 3.787, 3.787, 3.779, 3.762, 3.753, 3.744, 3.734, 3.727, 3.725, 3.721, 3.716, 3.714, 3.709, 3.709, 3.711, 3.711, 3.712, 3.717, 3.722, 3.725, 3.731, 3.739, 3.752, 3.762, 3.772, 3.778, 3.779, 3.789, 3.798, 3.826, + 3.791, 3.789, 3.784, 3.784, 3.775, 3.759, 3.746, 3.735, 3.729, 3.724, 3.718, 3.714, 3.712, 3.707, 3.704, 3.704, 3.706, 3.708, 3.709, 3.711, 3.716, 3.722, 3.726, 3.735, 3.746, 3.754, 3.767, 3.774, 3.777, 3.781, 3.794, 3.824, + 3.789, 3.784, 3.779, 3.781, 3.771, 3.753, 3.741, 3.732, 3.725, 3.719, 3.715, 3.711, 3.707, 3.704, 3.701, 3.701, 3.702, 3.704, 3.708, 3.709, 3.713, 3.718, 3.724, 3.731, 3.742, 3.749, 3.761, 3.768, 3.772, 3.778, 3.791, 3.822, + 3.789, 3.781, 3.777, 3.777, 3.764, 3.749, 3.739, 3.729, 3.722, 3.718, 3.711, 3.708, 3.705, 3.701, 3.699, 3.699, 3.699, 3.701, 3.705, 3.707, 3.711, 3.715, 3.721, 3.727, 3.738, 3.746, 3.757, 3.763, 3.765, 3.773, 3.788, 3.821, + 3.785, 3.779, 3.774, 3.774, 3.764, 3.747, 3.736, 3.726, 3.719, 3.711, 3.709, 3.706, 3.701, 3.698, 3.696, 3.695, 3.695, 3.698, 3.702, 3.704, 3.707, 3.712, 3.718, 3.725, 3.734, 3.741, 3.753, 3.756, 3.759, 3.764, 3.784, 3.818, + 3.779, 3.776, 3.773, 3.773, 3.759, 3.744, 3.733, 3.724, 3.714, 3.709, 3.706, 3.704, 3.699, 3.696, 3.694, 3.694, 3.694, 3.697, 3.701, 3.703, 3.706, 3.709, 3.714, 3.721, 3.731, 3.737, 3.749, 3.753, 3.758, 3.762, 3.783, 3.819, + 3.779, 3.776, 3.769, 3.769, 3.757, 3.741, 3.729, 3.721, 3.712, 3.708, 3.705, 3.701, 3.697, 3.695, 3.694, 3.694, 3.695, 3.696, 3.698, 3.702, 3.705, 3.709, 3.712, 3.717, 3.728, 3.736, 3.749, 3.752, 3.756, 3.761, 3.781, 3.815, + 3.779, 3.773, 3.768, 3.768, 3.756, 3.738, 3.731, 3.719, 3.711, 3.707, 3.703, 3.698, 3.695, 3.694, 3.694, 3.695, 3.695, 3.695, 3.696, 3.702, 3.705, 3.708, 3.712, 3.717, 3.728, 3.736, 3.747, 3.751, 3.754, 3.761, 3.781, 3.815, + 3.782, 3.773, 3.767, 3.767, 3.755, 3.738, 3.728, 3.721, 3.711, 3.707, 3.701, 3.698, 3.695, 3.693, 3.694, 3.696, 3.695, 3.695, 3.695, 3.701, 3.703, 3.706, 3.711, 3.715, 3.726, 3.735, 3.745, 3.751, 3.754, 3.763, 3.779, 3.815, + 3.781, 3.771, 3.767, 3.767, 3.754, 3.739, 3.726, 3.721, 3.712, 3.706, 3.701, 3.698, 3.695, 3.693, 3.693, 3.695, 3.695, 3.695, 3.696, 3.698, 3.703, 3.705, 3.709, 3.715, 3.725, 3.734, 3.745, 3.751, 3.755, 3.762, 3.783, 3.818, + 3.781, 3.774, 3.767, 3.767, 3.755, 3.741, 3.729, 3.722, 3.712, 3.708, 3.701, 3.699, 3.695, 3.693, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.725, 3.732, 3.746, 3.751, 3.756, 3.763, 3.783, 3.821, + 3.781, 3.774, 3.769, 3.769, 3.756, 3.741, 3.731, 3.724, 3.713, 3.711, 3.707, 3.699, 3.697, 3.694, 3.693, 3.694, 3.695, 3.695, 3.697, 3.698, 3.702, 3.704, 3.709, 3.713, 3.724, 3.734, 3.747, 3.751, 3.756, 3.765, 3.784, 3.821, + 3.784, 3.776, 3.773, 3.773, 3.759, 3.742, 3.733, 3.726, 3.719, 3.711, 3.709, 3.703, 3.698, 3.695, 3.694, 3.695, 3.697, 3.696, 3.698, 3.699, 3.703, 3.706, 3.711, 3.714, 3.727, 3.735, 3.746, 3.751, 3.757, 3.766, 3.787, 3.822, + 3.786, 3.783, 3.774, 3.774, 3.766, 3.747, 3.737, 3.727, 3.722, 3.716, 3.711, 3.706, 3.702, 3.698, 3.697, 3.698, 3.699, 3.699, 3.701, 3.703, 3.706, 3.711, 3.713, 3.719, 3.731, 3.739, 3.748, 3.753, 3.761, 3.769, 3.789, 3.826, + 3.786, 3.784, 3.779, 3.779, 3.769, 3.751, 3.742, 3.732, 3.725, 3.719, 3.715, 3.711, 3.706, 3.704, 3.701, 3.701, 3.702, 3.702, 3.705, 3.707, 3.712, 3.714, 3.717, 3.724, 3.733, 3.743, 3.749, 3.758, 3.764, 3.769, 3.791, 3.826, + 3.793, 3.787, 3.782, 3.782, 3.774, 3.756, 3.747, 3.737, 3.729, 3.725, 3.719, 3.715, 3.712, 3.708, 3.707, 3.706, 3.707, 3.708, 3.709, 3.713, 3.714, 3.717, 3.723, 3.729, 3.736, 3.747, 3.757, 3.764, 3.768, 3.774, 3.794, 3.829, + 3.794, 3.791, 3.786, 3.786, 3.779, 3.762, 3.751, 3.742, 3.735, 3.729, 3.725, 3.719, 3.716, 3.711, 3.709, 3.709, 3.709, 3.711, 3.716, 3.717, 3.721, 3.723, 3.726, 3.732, 3.741, 3.752, 3.761, 3.767, 3.773, 3.779, 3.801, 3.829, + 3.802, 3.798, 3.793, 3.793, 3.779, 3.766, 3.754, 3.746, 3.741, 3.736, 3.731, 3.726, 3.719, 3.717, 3.716, 3.715, 3.716, 3.717, 3.719, 3.721, 3.724, 3.726, 3.731, 3.737, 3.744, 3.756, 3.766, 3.772, 3.776, 3.784, 3.807, 3.839, + 3.805, 3.799, 3.795, 3.795, 3.784, 3.767, 3.757, 3.749, 3.744, 3.739, 3.736, 3.731, 3.726, 3.722, 3.719, 3.719, 3.719, 3.721, 3.723, 3.725, 3.727, 3.732, 3.738, 3.742, 3.751, 3.761, 3.771, 3.775, 3.782, 3.789, 3.811, 3.841, + 3.804, 3.801, 3.799, 3.799, 3.787, 3.772, 3.761, 3.752, 3.746, 3.742, 3.739, 3.735, 3.729, 3.726, 3.723, 3.724, 3.725, 3.726, 3.727, 3.728, 3.732, 3.736, 3.739, 3.745, 3.754, 3.765, 3.775, 3.779, 3.785, 3.795, 3.816, 3.844, + 3.801, 3.799, 3.796, 3.796, 3.787, 3.773, 3.761, 3.753, 3.746, 3.743, 3.739, 3.735, 3.731, 3.726, 3.725, 3.725, 3.725, 3.726, 3.727, 3.729, 3.733, 3.736, 3.741, 3.745, 3.755, 3.766, 3.776, 3.783, 3.786, 3.797, 3.819, 3.851, + 3.799, 3.795, 3.788, 3.788, 3.783, 3.772, 3.759, 3.749, 3.744, 3.738, 3.735, 3.733, 3.726, 3.724, 3.722, 3.722, 3.723, 3.724, 3.725, 3.727, 3.729, 3.733, 3.736, 3.742, 3.754, 3.762, 3.772, 3.779, 3.784, 3.796, 3.821, 3.859, + 3.799, 3.789, 3.787, 3.788, 3.779, 3.766, 3.755, 3.749, 3.742, 3.736, 3.733, 3.727, 3.723, 3.722, 3.721, 3.719, 3.719, 3.721, 3.725, 3.726, 3.728, 3.732, 3.734, 3.741, 3.747, 3.758, 3.771, 3.778, 3.785, 3.796, 3.825, 3.862, + 3.824, 3.799, 3.789, 3.789, 3.788, 3.777, 3.761, 3.751, 3.743, 3.739, 3.736, 3.728, 3.726, 3.725, 3.721, 3.719, 3.721, 3.723, 3.727, 3.728, 3.729, 3.733, 3.737, 3.744, 3.755, 3.769, 3.776, 3.784, 3.793, 3.819, 3.863, 3.877, + 3.833, 3.833, 3.833, 3.842, 3.825, 3.815, 3.807, 3.799, 3.792, 3.788, 3.785, 3.782, 3.778, 3.777, 3.773, 3.772, 3.772, 3.774, 3.778, 3.779, 3.779, 3.785, 3.792, 3.798, 3.803, 3.811, 3.822, 3.834, 3.843, 3.846, 3.877, 3.886 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.616, 2.616, 2.618, 2.621, 2.619, 2.618, 2.615, 2.615, 2.613, 2.611, 2.609, 2.609, 2.609, 2.611, 2.611, 2.611, 2.611, 2.609, 2.608, 2.608, 2.611, 2.613, 2.613, 2.614, 2.614, 2.615, 2.615, 2.622, 2.624, 2.621, 2.624, 2.641, + 2.616, 2.618, 2.621, 2.623, 2.623, 2.619, 2.618, 2.616, 2.616, 2.613, 2.611, 2.611, 2.611, 2.611, 2.612, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.612, 2.613, 2.612, 2.613, 2.615, 2.617, 2.621, 2.621, 2.619, 2.621, 2.641, + 2.621, 2.624, 2.627, 2.627, 2.625, 2.623, 2.621, 2.619, 2.618, 2.618, 2.618, 2.617, 2.616, 2.616, 2.615, 2.613, 2.612, 2.613, 2.613, 2.614, 2.614, 2.613, 2.614, 2.613, 2.614, 2.617, 2.619, 2.621, 2.621, 2.619, 2.623, 2.643, + 2.626, 2.627, 2.628, 2.629, 2.628, 2.625, 2.622, 2.621, 2.621, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.616, 2.616, 2.616, 2.618, 2.618, 2.617, 2.617, 2.618, 2.619, 2.621, 2.623, 2.624, 2.626, 2.625, 2.624, 2.625, 2.654, + 2.627, 2.628, 2.628, 2.628, 2.626, 2.623, 2.622, 2.622, 2.622, 2.622, 2.621, 2.621, 2.619, 2.617, 2.617, 2.616, 2.617, 2.617, 2.618, 2.619, 2.618, 2.618, 2.618, 2.621, 2.622, 2.624, 2.626, 2.627, 2.627, 2.626, 2.628, 2.655, + 2.625, 2.626, 2.627, 2.626, 2.625, 2.623, 2.622, 2.621, 2.622, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.616, 2.616, 2.616, 2.616, 2.616, 2.617, 2.618, 2.619, 2.621, 2.622, 2.624, 2.626, 2.628, 2.628, 2.629, 2.629, 2.655, + 2.626, 2.625, 2.626, 2.625, 2.625, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.616, 2.614, 2.613, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.618, 2.619, 2.621, 2.623, 2.624, 2.627, 2.629, 2.631, 2.629, 2.631, 2.651, + 2.625, 2.625, 2.625, 2.624, 2.623, 2.623, 2.622, 2.622, 2.622, 2.621, 2.619, 2.617, 2.614, 2.613, 2.612, 2.611, 2.611, 2.612, 2.612, 2.613, 2.616, 2.618, 2.619, 2.622, 2.624, 2.626, 2.628, 2.631, 2.631, 2.631, 2.631, 2.651, + 2.625, 2.625, 2.624, 2.623, 2.622, 2.622, 2.622, 2.622, 2.622, 2.621, 2.617, 2.615, 2.613, 2.612, 2.611, 2.611, 2.611, 2.611, 2.611, 2.613, 2.615, 2.618, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.624, 2.624, 2.622, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.618, 2.616, 2.614, 2.612, 2.611, 2.609, 2.609, 2.608, 2.609, 2.611, 2.611, 2.615, 2.617, 2.619, 2.621, 2.625, 2.628, 2.631, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.622, 2.623, 2.622, 2.622, 2.621, 2.619, 2.619, 2.619, 2.618, 2.616, 2.614, 2.613, 2.611, 2.609, 2.608, 2.606, 2.607, 2.607, 2.609, 2.611, 2.615, 2.617, 2.619, 2.622, 2.626, 2.629, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.622, 2.622, 2.621, 2.619, 2.619, 2.618, 2.617, 2.614, 2.613, 2.611, 2.611, 2.607, 2.606, 2.605, 2.604, 2.605, 2.607, 2.609, 2.613, 2.616, 2.619, 2.622, 2.627, 2.631, 2.632, 2.632, 2.631, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.621, 2.621, 2.619, 2.617, 2.616, 2.615, 2.613, 2.609, 2.607, 2.604, 2.602, 2.601, 2.602, 2.603, 2.605, 2.609, 2.612, 2.616, 2.619, 2.624, 2.628, 2.631, 2.632, 2.633, 2.629, 2.627, 2.627, 2.651, + 2.619, 2.621, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.615, 2.614, 2.612, 2.608, 2.603, 2.601, 2.598, 2.597, 2.599, 2.602, 2.605, 2.608, 2.611, 2.615, 2.622, 2.625, 2.629, 2.631, 2.631, 2.633, 2.631, 2.627, 2.627, 2.651, + 2.621, 2.622, 2.623, 2.623, 2.622, 2.621, 2.618, 2.617, 2.616, 2.614, 2.611, 2.606, 2.601, 2.598, 2.595, 2.595, 2.597, 2.601, 2.604, 2.608, 2.612, 2.615, 2.623, 2.627, 2.629, 2.631, 2.631, 2.632, 2.631, 2.628, 2.628, 2.651, + 2.622, 2.623, 2.624, 2.624, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.609, 2.606, 2.601, 2.596, 2.594, 2.594, 2.596, 2.599, 2.603, 2.609, 2.613, 2.617, 2.623, 2.627, 2.629, 2.631, 2.632, 2.632, 2.631, 2.629, 2.631, 2.651, + 2.623, 2.625, 2.625, 2.624, 2.621, 2.621, 2.619, 2.617, 2.616, 2.613, 2.608, 2.605, 2.601, 2.595, 2.593, 2.593, 2.595, 2.598, 2.604, 2.609, 2.615, 2.619, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.632, 2.629, 2.631, 2.651, + 2.624, 2.626, 2.626, 2.623, 2.621, 2.619, 2.618, 2.617, 2.615, 2.612, 2.608, 2.605, 2.601, 2.597, 2.595, 2.595, 2.596, 2.598, 2.605, 2.609, 2.616, 2.621, 2.626, 2.627, 2.629, 2.631, 2.633, 2.633, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.618, 2.617, 2.614, 2.612, 2.609, 2.606, 2.602, 2.599, 2.598, 2.597, 2.598, 2.602, 2.607, 2.612, 2.619, 2.621, 2.626, 2.628, 2.629, 2.632, 2.633, 2.634, 2.633, 2.631, 2.631, 2.655, + 2.624, 2.625, 2.625, 2.623, 2.621, 2.621, 2.618, 2.617, 2.614, 2.612, 2.611, 2.608, 2.604, 2.602, 2.599, 2.599, 2.603, 2.606, 2.611, 2.616, 2.621, 2.624, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.634, 2.633, 2.633, 2.656, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.621, 2.619, 2.617, 2.615, 2.613, 2.611, 2.611, 2.607, 2.604, 2.604, 2.604, 2.606, 2.609, 2.613, 2.619, 2.622, 2.625, 2.628, 2.631, 2.632, 2.633, 2.633, 2.636, 2.636, 2.634, 2.634, 2.658, + 2.623, 2.624, 2.625, 2.623, 2.622, 2.619, 2.618, 2.616, 2.614, 2.613, 2.612, 2.611, 2.609, 2.608, 2.607, 2.608, 2.609, 2.613, 2.617, 2.621, 2.623, 2.626, 2.629, 2.631, 2.632, 2.633, 2.634, 2.635, 2.636, 2.636, 2.636, 2.661, + 2.623, 2.624, 2.625, 2.625, 2.623, 2.621, 2.619, 2.616, 2.615, 2.614, 2.613, 2.612, 2.612, 2.611, 2.611, 2.611, 2.614, 2.615, 2.619, 2.622, 2.625, 2.627, 2.631, 2.632, 2.633, 2.635, 2.635, 2.637, 2.637, 2.636, 2.637, 2.661, + 2.623, 2.624, 2.625, 2.626, 2.624, 2.621, 2.619, 2.617, 2.616, 2.615, 2.615, 2.614, 2.614, 2.614, 2.614, 2.614, 2.616, 2.619, 2.621, 2.623, 2.626, 2.628, 2.631, 2.632, 2.634, 2.635, 2.636, 2.637, 2.638, 2.637, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.627, 2.626, 2.623, 2.619, 2.619, 2.618, 2.618, 2.618, 2.617, 2.617, 2.616, 2.616, 2.616, 2.619, 2.622, 2.623, 2.625, 2.628, 2.628, 2.631, 2.632, 2.634, 2.636, 2.638, 2.639, 2.639, 2.638, 2.638, 2.661, + 2.625, 2.626, 2.627, 2.628, 2.626, 2.623, 2.621, 2.619, 2.619, 2.619, 2.619, 2.619, 2.619, 2.618, 2.618, 2.619, 2.623, 2.624, 2.625, 2.627, 2.629, 2.629, 2.632, 2.633, 2.635, 2.638, 2.639, 2.639, 2.639, 2.636, 2.636, 2.662, + 2.625, 2.627, 2.628, 2.628, 2.626, 2.624, 2.623, 2.622, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.621, 2.624, 2.624, 2.625, 2.627, 2.628, 2.631, 2.631, 2.632, 2.634, 2.636, 2.639, 2.639, 2.641, 2.639, 2.635, 2.635, 2.663, + 2.625, 2.626, 2.628, 2.628, 2.627, 2.625, 2.624, 2.623, 2.623, 2.622, 2.623, 2.624, 2.624, 2.625, 2.625, 2.625, 2.625, 2.626, 2.627, 2.629, 2.631, 2.632, 2.633, 2.635, 2.638, 2.641, 2.642, 2.643, 2.642, 2.636, 2.636, 2.665, + 2.624, 2.626, 2.628, 2.628, 2.628, 2.626, 2.624, 2.624, 2.623, 2.623, 2.623, 2.625, 2.627, 2.627, 2.626, 2.626, 2.626, 2.627, 2.628, 2.629, 2.632, 2.633, 2.635, 2.637, 2.639, 2.642, 2.644, 2.644, 2.642, 2.638, 2.638, 2.665, + 2.623, 2.625, 2.626, 2.627, 2.626, 2.626, 2.624, 2.623, 2.623, 2.623, 2.623, 2.623, 2.626, 2.627, 2.626, 2.626, 2.626, 2.626, 2.628, 2.628, 2.629, 2.631, 2.634, 2.636, 2.639, 2.642, 2.644, 2.643, 2.641, 2.637, 2.638, 2.659, + 2.623, 2.627, 2.627, 2.627, 2.627, 2.628, 2.627, 2.624, 2.624, 2.623, 2.624, 2.624, 2.628, 2.628, 2.627, 2.628, 2.628, 2.628, 2.629, 2.629, 2.631, 2.635, 2.637, 2.639, 2.641, 2.643, 2.646, 2.645, 2.643, 2.641, 2.654, 2.659, + 2.642, 2.641, 2.643, 2.645, 2.645, 2.644, 2.644, 2.643, 2.643, 2.642, 2.642, 2.642, 2.643, 2.644, 2.644, 2.644, 2.646, 2.646, 2.647, 2.649, 2.651, 2.652, 2.654, 2.656, 2.658, 2.661, 2.661, 2.661, 2.659, 2.654, 2.659, 2.659 + ] + }, + { + "ct": 5000, + "table": + [ + 1.391, 1.394, 1.395, 1.396, 1.398, 1.398, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.398, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.398, 1.399, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.399, 1.397, 1.399, 1.402, + 1.393, 1.395, 1.396, 1.398, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.401, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.399, 1.398, 1.398, 1.399, 1.401, 1.401, 1.399, 1.398, 1.399, 1.402, + 1.398, 1.401, 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.401, 1.401, 1.399, 1.399, 1.401, 1.401, 1.401, 1.401, 1.399, 1.401, 1.406, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.402, 1.403, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.405, 1.404, 1.404, 1.404, 1.404, 1.405, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.404, 1.405, 1.412, + 1.401, 1.401, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.406, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.405, 1.404, 1.404, 1.404, 1.404, 1.404, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.407, 1.408, 1.408, 1.407, 1.405, 1.405, 1.412, + 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.404, 1.405, 1.406, 1.405, 1.405, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.402, 1.402, 1.402, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.403, 1.402, 1.402, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.408, 1.407, 1.405, 1.405, 1.414, + 1.402, 1.402, 1.402, 1.402, 1.403, 1.403, 1.405, 1.405, 1.405, 1.405, 1.404, 1.404, 1.403, 1.402, 1.402, 1.401, 1.401, 1.402, 1.403, 1.403, 1.404, 1.405, 1.406, 1.407, 1.409, 1.409, 1.409, 1.409, 1.407, 1.405, 1.405, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.403, 1.404, 1.405, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.408, 1.405, 1.405, 1.413, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.405, 1.404, 1.402, 1.401, 1.399, 1.398, 1.398, 1.399, 1.399, 1.401, 1.403, 1.404, 1.405, 1.407, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.401, 1.398, 1.397, 1.397, 1.398, 1.399, 1.401, 1.403, 1.404, 1.405, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.399, 1.401, 1.403, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.408, 1.406, 1.406, 1.413, + 1.403, 1.404, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.404, 1.403, 1.402, 1.399, 1.397, 1.396, 1.396, 1.397, 1.398, 1.401, 1.403, 1.406, 1.407, 1.409, 1.409, 1.411, 1.409, 1.409, 1.409, 1.408, 1.407, 1.407, 1.413, + 1.403, 1.404, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.404, 1.404, 1.403, 1.402, 1.399, 1.398, 1.397, 1.397, 1.398, 1.399, 1.402, 1.404, 1.406, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.414, + 1.403, 1.403, 1.404, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.402, 1.401, 1.399, 1.398, 1.398, 1.398, 1.401, 1.403, 1.404, 1.408, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.401, 1.401, 1.399, 1.399, 1.401, 1.402, 1.404, 1.407, 1.408, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.407, 1.407, 1.415, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.403, 1.403, 1.403, 1.402, 1.401, 1.401, 1.401, 1.402, 1.404, 1.406, 1.407, 1.408, 1.409, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.409, 1.408, 1.408, 1.415, + 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.405, 1.406, 1.408, 1.408, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.409, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.402, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.403, 1.404, 1.404, 1.404, 1.405, 1.406, 1.407, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.416, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.407, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.405, 1.406, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.409, 1.408, 1.408, 1.417, + 1.403, 1.403, 1.403, 1.403, 1.404, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.406, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.416, + 1.402, 1.403, 1.403, 1.403, 1.404, 1.404, 1.404, 1.404, 1.405, 1.405, 1.406, 1.407, 1.407, 1.407, 1.408, 1.409, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.409, 1.407, 1.407, 1.417, + 1.402, 1.403, 1.403, 1.404, 1.404, 1.404, 1.405, 1.405, 1.405, 1.406, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.415, + 1.402, 1.402, 1.403, 1.403, 1.404, 1.404, 1.405, 1.405, 1.405, 1.405, 1.406, 1.407, 1.408, 1.408, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.412, 1.411, 1.409, 1.407, 1.407, 1.413, + 1.402, 1.402, 1.403, 1.403, 1.405, 1.406, 1.406, 1.406, 1.406, 1.406, 1.407, 1.408, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.412, 1.412, 1.413, 1.413, 1.411, 1.408, 1.411, 1.413, + 1.406, 1.406, 1.408, 1.408, 1.409, 1.409, 1.411, 1.411, 1.411, 1.411, 1.411, 1.411, 1.414, 1.414, 1.414, 1.414, 1.415, 1.415, 1.415, 1.415, 1.416, 1.416, 1.416, 1.417, 1.418, 1.418, 1.417, 1.417, 1.414, 1.411, 1.413, 1.413 + ] + } + ], + "luminance_lut": + [ + 1.554, 1.522, 1.466, 1.422, 1.385, 1.351, 1.322, 1.294, 1.269, 1.246, 1.228, 1.214, 1.207, 1.202, 1.199, 1.199, 1.199, 1.199, 1.202, 1.207, 1.218, 1.235, 1.255, 1.279, 1.305, 1.333, 1.365, 1.402, 1.447, 1.508, 1.602, 1.638, + 1.522, 1.478, 1.431, 1.391, 1.355, 1.323, 1.298, 1.271, 1.247, 1.228, 1.212, 1.199, 1.187, 1.179, 1.173, 1.172, 1.172, 1.174, 1.179, 1.189, 1.201, 1.216, 1.235, 1.256, 1.282, 1.308, 1.335, 1.368, 1.411, 1.461, 1.535, 1.602, + 1.479, 1.449, 1.407, 1.367, 1.332, 1.301, 1.271, 1.247, 1.226, 1.208, 1.191, 1.178, 1.166, 1.158, 1.153, 1.151, 1.151, 1.153, 1.159, 1.168, 1.179, 1.194, 1.212, 1.234, 1.256, 1.282, 1.311, 1.343, 1.382, 1.427, 1.489, 1.535, + 1.454, 1.423, 1.383, 1.345, 1.309, 1.278, 1.249, 1.226, 1.206, 1.187, 1.171, 1.158, 1.146, 1.138, 1.132, 1.129, 1.129, 1.133, 1.139, 1.147, 1.159, 1.173, 1.191, 1.212, 1.234, 1.261, 1.288, 1.321, 1.357, 1.401, 1.455, 1.489, + 1.433, 1.401, 1.362, 1.325, 1.289, 1.258, 1.231, 1.206, 1.187, 1.169, 1.153, 1.138, 1.129, 1.121, 1.115, 1.112, 1.112, 1.114, 1.121, 1.129, 1.141, 1.155, 1.172, 1.191, 1.214, 1.241, 1.269, 1.301, 1.337, 1.377, 1.428, 1.457, + 1.415, 1.382, 1.343, 1.306, 1.273, 1.241, 1.213, 1.189, 1.169, 1.153, 1.137, 1.123, 1.112, 1.105, 1.097, 1.095, 1.095, 1.098, 1.103, 1.112, 1.124, 1.139, 1.155, 1.173, 1.197, 1.222, 1.252, 1.282, 1.317, 1.356, 1.405, 1.434, + 1.398, 1.363, 1.325, 1.289, 1.256, 1.224, 1.198, 1.175, 1.155, 1.137, 1.123, 1.108, 1.097, 1.089, 1.083, 1.079, 1.079, 1.083, 1.088, 1.097, 1.109, 1.124, 1.139, 1.158, 1.181, 1.206, 1.234, 1.266, 1.299, 1.339, 1.384, 1.415, + 1.382, 1.347, 1.309, 1.274, 1.242, 1.211, 1.185, 1.162, 1.142, 1.124, 1.108, 1.095, 1.083, 1.075, 1.069, 1.066, 1.066, 1.068, 1.074, 1.083, 1.096, 1.109, 1.125, 1.145, 1.166, 1.191, 1.219, 1.251, 1.285, 1.324, 1.367, 1.399, + 1.369, 1.334, 1.296, 1.261, 1.228, 1.199, 1.173, 1.151, 1.131, 1.112, 1.095, 1.083, 1.071, 1.062, 1.056, 1.053, 1.053, 1.055, 1.061, 1.069, 1.083, 1.096, 1.112, 1.132, 1.153, 1.178, 1.206, 1.237, 1.271, 1.309, 1.353, 1.385, + 1.359, 1.321, 1.284, 1.251, 1.217, 1.189, 1.164, 1.141, 1.121, 1.102, 1.086, 1.071, 1.061, 1.049, 1.045, 1.042, 1.042, 1.043, 1.051, 1.061, 1.069, 1.085, 1.101, 1.121, 1.143, 1.167, 1.195, 1.225, 1.259, 1.298, 1.341, 1.375, + 1.351, 1.312, 1.275, 1.241, 1.209, 1.181, 1.155, 1.133, 1.112, 1.092, 1.076, 1.061, 1.049, 1.041, 1.034, 1.032, 1.032, 1.035, 1.041, 1.051, 1.061, 1.075, 1.092, 1.112, 1.133, 1.158, 1.185, 1.216, 1.249, 1.288, 1.331, 1.364, + 1.344, 1.303, 1.267, 1.233, 1.201, 1.173, 1.147, 1.124, 1.104, 1.085, 1.067, 1.053, 1.041, 1.033, 1.024, 1.022, 1.022, 1.025, 1.034, 1.041, 1.053, 1.066, 1.083, 1.103, 1.126, 1.149, 1.177, 1.207, 1.241, 1.279, 1.321, 1.357, + 1.339, 1.297, 1.261, 1.226, 1.194, 1.166, 1.142, 1.119, 1.098, 1.078, 1.061, 1.046, 1.034, 1.024, 1.017, 1.014, 1.014, 1.017, 1.025, 1.034, 1.046, 1.059, 1.077, 1.096, 1.118, 1.143, 1.169, 1.201, 1.235, 1.273, 1.314, 1.352, + 1.337, 1.293, 1.256, 1.223, 1.191, 1.163, 1.136, 1.114, 1.093, 1.074, 1.056, 1.041, 1.027, 1.017, 1.012, 1.006, 1.006, 1.013, 1.017, 1.028, 1.041, 1.055, 1.072, 1.092, 1.114, 1.138, 1.165, 1.195, 1.229, 1.268, 1.309, 1.348, + 1.337, 1.291, 1.253, 1.219, 1.187, 1.159, 1.133, 1.109, 1.089, 1.071, 1.053, 1.037, 1.023, 1.012, 1.006, 1.002, 1.003, 1.006, 1.013, 1.023, 1.038, 1.052, 1.069, 1.089, 1.111, 1.135, 1.161, 1.192, 1.226, 1.264, 1.306, 1.348, + 1.337, 1.291, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.035, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.341, 1.292, 1.253, 1.218, 1.186, 1.157, 1.132, 1.109, 1.088, 1.068, 1.049, 1.034, 1.021, 1.009, 1.001, 1.001, 1.001, 1.003, 1.011, 1.021, 1.035, 1.051, 1.069, 1.087, 1.109, 1.133, 1.161, 1.189, 1.224, 1.262, 1.304, 1.347, + 1.348, 1.298, 1.255, 1.219, 1.188, 1.159, 1.134, 1.111, 1.088, 1.069, 1.051, 1.035, 1.021, 1.009, 1.003, 1.001, 1.002, 1.004, 1.011, 1.022, 1.036, 1.053, 1.071, 1.089, 1.111, 1.135, 1.162, 1.191, 1.226, 1.264, 1.306, 1.347, + 1.354, 1.306, 1.258, 1.222, 1.191, 1.162, 1.135, 1.113, 1.092, 1.073, 1.054, 1.038, 1.024, 1.014, 1.008, 1.003, 1.004, 1.008, 1.014, 1.026, 1.039, 1.056, 1.073, 1.093, 1.115, 1.139, 1.165, 1.195, 1.229, 1.267, 1.309, 1.349, + 1.358, 1.312, 1.263, 1.227, 1.195, 1.167, 1.141, 1.117, 1.097, 1.078, 1.061, 1.043, 1.029, 1.021, 1.014, 1.008, 1.008, 1.014, 1.021, 1.032, 1.045, 1.061, 1.078, 1.097, 1.119, 1.144, 1.169, 1.201, 1.234, 1.272, 1.315, 1.353, + 1.364, 1.319, 1.269, 1.234, 1.201, 1.174, 1.148, 1.124, 1.103, 1.084, 1.067, 1.052, 1.038, 1.029, 1.021, 1.016, 1.016, 1.021, 1.029, 1.038, 1.051, 1.067, 1.084, 1.103, 1.126, 1.151, 1.176, 1.207, 1.241, 1.279, 1.321, 1.358, + 1.371, 1.326, 1.277, 1.242, 1.209, 1.181, 1.155, 1.132, 1.111, 1.092, 1.075, 1.061, 1.049, 1.038, 1.029, 1.027, 1.027, 1.029, 1.038, 1.047, 1.061, 1.075, 1.092, 1.111, 1.133, 1.157, 1.185, 1.213, 1.247, 1.286, 1.329, 1.365, + 1.379, 1.334, 1.287, 1.251, 1.219, 1.191, 1.164, 1.141, 1.119, 1.101, 1.085, 1.071, 1.061, 1.049, 1.041, 1.038, 1.038, 1.041, 1.047, 1.059, 1.071, 1.084, 1.101, 1.119, 1.141, 1.165, 1.193, 1.223, 1.257, 1.295, 1.338, 1.374, + 1.389, 1.343, 1.298, 1.262, 1.231, 1.201, 1.174, 1.151, 1.131, 1.111, 1.095, 1.083, 1.071, 1.061, 1.054, 1.051, 1.051, 1.054, 1.059, 1.071, 1.081, 1.094, 1.111, 1.129, 1.152, 1.176, 1.203, 1.235, 1.269, 1.307, 1.351, 1.384, + 1.401, 1.351, 1.311, 1.274, 1.242, 1.214, 1.187, 1.164, 1.142, 1.124, 1.108, 1.095, 1.083, 1.074, 1.068, 1.066, 1.066, 1.068, 1.073, 1.081, 1.094, 1.108, 1.123, 1.141, 1.164, 1.188, 1.215, 1.247, 1.281, 1.321, 1.364, 1.396, + 1.412, 1.366, 1.327, 1.289, 1.257, 1.227, 1.201, 1.176, 1.156, 1.137, 1.122, 1.108, 1.096, 1.088, 1.083, 1.081, 1.081, 1.082, 1.087, 1.095, 1.108, 1.122, 1.136, 1.154, 1.177, 1.201, 1.229, 1.261, 1.296, 1.337, 1.382, 1.409, + 1.421, 1.383, 1.343, 1.306, 1.273, 1.243, 1.216, 1.192, 1.169, 1.152, 1.137, 1.122, 1.111, 1.103, 1.098, 1.095, 1.095, 1.097, 1.102, 1.111, 1.123, 1.136, 1.152, 1.169, 1.191, 1.217, 1.246, 1.278, 1.314, 1.354, 1.399, 1.429, + 1.434, 1.402, 1.362, 1.324, 1.291, 1.261, 1.232, 1.208, 1.187, 1.168, 1.152, 1.138, 1.127, 1.119, 1.114, 1.112, 1.112, 1.115, 1.121, 1.128, 1.139, 1.152, 1.169, 1.186, 1.209, 1.234, 1.262, 1.295, 1.332, 1.372, 1.419, 1.451, + 1.453, 1.422, 1.382, 1.344, 1.309, 1.278, 1.249, 1.226, 1.204, 1.187, 1.168, 1.155, 1.144, 1.135, 1.131, 1.131, 1.131, 1.133, 1.138, 1.146, 1.157, 1.171, 1.186, 1.206, 1.227, 1.252, 1.281, 1.314, 1.351, 1.393, 1.442, 1.473, + 1.475, 1.446, 1.404, 1.366, 1.329, 1.298, 1.269, 1.245, 1.224, 1.204, 1.188, 1.174, 1.163, 1.154, 1.149, 1.148, 1.148, 1.152, 1.156, 1.164, 1.176, 1.189, 1.206, 1.226, 1.247, 1.274, 1.303, 1.336, 1.374, 1.417, 1.471, 1.505, + 1.503, 1.472, 1.428, 1.389, 1.353, 1.321, 1.291, 1.266, 1.245, 1.224, 1.207, 1.192, 1.183, 1.174, 1.169, 1.167, 1.168, 1.169, 1.175, 1.183, 1.195, 1.209, 1.226, 1.247, 1.267, 1.294, 1.325, 1.359, 1.397, 1.445, 1.505, 1.548, + 1.534, 1.503, 1.455, 1.413, 1.378, 1.344, 1.315, 1.289, 1.265, 1.243, 1.224, 1.207, 1.196, 1.192, 1.189, 1.189, 1.189, 1.189, 1.192, 1.198, 1.209, 1.226, 1.244, 1.266, 1.291, 1.318, 1.349, 1.383, 1.425, 1.475, 1.548, 1.591 + ], + "sigma": 0.00095, + "sigma_Cb": 0.00098 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2360, + "ccm": + [ + 1.66078, -0.23588, -0.42491, + -0.47456, 1.82763, -0.35307, + -0.00545, -1.44729, 2.45273 + ] + }, + { + "ct": 2870, + "ccm": + [ + 1.78373, -0.55344, -0.23029, + -0.39951, 1.69701, -0.29751, + 0.01986, -1.06525, 2.04539 + ] + }, + { + "ct": 2970, + "ccm": + [ + 1.73511, -0.56973, -0.16537, + -0.36338, 1.69878, -0.33539, + -0.02354, -0.76813, 1.79168 + ] + }, + { + "ct": 3000, + "ccm": + [ + 2.06374, -0.92218, -0.14156, + -0.41721, 1.69289, -0.27568, + -0.00554, -0.92741, 1.93295 + ] + }, + { + "ct": 3700, + "ccm": + [ + 2.13792, -1.08136, -0.05655, + -0.34739, 1.58989, -0.24249, + -0.00349, -0.76789, 1.77138 + ] + }, + { + "ct": 3870, + "ccm": + [ + 1.83834, -0.70528, -0.13307, + -0.30499, 1.60523, -0.30024, + -0.05701, -0.58313, 1.64014 + ] + }, + { + "ct": 4000, + "ccm": + [ + 2.15741, -1.10295, -0.05447, + -0.34631, 1.61158, -0.26528, + -0.02723, -0.70288, 1.73011 + ] + }, + { + "ct": 4400, + "ccm": + [ + 2.05729, -0.95007, -0.10723, + -0.41712, 1.78606, -0.36894, + -0.11899, -0.55727, 1.67626 + ] + }, + { + "ct": 4715, + "ccm": + [ + 1.90255, -0.77478, -0.12777, + -0.31338, 1.88197, -0.56858, + -0.06001, -0.61785, 1.67786 + ] + }, + { + "ct": 5920, + "ccm": + [ + 1.98691, -0.84671, -0.14019, + -0.26581, 1.70615, -0.44035, + -0.09532, -0.47332, 1.56864 + ] + }, + { + "ct": 9050, + "ccm": + [ + 2.09255, -0.76541, -0.32714, + -0.28973, 2.27462, -0.98489, + -0.17299, -0.61275, 1.78574 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx477_scientific.json b/src/ipa/rpi/pisp/data/imx477_scientific.json new file mode 100644 index 00000000..4ec5a15b --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx477_scientific.json @@ -0,0 +1,546 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 12000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 740, + "reference_Y": 15051 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.809 + } + }, + { + "rpi.geq": + { + "offset": 204, + "slope": 0.0061 + } + }, + { + "rpi.denoise": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2000.0, 0.6331025775790707, 0.27424225990946915, + 2200.0, 0.5696117366212947, 0.3116091368689487, + 2400.0, 0.5204264653110015, 0.34892179554105873, + 2600.0, 0.48148675531667223, 0.38565229719076793, + 2800.0, 0.450085403501908, 0.42145684622485047, + 3000.0, 0.42436130159169017, 0.45611835670028816, + 3200.0, 0.40300023695527337, 0.48950766215198593, + 3400.0, 0.3850520052612984, 0.5215567075837261, + 3600.0, 0.36981508088230314, 0.5522397906415475, + 4100.0, 0.333468007836758, 0.5909770465167908, + 4600.0, 0.31196097364221376, 0.6515706327327178, + 5100.0, 0.2961860409294588, 0.7068178946570284, + 5600.0, 0.2842607232745885, 0.7564837749584288, + 6100.0, 0.2750265787051251, 0.8006183524920533, + 6600.0, 0.2677057225584924, 0.8398879225373039, + 7100.0, 0.2617955199757274, 0.8746456080032436, + 7600.0, 0.25693714288250125, 0.905569559506562, + 8100.0, 0.25287531441063316, 0.9331696750390895, + 8600.0, 0.24946601483331993, 0.9576820904825795 + ], + "sensitivity_r": 1.05, + "sensitivity_b": 1.05, + "transverse_pos": 0.0238, + "transverse_neg": 0.04429, + "coarse_step": 0.1 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.contrast": + { + "ce_enable": 0, + "gamma_curve": + [ + 0, 0, + 512, 2304, + 1024, 4608, + 1536, 6573, + 2048, 8401, + 2560, 9992, + 3072, 11418, + 3584, 12719, + 4096, 13922, + 4608, 15045, + 5120, 16103, + 5632, 17104, + 6144, 18056, + 6656, 18967, + 7168, 19839, + 7680, 20679, + 8192, 21488, + 9216, 23028, + 10240, 24477, + 11264, 25849, + 12288, 27154, + 13312, 28401, + 14336, 29597, + 15360, 30747, + 16384, 31856, + 17408, 32928, + 18432, 33966, + 19456, 34973, + 20480, 35952, + 22528, 37832, + 24576, 39621, + 26624, 41330, + 28672, 42969, + 30720, 44545, + 32768, 46065, + 34816, 47534, + 36864, 48956, + 38912, 50336, + 40960, 51677, + 43008, 52982, + 45056, 54253, + 47104, 55493, + 49152, 56704, + 51200, 57888, + 53248, 59046, + 55296, 60181, + 57344, 61292, + 59392, 62382, + 61440, 63452, + 63488, 64503, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2000, + "ccm": + [ + 1.5813882365848004, -0.35293683714581114, -0.27378771561617715, + -0.4347297185453639, 1.5792631087746074, -0.12102601986382337, + 0.2322290578987574, -1.4382672640468128, 2.1386425781770755 + ] + }, + { + "ct": 2200, + "ccm": + [ + 1.6322048484088305, -0.45932286857238486, -0.21373542690252198, + -0.3970719209901105, 1.5877868651467202, -0.17249380832122455, + 0.20753774825903412, -1.2660673594740142, 2.005654261091916 + ] + }, + { + "ct": 2400, + "ccm": + [ + 1.6766610071470398, -0.5447101051688111, -0.16838641107407676, + -0.3659845183388154, 1.592223692670396, -0.2127091997471162, + 0.1833964516767549, -1.1339155942419321, 1.9089342978542396 + ] + }, + { + "ct": 2600, + "ccm": + [ + 1.7161984340622154, -0.6152585785678794, -0.1331100845092582, + -0.33972082628066275, 1.5944888273736966, -0.2453979465898787, + 0.1615577497676328, -1.0298684958833109, 1.8357854177422053 + ] + }, + { + "ct": 2800, + "ccm": + [ + 1.7519307259815728, -0.6748682080165339, -0.10515169074540848, + -0.3171703484479931, 1.5955820297498486, -0.2727395854813966, + 0.14230870739974305, -0.9460976023551511, 1.778709391659538 + ] + }, + { + "ct": 3000, + "ccm": + [ + 1.7846716625128374, -0.7261240476375332, -0.08274697420358428, + -0.2975654035173307, 1.5960425637021738, -0.2961043416505157, + 0.12546426281675097, -0.8773434727076518, 1.7330356805246685 + ] + }, + { + "ct": 3200, + "ccm": + [ + 1.8150085872943436, -0.7708109672515514, -0.06469468211419174, + -0.2803468940646277, 1.596168842967451, -0.3164044170681625, + 0.11071494533513807, -0.8199772290209191, 1.69572135046367 + ] + }, + { + "ct": 3400, + "ccm": + [ + 1.8433668304932087, -0.8102060605062592, -0.05013485852801454, + -0.2650934036324084, 1.5961288492969294, -0.33427554893845535, + 0.0977478941863518, -0.7714303112098978, 1.6647070820146963 + ] + }, + { + "ct": 3600, + "ccm": + [ + 1.8700575831917468, -0.8452518300291346, -0.03842644337477299, + -0.2514794528347016, 1.5960178299141876, -0.3501774949366156, + 0.08628520830733245, -0.729841503339915, 1.638553343939267 + ] + }, + { + "ct": 4100, + "ccm": + [ + 1.8988700903560716, -0.8911278803351247, -0.018848644425650693, + -0.21487101487384094, 1.599236541382614, -0.39405450457918206, + 0.08251488056482173, -0.7178919368326191, 1.6267009056502704 + ] + }, + { + "ct": 4600, + "ccm": + [ + 1.960355191764125, -0.9624344812121991, -0.0017122408632169205, + -0.19444620905212898, 1.5978493736948447, -0.416727638296156, + 0.06310261513271084, -0.6483790952487849, 1.5834605477213093 + ] + }, + { + "ct": 5100, + "ccm": + [ + 2.014680536961399, -1.0195930302148566, 0.007728256612638915, + -0.17751999660735496, 1.5977081555831, -0.4366085498741474, + 0.04741267583041334, -0.5950327902073489, 1.5512919847321853 + ] + }, + { + "ct": 5600, + "ccm": + [ + 2.062652337917251, -1.0658386679125478, 0.011886354256281267, + -0.16319197721451495, 1.598363237584736, -0.45422061523742235, + 0.03465810928795378, -0.5535454108047286, 1.5269025836946852 + ] + }, + { + "ct": 6100, + "ccm": + [ + 2.104985902038069, -1.103597868736314, 0.012503517136539277, + -0.15090797064906178, 1.5994703078166095, -0.4698414300864995, + 0.02421766063474242, -0.5208922818196823, 1.5081270847783788 + ] + }, + { + "ct": 6600, + "ccm": + [ + 2.1424988751299714, -1.134760232367728, 0.010730356010435522, + -0.14021846798466234, 1.600822462230719, -0.48379204794526487, + 0.015521315410496622, -0.49463630325832275, 1.4933313534840327 + ] + }, + { + "ct": 7100, + "ccm": + [ + 2.1758034100130925, -1.1607558481037359, 0.007452724895469076, + -0.13085694672641826, 1.6022648614493245, -0.4962330524084075, + 0.008226943206113427, -0.4733077192319791, 1.4815336120437468 + ] + }, + { + "ct": 7600, + "ccm": + [ + 2.205529206931895, -1.1826662383072108, 0.0032019529917605167, + -0.122572009780486, 1.6037258133595753, -0.5073973734282445, + 0.0020132587619863425, -0.4556590236414181, 1.471939788496745 + ] + }, + { + "ct": 8100, + "ccm": + [ + 2.232224969223067, -1.2013672897252885, -0.0016234598095482985, + -0.11518026734442414, 1.6051544769439803, -0.5174558699422255, + -0.0033378143542219835, -0.4408590373867774, 1.4640252230667452 + ] + }, + { + "ct": 8600, + "ccm": + [ + 2.256082295891265, -1.2173210549996634, -0.0067231350481711675, + -0.10860272839843167, 1.6065150139140594, -0.5264728573611493, + -0.007952618707984149, -0.4284003574050791, 1.4574646927117558 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx519.json b/src/ipa/rpi/pisp/data/imx519.json new file mode 100644 index 00000000..9bc4d9a3 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx519.json @@ -0,0 +1,634 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 13841, + "reference_gain": 2.0, + "reference_aperture": 1.0, + "reference_lux": 900, + "reference_Y": 12064 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.776 + } + }, + { + "rpi.geq": + { + "offset": 189, + "slope": 0.01495 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7900 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2890.0, 0.7328, 0.3734, + 3550.0, 0.6228, 0.4763, + 4500.0, 0.5208, 0.5825, + 5700.0, 0.4467, 0.6671, + 7900.0, 0.3858, 0.7411 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02027, + "transverse_neg": 0.01935 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.5, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.527, 1.524, 1.521, 1.515, 1.509, 1.502, 1.494, 1.486, 1.478, 1.469, 1.458, 1.451, 1.445, 1.442, 1.441, 1.441, 1.441, 1.441, 1.441, 1.442, 1.446, 1.451, 1.46, 1.469, 1.477, 1.484, 1.489, 1.495, 1.499, 1.503, 1.504, 1.504, + 1.526, 1.522, 1.518, 1.512, 1.505, 1.497, 1.489, 1.481, 1.473, 1.462, 1.451, 1.443, 1.436, 1.432, 1.431, 1.43, 1.43, 1.43, 1.431, 1.434, 1.438, 1.444, 1.454, 1.463, 1.471, 1.479, 1.485, 1.491, 1.496, 1.5, 1.502, 1.504, + 1.526, 1.521, 1.516, 1.508, 1.501, 1.492, 1.483, 1.475, 1.467, 1.456, 1.444, 1.435, 1.428, 1.423, 1.42, 1.418, 1.418, 1.419, 1.422, 1.425, 1.431, 1.438, 1.447, 1.457, 1.466, 1.474, 1.482, 1.488, 1.493, 1.498, 1.5, 1.503, + 1.524, 1.519, 1.513, 1.505, 1.496, 1.487, 1.478, 1.469, 1.461, 1.45, 1.437, 1.427, 1.419, 1.413, 1.409, 1.407, 1.407, 1.408, 1.412, 1.417, 1.423, 1.431, 1.441, 1.45, 1.46, 1.469, 1.477, 1.485, 1.49, 1.495, 1.499, 1.502, + 1.522, 1.516, 1.51, 1.502, 1.493, 1.483, 1.472, 1.462, 1.452, 1.441, 1.429, 1.419, 1.409, 1.402, 1.397, 1.395, 1.395, 1.397, 1.401, 1.406, 1.414, 1.422, 1.433, 1.443, 1.453, 1.462, 1.471, 1.48, 1.486, 1.492, 1.496, 1.5, + 1.519, 1.513, 1.508, 1.499, 1.489, 1.478, 1.467, 1.455, 1.443, 1.432, 1.421, 1.41, 1.399, 1.391, 1.386, 1.383, 1.383, 1.386, 1.39, 1.396, 1.405, 1.414, 1.425, 1.436, 1.446, 1.456, 1.466, 1.475, 1.483, 1.49, 1.493, 1.497, + 1.516, 1.511, 1.505, 1.495, 1.485, 1.473, 1.461, 1.448, 1.435, 1.423, 1.412, 1.401, 1.389, 1.381, 1.375, 1.372, 1.372, 1.374, 1.379, 1.386, 1.396, 1.406, 1.418, 1.429, 1.439, 1.449, 1.46, 1.47, 1.479, 1.487, 1.491, 1.494, + 1.515, 1.508, 1.502, 1.491, 1.48, 1.467, 1.454, 1.441, 1.427, 1.415, 1.404, 1.392, 1.38, 1.371, 1.364, 1.361, 1.361, 1.363, 1.369, 1.377, 1.388, 1.398, 1.409, 1.42, 1.431, 1.443, 1.454, 1.465, 1.475, 1.484, 1.488, 1.492, + 1.513, 1.505, 1.498, 1.487, 1.475, 1.461, 1.448, 1.434, 1.419, 1.407, 1.396, 1.383, 1.37, 1.361, 1.353, 1.349, 1.349, 1.352, 1.359, 1.367, 1.379, 1.391, 1.401, 1.412, 1.424, 1.436, 1.448, 1.46, 1.471, 1.481, 1.485, 1.49, + 1.511, 1.503, 1.495, 1.483, 1.47, 1.456, 1.442, 1.427, 1.412, 1.399, 1.387, 1.375, 1.362, 1.352, 1.344, 1.34, 1.34, 1.343, 1.35, 1.36, 1.371, 1.383, 1.393, 1.405, 1.418, 1.431, 1.443, 1.455, 1.467, 1.478, 1.483, 1.488, + 1.51, 1.501, 1.492, 1.479, 1.466, 1.451, 1.436, 1.421, 1.406, 1.392, 1.378, 1.366, 1.355, 1.346, 1.336, 1.332, 1.332, 1.335, 1.344, 1.353, 1.363, 1.374, 1.385, 1.398, 1.412, 1.425, 1.439, 1.452, 1.463, 1.475, 1.481, 1.486, + 1.508, 1.499, 1.489, 1.475, 1.461, 1.446, 1.43, 1.414, 1.399, 1.384, 1.369, 1.358, 1.348, 1.339, 1.329, 1.324, 1.323, 1.328, 1.338, 1.347, 1.355, 1.365, 1.378, 1.391, 1.406, 1.42, 1.434, 1.448, 1.46, 1.472, 1.478, 1.484, + 1.508, 1.497, 1.487, 1.473, 1.459, 1.443, 1.426, 1.41, 1.394, 1.379, 1.363, 1.351, 1.342, 1.333, 1.325, 1.321, 1.32, 1.324, 1.332, 1.34, 1.349, 1.359, 1.372, 1.386, 1.401, 1.416, 1.43, 1.445, 1.457, 1.47, 1.477, 1.484, + 1.507, 1.496, 1.485, 1.471, 1.457, 1.441, 1.424, 1.407, 1.391, 1.375, 1.359, 1.346, 1.335, 1.327, 1.322, 1.319, 1.319, 1.321, 1.326, 1.333, 1.343, 1.354, 1.368, 1.382, 1.397, 1.412, 1.427, 1.442, 1.455, 1.468, 1.476, 1.483, + 1.507, 1.495, 1.483, 1.469, 1.455, 1.439, 1.422, 1.404, 1.387, 1.371, 1.355, 1.341, 1.328, 1.321, 1.319, 1.318, 1.318, 1.319, 1.321, 1.326, 1.338, 1.35, 1.363, 1.378, 1.393, 1.408, 1.424, 1.439, 1.453, 1.466, 1.474, 1.483, + 1.507, 1.495, 1.483, 1.469, 1.455, 1.438, 1.421, 1.404, 1.387, 1.37, 1.354, 1.34, 1.327, 1.32, 1.318, 1.316, 1.316, 1.317, 1.32, 1.326, 1.337, 1.35, 1.363, 1.377, 1.393, 1.408, 1.424, 1.439, 1.452, 1.466, 1.474, 1.483, + 1.507, 1.495, 1.483, 1.469, 1.455, 1.438, 1.421, 1.404, 1.387, 1.37, 1.354, 1.34, 1.327, 1.32, 1.316, 1.315, 1.315, 1.316, 1.319, 1.326, 1.337, 1.35, 1.363, 1.377, 1.393, 1.408, 1.424, 1.439, 1.452, 1.466, 1.474, 1.483, + 1.507, 1.495, 1.483, 1.469, 1.455, 1.438, 1.422, 1.404, 1.387, 1.37, 1.355, 1.341, 1.328, 1.32, 1.315, 1.313, 1.313, 1.315, 1.319, 1.326, 1.338, 1.35, 1.363, 1.377, 1.393, 1.408, 1.424, 1.439, 1.452, 1.466, 1.474, 1.483, + 1.507, 1.495, 1.484, 1.47, 1.456, 1.439, 1.423, 1.406, 1.389, 1.372, 1.357, 1.343, 1.331, 1.323, 1.318, 1.316, 1.316, 1.318, 1.323, 1.33, 1.34, 1.352, 1.366, 1.38, 1.395, 1.41, 1.425, 1.44, 1.453, 1.466, 1.475, 1.483, + 1.507, 1.496, 1.485, 1.471, 1.456, 1.44, 1.424, 1.407, 1.39, 1.374, 1.359, 1.346, 1.335, 1.326, 1.32, 1.318, 1.319, 1.321, 1.327, 1.334, 1.343, 1.354, 1.368, 1.383, 1.398, 1.412, 1.427, 1.442, 1.455, 1.467, 1.475, 1.483, + 1.507, 1.497, 1.486, 1.472, 1.458, 1.442, 1.426, 1.41, 1.393, 1.377, 1.362, 1.35, 1.339, 1.331, 1.324, 1.321, 1.322, 1.325, 1.331, 1.338, 1.347, 1.357, 1.372, 1.386, 1.4, 1.415, 1.429, 1.443, 1.456, 1.468, 1.475, 1.483, + 1.507, 1.497, 1.487, 1.474, 1.46, 1.445, 1.43, 1.414, 1.398, 1.382, 1.368, 1.356, 1.347, 1.338, 1.329, 1.326, 1.326, 1.33, 1.337, 1.345, 1.353, 1.364, 1.377, 1.39, 1.405, 1.419, 1.432, 1.446, 1.458, 1.469, 1.476, 1.483, + 1.508, 1.498, 1.489, 1.476, 1.463, 1.448, 1.433, 1.418, 1.402, 1.388, 1.373, 1.363, 1.354, 1.345, 1.335, 1.33, 1.331, 1.334, 1.343, 1.351, 1.36, 1.37, 1.382, 1.394, 1.409, 1.422, 1.435, 1.448, 1.46, 1.471, 1.477, 1.484, + 1.508, 1.499, 1.49, 1.478, 1.466, 1.452, 1.437, 1.422, 1.407, 1.394, 1.381, 1.37, 1.361, 1.352, 1.342, 1.338, 1.338, 1.341, 1.349, 1.358, 1.367, 1.377, 1.388, 1.4, 1.413, 1.427, 1.439, 1.451, 1.462, 1.472, 1.478, 1.484, + 1.51, 1.501, 1.492, 1.481, 1.469, 1.455, 1.441, 1.427, 1.413, 1.4, 1.389, 1.378, 1.368, 1.359, 1.351, 1.347, 1.347, 1.35, 1.357, 1.364, 1.375, 1.385, 1.395, 1.406, 1.419, 1.432, 1.443, 1.455, 1.465, 1.474, 1.48, 1.486, + 1.511, 1.502, 1.494, 1.483, 1.472, 1.459, 1.445, 1.431, 1.418, 1.407, 1.398, 1.387, 1.375, 1.366, 1.36, 1.357, 1.357, 1.359, 1.364, 1.371, 1.382, 1.393, 1.402, 1.412, 1.424, 1.436, 1.447, 1.458, 1.467, 1.477, 1.482, 1.487, + 1.511, 1.503, 1.495, 1.485, 1.475, 1.462, 1.449, 1.436, 1.424, 1.414, 1.405, 1.394, 1.383, 1.375, 1.37, 1.368, 1.368, 1.369, 1.373, 1.38, 1.39, 1.4, 1.409, 1.418, 1.43, 1.441, 1.451, 1.461, 1.469, 1.478, 1.482, 1.487, + 1.511, 1.503, 1.496, 1.486, 1.477, 1.465, 1.453, 1.442, 1.431, 1.421, 1.412, 1.402, 1.392, 1.385, 1.381, 1.378, 1.378, 1.38, 1.383, 1.389, 1.398, 1.406, 1.415, 1.424, 1.436, 1.446, 1.455, 1.464, 1.471, 1.478, 1.483, 1.487, + 1.511, 1.504, 1.496, 1.488, 1.479, 1.468, 1.457, 1.447, 1.437, 1.428, 1.418, 1.409, 1.401, 1.395, 1.391, 1.389, 1.389, 1.39, 1.393, 1.398, 1.405, 1.413, 1.421, 1.431, 1.441, 1.452, 1.459, 1.466, 1.473, 1.479, 1.483, 1.487, + 1.511, 1.504, 1.496, 1.488, 1.479, 1.469, 1.459, 1.45, 1.442, 1.433, 1.424, 1.416, 1.408, 1.403, 1.4, 1.399, 1.399, 1.4, 1.402, 1.406, 1.412, 1.419, 1.427, 1.436, 1.446, 1.455, 1.462, 1.468, 1.474, 1.48, 1.484, 1.487, + 1.511, 1.503, 1.496, 1.488, 1.479, 1.47, 1.461, 1.453, 1.446, 1.438, 1.429, 1.422, 1.415, 1.411, 1.41, 1.409, 1.409, 1.41, 1.411, 1.413, 1.419, 1.425, 1.433, 1.441, 1.45, 1.457, 1.464, 1.47, 1.476, 1.481, 1.484, 1.487, + 1.511, 1.503, 1.496, 1.487, 1.479, 1.471, 1.464, 1.457, 1.45, 1.442, 1.435, 1.428, 1.422, 1.419, 1.419, 1.419, 1.419, 1.419, 1.419, 1.421, 1.426, 1.432, 1.439, 1.447, 1.454, 1.46, 1.466, 1.472, 1.477, 1.482, 1.485, 1.487 + ] + }, + { + "ct": 6000, + "table": + [ + 2.581, 2.577, 2.573, 2.566, 2.559, 2.55, 2.541, 2.529, 2.517, 2.504, 2.491, 2.482, 2.476, 2.472, 2.471, 2.471, 2.471, 2.471, 2.471, 2.473, 2.476, 2.482, 2.492, 2.501, 2.51, 2.518, 2.526, 2.533, 2.538, 2.543, 2.544, 2.544, + 2.579, 2.574, 2.568, 2.56, 2.552, 2.543, 2.534, 2.522, 2.509, 2.496, 2.481, 2.471, 2.463, 2.458, 2.455, 2.453, 2.453, 2.454, 2.455, 2.458, 2.464, 2.472, 2.483, 2.494, 2.503, 2.512, 2.52, 2.528, 2.534, 2.54, 2.542, 2.544, + 2.577, 2.57, 2.564, 2.555, 2.546, 2.537, 2.528, 2.515, 2.501, 2.487, 2.471, 2.46, 2.45, 2.443, 2.438, 2.436, 2.436, 2.437, 2.44, 2.444, 2.452, 2.462, 2.474, 2.486, 2.496, 2.506, 2.515, 2.524, 2.53, 2.537, 2.54, 2.543, + 2.574, 2.566, 2.559, 2.549, 2.539, 2.53, 2.521, 2.507, 2.493, 2.477, 2.461, 2.448, 2.437, 2.428, 2.421, 2.418, 2.418, 2.42, 2.424, 2.43, 2.44, 2.451, 2.465, 2.478, 2.489, 2.499, 2.509, 2.519, 2.526, 2.533, 2.538, 2.542, + 2.569, 2.562, 2.555, 2.544, 2.533, 2.522, 2.511, 2.496, 2.481, 2.465, 2.449, 2.435, 2.422, 2.413, 2.405, 2.402, 2.402, 2.404, 2.409, 2.416, 2.426, 2.438, 2.453, 2.466, 2.479, 2.491, 2.502, 2.512, 2.52, 2.528, 2.533, 2.538, + 2.564, 2.558, 2.552, 2.539, 2.527, 2.514, 2.5, 2.485, 2.469, 2.453, 2.436, 2.422, 2.408, 2.398, 2.389, 2.385, 2.385, 2.388, 2.393, 2.401, 2.413, 2.425, 2.44, 2.455, 2.469, 2.483, 2.494, 2.505, 2.514, 2.523, 2.529, 2.534, + 2.56, 2.553, 2.547, 2.534, 2.52, 2.505, 2.49, 2.474, 2.457, 2.441, 2.424, 2.409, 2.393, 2.382, 2.373, 2.369, 2.369, 2.371, 2.378, 2.386, 2.399, 2.412, 2.428, 2.444, 2.459, 2.473, 2.486, 2.497, 2.508, 2.518, 2.524, 2.53, + 2.557, 2.549, 2.541, 2.527, 2.512, 2.495, 2.479, 2.462, 2.445, 2.429, 2.413, 2.396, 2.379, 2.366, 2.356, 2.351, 2.351, 2.354, 2.362, 2.372, 2.385, 2.399, 2.416, 2.433, 2.448, 2.463, 2.476, 2.489, 2.501, 2.513, 2.519, 2.526, + 2.553, 2.544, 2.535, 2.519, 2.504, 2.486, 2.468, 2.45, 2.433, 2.417, 2.401, 2.383, 2.364, 2.349, 2.338, 2.333, 2.333, 2.337, 2.346, 2.357, 2.371, 2.386, 2.404, 2.421, 2.437, 2.452, 2.467, 2.481, 2.495, 2.508, 2.514, 2.521, + 2.55, 2.54, 2.529, 2.513, 2.497, 2.478, 2.458, 2.44, 2.422, 2.405, 2.388, 2.37, 2.352, 2.336, 2.323, 2.317, 2.317, 2.322, 2.332, 2.344, 2.358, 2.374, 2.392, 2.409, 2.426, 2.442, 2.458, 2.474, 2.489, 2.502, 2.509, 2.516, + 2.547, 2.536, 2.525, 2.507, 2.49, 2.47, 2.45, 2.43, 2.411, 2.393, 2.374, 2.357, 2.342, 2.326, 2.31, 2.303, 2.302, 2.308, 2.32, 2.333, 2.348, 2.363, 2.379, 2.396, 2.414, 2.433, 2.45, 2.468, 2.482, 2.497, 2.504, 2.512, + 2.544, 2.532, 2.52, 2.502, 2.483, 2.463, 2.442, 2.421, 2.4, 2.38, 2.36, 2.344, 2.332, 2.316, 2.297, 2.288, 2.288, 2.294, 2.308, 2.322, 2.337, 2.352, 2.367, 2.383, 2.403, 2.423, 2.442, 2.461, 2.476, 2.491, 2.499, 2.507, + 2.542, 2.53, 2.517, 2.498, 2.479, 2.458, 2.436, 2.414, 2.393, 2.371, 2.35, 2.334, 2.319, 2.305, 2.29, 2.283, 2.282, 2.287, 2.298, 2.311, 2.326, 2.341, 2.358, 2.375, 2.396, 2.417, 2.436, 2.456, 2.472, 2.487, 2.496, 2.505, + 2.542, 2.528, 2.515, 2.495, 2.476, 2.453, 2.431, 2.408, 2.386, 2.364, 2.342, 2.323, 2.306, 2.294, 2.284, 2.28, 2.28, 2.284, 2.291, 2.3, 2.315, 2.331, 2.35, 2.369, 2.39, 2.411, 2.431, 2.451, 2.467, 2.484, 2.494, 2.505, + 2.541, 2.527, 2.512, 2.492, 2.472, 2.449, 2.426, 2.403, 2.379, 2.356, 2.334, 2.313, 2.293, 2.283, 2.279, 2.278, 2.279, 2.28, 2.283, 2.29, 2.304, 2.321, 2.342, 2.363, 2.384, 2.406, 2.426, 2.446, 2.463, 2.48, 2.492, 2.504, + 2.541, 2.526, 2.512, 2.492, 2.472, 2.449, 2.426, 2.402, 2.378, 2.356, 2.333, 2.312, 2.292, 2.281, 2.276, 2.274, 2.275, 2.277, 2.28, 2.288, 2.303, 2.32, 2.341, 2.363, 2.384, 2.405, 2.425, 2.445, 2.463, 2.48, 2.492, 2.504, + 2.541, 2.526, 2.512, 2.492, 2.472, 2.449, 2.426, 2.402, 2.378, 2.356, 2.333, 2.312, 2.292, 2.28, 2.273, 2.27, 2.271, 2.273, 2.279, 2.288, 2.303, 2.32, 2.341, 2.363, 2.384, 2.405, 2.425, 2.445, 2.463, 2.48, 2.492, 2.504, + 2.541, 2.526, 2.512, 2.492, 2.472, 2.449, 2.426, 2.402, 2.379, 2.356, 2.334, 2.313, 2.293, 2.28, 2.271, 2.267, 2.267, 2.271, 2.278, 2.288, 2.303, 2.32, 2.342, 2.363, 2.384, 2.405, 2.426, 2.445, 2.463, 2.48, 2.492, 2.504, + 2.541, 2.527, 2.512, 2.493, 2.473, 2.45, 2.427, 2.404, 2.382, 2.36, 2.338, 2.318, 2.299, 2.285, 2.276, 2.271, 2.272, 2.276, 2.284, 2.294, 2.308, 2.324, 2.345, 2.365, 2.386, 2.407, 2.427, 2.447, 2.464, 2.481, 2.492, 2.504, + 2.541, 2.527, 2.513, 2.493, 2.474, 2.451, 2.429, 2.406, 2.385, 2.363, 2.342, 2.323, 2.305, 2.291, 2.28, 2.275, 2.276, 2.28, 2.29, 2.301, 2.313, 2.328, 2.348, 2.368, 2.389, 2.409, 2.429, 2.448, 2.465, 2.481, 2.493, 2.504, + 2.541, 2.527, 2.514, 2.495, 2.476, 2.454, 2.431, 2.41, 2.389, 2.368, 2.347, 2.329, 2.312, 2.298, 2.286, 2.281, 2.281, 2.286, 2.297, 2.308, 2.319, 2.333, 2.352, 2.372, 2.392, 2.412, 2.432, 2.45, 2.467, 2.482, 2.493, 2.504, + 2.542, 2.529, 2.516, 2.498, 2.479, 2.458, 2.437, 2.416, 2.395, 2.376, 2.356, 2.339, 2.324, 2.31, 2.295, 2.289, 2.289, 2.294, 2.305, 2.317, 2.329, 2.344, 2.361, 2.379, 2.398, 2.418, 2.436, 2.454, 2.47, 2.485, 2.495, 2.505, + 2.543, 2.531, 2.518, 2.501, 2.483, 2.463, 2.442, 2.422, 2.402, 2.383, 2.364, 2.349, 2.336, 2.321, 2.305, 2.297, 2.297, 2.303, 2.313, 2.325, 2.339, 2.354, 2.37, 2.386, 2.405, 2.423, 2.441, 2.459, 2.473, 2.487, 2.496, 2.506, + 2.545, 2.533, 2.521, 2.504, 2.488, 2.468, 2.448, 2.429, 2.41, 2.392, 2.375, 2.36, 2.347, 2.332, 2.316, 2.309, 2.309, 2.313, 2.323, 2.335, 2.35, 2.365, 2.379, 2.394, 2.412, 2.43, 2.447, 2.463, 2.477, 2.49, 2.498, 2.506, + 2.547, 2.535, 2.524, 2.508, 2.492, 2.474, 2.456, 2.438, 2.419, 2.403, 2.388, 2.372, 2.357, 2.343, 2.33, 2.324, 2.324, 2.328, 2.335, 2.346, 2.361, 2.375, 2.389, 2.403, 2.42, 2.437, 2.453, 2.468, 2.481, 2.493, 2.5, 2.508, + 2.548, 2.538, 2.527, 2.512, 2.497, 2.481, 2.464, 2.446, 2.428, 2.414, 2.401, 2.384, 2.367, 2.354, 2.344, 2.339, 2.339, 2.342, 2.348, 2.357, 2.371, 2.386, 2.399, 2.413, 2.429, 2.444, 2.459, 2.473, 2.485, 2.496, 2.502, 2.509, + 2.55, 2.539, 2.529, 2.515, 2.501, 2.486, 2.47, 2.454, 2.438, 2.425, 2.411, 2.396, 2.379, 2.367, 2.359, 2.355, 2.355, 2.357, 2.361, 2.369, 2.382, 2.396, 2.409, 2.423, 2.437, 2.451, 2.464, 2.476, 2.487, 2.498, 2.504, 2.509, + 2.551, 2.541, 2.531, 2.518, 2.505, 2.49, 2.476, 2.463, 2.449, 2.435, 2.421, 2.406, 2.392, 2.381, 2.374, 2.371, 2.371, 2.372, 2.376, 2.382, 2.393, 2.406, 2.42, 2.434, 2.446, 2.458, 2.469, 2.479, 2.489, 2.499, 2.504, 2.51, + 2.552, 2.542, 2.532, 2.52, 2.508, 2.495, 2.482, 2.471, 2.46, 2.446, 2.43, 2.417, 2.404, 2.396, 2.389, 2.386, 2.386, 2.387, 2.39, 2.395, 2.404, 2.415, 2.431, 2.445, 2.455, 2.465, 2.473, 2.482, 2.491, 2.499, 2.505, 2.511, + 2.552, 2.543, 2.533, 2.521, 2.509, 2.497, 2.486, 2.476, 2.466, 2.453, 2.439, 2.427, 2.415, 2.407, 2.403, 2.401, 2.401, 2.401, 2.403, 2.407, 2.415, 2.425, 2.439, 2.452, 2.462, 2.471, 2.478, 2.485, 2.493, 2.501, 2.506, 2.511, + 2.553, 2.543, 2.533, 2.521, 2.509, 2.499, 2.488, 2.48, 2.471, 2.46, 2.448, 2.436, 2.424, 2.418, 2.416, 2.415, 2.415, 2.415, 2.416, 2.419, 2.425, 2.434, 2.447, 2.459, 2.468, 2.477, 2.483, 2.489, 2.496, 2.503, 2.507, 2.511, + 2.553, 2.543, 2.534, 2.522, 2.51, 2.5, 2.491, 2.484, 2.477, 2.468, 2.457, 2.446, 2.434, 2.429, 2.429, 2.429, 2.429, 2.429, 2.429, 2.431, 2.436, 2.443, 2.454, 2.465, 2.474, 2.482, 2.487, 2.493, 2.499, 2.504, 2.508, 2.511 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.132, 3.129, 3.126, 3.121, 3.117, 3.111, 3.104, 3.101, 3.098, 3.095, 3.092, 3.09, 3.088, 3.087, 3.086, 3.087, 3.087, 3.089, 3.09, 3.091, 3.092, 3.094, 3.098, 3.103, 3.109, 3.114, 3.118, 3.122, 3.132, 3.141, 3.143, 3.144, + 3.138, 3.133, 3.128, 3.124, 3.119, 3.113, 3.106, 3.102, 3.099, 3.096, 3.094, 3.091, 3.089, 3.088, 3.087, 3.088, 3.089, 3.09, 3.091, 3.092, 3.094, 3.097, 3.101, 3.105, 3.11, 3.115, 3.12, 3.125, 3.134, 3.142, 3.145, 3.147, + 3.144, 3.137, 3.131, 3.126, 3.122, 3.115, 3.108, 3.104, 3.101, 3.098, 3.095, 3.093, 3.091, 3.089, 3.089, 3.089, 3.09, 3.09, 3.091, 3.093, 3.096, 3.1, 3.103, 3.107, 3.111, 3.116, 3.122, 3.128, 3.136, 3.143, 3.147, 3.15, + 3.15, 3.142, 3.134, 3.129, 3.124, 3.117, 3.11, 3.106, 3.102, 3.1, 3.097, 3.095, 3.093, 3.091, 3.09, 3.09, 3.091, 3.092, 3.092, 3.094, 3.099, 3.102, 3.105, 3.109, 3.113, 3.118, 3.124, 3.13, 3.138, 3.145, 3.149, 3.153, + 3.154, 3.147, 3.14, 3.133, 3.126, 3.12, 3.115, 3.11, 3.105, 3.102, 3.1, 3.098, 3.096, 3.095, 3.094, 3.094, 3.095, 3.096, 3.096, 3.098, 3.101, 3.105, 3.108, 3.112, 3.116, 3.121, 3.126, 3.132, 3.14, 3.148, 3.152, 3.156, + 3.158, 3.152, 3.146, 3.137, 3.129, 3.124, 3.119, 3.114, 3.108, 3.105, 3.102, 3.101, 3.099, 3.099, 3.098, 3.098, 3.099, 3.099, 3.1, 3.102, 3.104, 3.107, 3.111, 3.115, 3.119, 3.124, 3.129, 3.134, 3.143, 3.151, 3.154, 3.158, + 3.163, 3.157, 3.151, 3.142, 3.132, 3.127, 3.123, 3.117, 3.112, 3.108, 3.106, 3.105, 3.104, 3.103, 3.103, 3.103, 3.103, 3.104, 3.105, 3.106, 3.108, 3.11, 3.114, 3.118, 3.123, 3.127, 3.132, 3.137, 3.146, 3.154, 3.157, 3.161, + 3.168, 3.162, 3.155, 3.146, 3.137, 3.131, 3.126, 3.121, 3.117, 3.114, 3.112, 3.111, 3.109, 3.109, 3.109, 3.109, 3.109, 3.11, 3.11, 3.111, 3.113, 3.115, 3.118, 3.122, 3.126, 3.13, 3.135, 3.141, 3.149, 3.156, 3.16, 3.165, + 3.174, 3.167, 3.16, 3.151, 3.143, 3.136, 3.129, 3.125, 3.122, 3.12, 3.119, 3.117, 3.115, 3.115, 3.115, 3.115, 3.115, 3.116, 3.116, 3.117, 3.119, 3.12, 3.122, 3.125, 3.129, 3.134, 3.139, 3.145, 3.152, 3.158, 3.164, 3.169, + 3.177, 3.171, 3.164, 3.156, 3.148, 3.14, 3.133, 3.13, 3.128, 3.127, 3.126, 3.124, 3.122, 3.122, 3.122, 3.122, 3.122, 3.123, 3.123, 3.124, 3.125, 3.126, 3.127, 3.129, 3.133, 3.138, 3.144, 3.15, 3.156, 3.162, 3.167, 3.173, + 3.18, 3.175, 3.17, 3.161, 3.152, 3.145, 3.139, 3.136, 3.135, 3.134, 3.133, 3.132, 3.13, 3.13, 3.13, 3.131, 3.131, 3.131, 3.131, 3.131, 3.132, 3.133, 3.133, 3.135, 3.138, 3.142, 3.149, 3.155, 3.16, 3.166, 3.171, 3.175, + 3.182, 3.179, 3.175, 3.166, 3.157, 3.15, 3.144, 3.142, 3.141, 3.141, 3.141, 3.14, 3.138, 3.137, 3.138, 3.139, 3.139, 3.139, 3.139, 3.139, 3.139, 3.139, 3.139, 3.14, 3.143, 3.146, 3.153, 3.16, 3.165, 3.17, 3.174, 3.178, + 3.185, 3.181, 3.178, 3.169, 3.16, 3.154, 3.148, 3.147, 3.146, 3.146, 3.147, 3.146, 3.145, 3.145, 3.146, 3.147, 3.147, 3.147, 3.147, 3.147, 3.147, 3.147, 3.146, 3.147, 3.149, 3.152, 3.158, 3.164, 3.169, 3.173, 3.177, 3.181, + 3.187, 3.184, 3.18, 3.172, 3.163, 3.158, 3.153, 3.152, 3.151, 3.151, 3.151, 3.151, 3.151, 3.152, 3.154, 3.154, 3.154, 3.154, 3.154, 3.154, 3.154, 3.154, 3.154, 3.154, 3.155, 3.157, 3.162, 3.167, 3.171, 3.176, 3.18, 3.184, + 3.189, 3.186, 3.183, 3.175, 3.166, 3.161, 3.157, 3.156, 3.156, 3.156, 3.156, 3.157, 3.158, 3.159, 3.161, 3.162, 3.162, 3.162, 3.162, 3.162, 3.162, 3.162, 3.161, 3.161, 3.162, 3.163, 3.166, 3.169, 3.174, 3.179, 3.183, 3.187, + 3.192, 3.189, 3.185, 3.177, 3.168, 3.164, 3.16, 3.159, 3.159, 3.159, 3.16, 3.161, 3.162, 3.164, 3.165, 3.166, 3.166, 3.166, 3.166, 3.166, 3.166, 3.165, 3.164, 3.164, 3.164, 3.166, 3.168, 3.172, 3.177, 3.182, 3.185, 3.188, + 3.196, 3.192, 3.187, 3.179, 3.17, 3.166, 3.163, 3.162, 3.162, 3.162, 3.163, 3.165, 3.166, 3.168, 3.169, 3.17, 3.17, 3.17, 3.169, 3.169, 3.169, 3.168, 3.167, 3.167, 3.167, 3.168, 3.171, 3.174, 3.179, 3.185, 3.186, 3.188, + 3.199, 3.194, 3.19, 3.181, 3.172, 3.169, 3.166, 3.165, 3.164, 3.165, 3.167, 3.168, 3.17, 3.172, 3.173, 3.173, 3.173, 3.173, 3.172, 3.172, 3.171, 3.171, 3.17, 3.169, 3.169, 3.17, 3.173, 3.176, 3.182, 3.187, 3.188, 3.189, + 3.202, 3.197, 3.192, 3.183, 3.175, 3.171, 3.168, 3.166, 3.165, 3.165, 3.167, 3.168, 3.17, 3.172, 3.173, 3.173, 3.173, 3.173, 3.172, 3.172, 3.171, 3.171, 3.17, 3.17, 3.17, 3.171, 3.174, 3.177, 3.183, 3.189, 3.19, 3.191, + 3.204, 3.199, 3.195, 3.186, 3.177, 3.173, 3.17, 3.168, 3.165, 3.166, 3.167, 3.168, 3.17, 3.172, 3.173, 3.173, 3.173, 3.173, 3.172, 3.172, 3.171, 3.171, 3.171, 3.171, 3.171, 3.172, 3.175, 3.177, 3.184, 3.191, 3.192, 3.193, + 3.206, 3.201, 3.196, 3.188, 3.178, 3.175, 3.172, 3.169, 3.166, 3.165, 3.166, 3.168, 3.169, 3.17, 3.171, 3.172, 3.172, 3.172, 3.171, 3.171, 3.171, 3.171, 3.171, 3.171, 3.172, 3.173, 3.176, 3.178, 3.185, 3.192, 3.193, 3.194, + 3.207, 3.202, 3.197, 3.188, 3.179, 3.175, 3.172, 3.169, 3.165, 3.164, 3.164, 3.165, 3.165, 3.166, 3.167, 3.168, 3.168, 3.168, 3.168, 3.169, 3.169, 3.169, 3.17, 3.171, 3.172, 3.174, 3.176, 3.18, 3.186, 3.193, 3.194, 3.196, + 3.208, 3.203, 3.197, 3.188, 3.179, 3.175, 3.172, 3.168, 3.165, 3.163, 3.162, 3.162, 3.161, 3.162, 3.163, 3.164, 3.164, 3.164, 3.165, 3.166, 3.167, 3.168, 3.17, 3.171, 3.172, 3.174, 3.177, 3.181, 3.187, 3.193, 3.195, 3.197, + 3.208, 3.203, 3.197, 3.188, 3.179, 3.174, 3.171, 3.168, 3.164, 3.162, 3.161, 3.16, 3.159, 3.159, 3.159, 3.16, 3.161, 3.162, 3.163, 3.164, 3.166, 3.167, 3.169, 3.171, 3.172, 3.174, 3.178, 3.182, 3.188, 3.194, 3.196, 3.198, + 3.206, 3.201, 3.196, 3.187, 3.178, 3.173, 3.169, 3.166, 3.163, 3.161, 3.159, 3.158, 3.157, 3.157, 3.157, 3.158, 3.16, 3.161, 3.162, 3.163, 3.164, 3.166, 3.168, 3.17, 3.172, 3.174, 3.178, 3.182, 3.189, 3.196, 3.197, 3.199, + 3.205, 3.2, 3.195, 3.186, 3.177, 3.172, 3.167, 3.164, 3.162, 3.16, 3.157, 3.156, 3.155, 3.155, 3.155, 3.156, 3.158, 3.16, 3.161, 3.162, 3.163, 3.165, 3.167, 3.169, 3.171, 3.174, 3.178, 3.183, 3.19, 3.197, 3.198, 3.199, + 3.203, 3.198, 3.194, 3.185, 3.177, 3.172, 3.167, 3.164, 3.162, 3.159, 3.157, 3.155, 3.154, 3.154, 3.154, 3.155, 3.157, 3.159, 3.16, 3.162, 3.163, 3.165, 3.167, 3.169, 3.171, 3.174, 3.179, 3.184, 3.191, 3.198, 3.198, 3.199, + 3.201, 3.197, 3.193, 3.185, 3.177, 3.172, 3.168, 3.165, 3.162, 3.159, 3.157, 3.156, 3.154, 3.153, 3.153, 3.154, 3.156, 3.158, 3.16, 3.162, 3.163, 3.165, 3.167, 3.169, 3.171, 3.174, 3.18, 3.185, 3.191, 3.197, 3.198, 3.199, + 3.199, 3.195, 3.191, 3.184, 3.177, 3.173, 3.169, 3.166, 3.162, 3.16, 3.158, 3.156, 3.154, 3.153, 3.153, 3.154, 3.155, 3.157, 3.16, 3.162, 3.163, 3.165, 3.167, 3.169, 3.171, 3.174, 3.18, 3.186, 3.191, 3.196, 3.198, 3.199, + 3.199, 3.195, 3.19, 3.184, 3.178, 3.174, 3.171, 3.167, 3.163, 3.16, 3.158, 3.156, 3.154, 3.153, 3.153, 3.154, 3.155, 3.157, 3.159, 3.161, 3.163, 3.166, 3.167, 3.17, 3.172, 3.175, 3.181, 3.186, 3.191, 3.195, 3.197, 3.199, + 3.199, 3.194, 3.189, 3.184, 3.179, 3.175, 3.172, 3.168, 3.165, 3.161, 3.158, 3.156, 3.154, 3.153, 3.153, 3.154, 3.155, 3.157, 3.159, 3.161, 3.164, 3.167, 3.169, 3.171, 3.173, 3.176, 3.181, 3.186, 3.19, 3.194, 3.196, 3.198, + 3.199, 3.194, 3.188, 3.184, 3.18, 3.176, 3.174, 3.17, 3.166, 3.162, 3.158, 3.156, 3.154, 3.153, 3.154, 3.155, 3.155, 3.157, 3.158, 3.161, 3.164, 3.168, 3.17, 3.172, 3.174, 3.177, 3.181, 3.186, 3.189, 3.193, 3.196, 3.198 + ] + }, + { + "ct": 6000, + "table": + [ + 1.579, 1.579, 1.579, 1.578, 1.577, 1.576, 1.574, 1.574, 1.573, 1.572, 1.571, 1.571, 1.571, 1.571, 1.571, 1.571, 1.571, 1.571, 1.57, 1.569, 1.569, 1.569, 1.57, 1.571, 1.572, 1.572, 1.573, 1.574, 1.576, 1.577, 1.578, 1.578, + 1.581, 1.58, 1.579, 1.578, 1.577, 1.576, 1.575, 1.574, 1.573, 1.572, 1.572, 1.571, 1.571, 1.571, 1.571, 1.571, 1.571, 1.571, 1.57, 1.57, 1.57, 1.57, 1.571, 1.571, 1.572, 1.573, 1.574, 1.575, 1.576, 1.577, 1.578, 1.578, + 1.583, 1.581, 1.579, 1.578, 1.578, 1.576, 1.575, 1.574, 1.573, 1.573, 1.572, 1.571, 1.571, 1.571, 1.572, 1.572, 1.572, 1.571, 1.571, 1.57, 1.57, 1.571, 1.571, 1.572, 1.572, 1.573, 1.574, 1.576, 1.577, 1.578, 1.578, 1.579, + 1.584, 1.582, 1.579, 1.579, 1.578, 1.577, 1.575, 1.574, 1.573, 1.573, 1.572, 1.572, 1.571, 1.571, 1.572, 1.572, 1.572, 1.572, 1.571, 1.571, 1.571, 1.571, 1.572, 1.572, 1.573, 1.573, 1.575, 1.576, 1.577, 1.578, 1.579, 1.579, + 1.585, 1.583, 1.581, 1.58, 1.579, 1.578, 1.576, 1.575, 1.574, 1.573, 1.573, 1.572, 1.572, 1.572, 1.573, 1.573, 1.573, 1.573, 1.573, 1.572, 1.572, 1.572, 1.572, 1.573, 1.574, 1.575, 1.576, 1.577, 1.578, 1.579, 1.58, 1.58, + 1.586, 1.585, 1.583, 1.581, 1.579, 1.578, 1.577, 1.576, 1.575, 1.574, 1.573, 1.573, 1.573, 1.573, 1.574, 1.574, 1.574, 1.574, 1.574, 1.573, 1.573, 1.573, 1.573, 1.574, 1.575, 1.576, 1.577, 1.578, 1.579, 1.58, 1.58, 1.581, + 1.588, 1.586, 1.584, 1.582, 1.58, 1.579, 1.578, 1.577, 1.576, 1.575, 1.574, 1.574, 1.574, 1.574, 1.575, 1.576, 1.576, 1.576, 1.575, 1.575, 1.574, 1.574, 1.574, 1.575, 1.576, 1.576, 1.577, 1.579, 1.58, 1.582, 1.582, 1.582, + 1.589, 1.587, 1.586, 1.584, 1.582, 1.58, 1.579, 1.578, 1.577, 1.576, 1.576, 1.576, 1.576, 1.576, 1.577, 1.578, 1.578, 1.578, 1.578, 1.577, 1.576, 1.575, 1.575, 1.576, 1.576, 1.577, 1.578, 1.58, 1.581, 1.583, 1.583, 1.583, + 1.59, 1.588, 1.587, 1.585, 1.583, 1.581, 1.579, 1.578, 1.578, 1.578, 1.578, 1.578, 1.578, 1.579, 1.58, 1.58, 1.58, 1.58, 1.58, 1.579, 1.578, 1.577, 1.577, 1.577, 1.577, 1.578, 1.579, 1.581, 1.583, 1.584, 1.585, 1.585, + 1.592, 1.59, 1.588, 1.586, 1.585, 1.583, 1.581, 1.58, 1.579, 1.58, 1.58, 1.58, 1.581, 1.581, 1.582, 1.582, 1.582, 1.582, 1.582, 1.582, 1.58, 1.579, 1.579, 1.578, 1.579, 1.579, 1.581, 1.582, 1.584, 1.586, 1.586, 1.587, + 1.593, 1.591, 1.589, 1.588, 1.586, 1.584, 1.583, 1.582, 1.582, 1.582, 1.583, 1.583, 1.583, 1.584, 1.584, 1.584, 1.585, 1.585, 1.585, 1.584, 1.583, 1.582, 1.581, 1.581, 1.581, 1.582, 1.583, 1.584, 1.586, 1.587, 1.587, 1.588, + 1.595, 1.593, 1.591, 1.589, 1.587, 1.586, 1.585, 1.584, 1.584, 1.585, 1.585, 1.586, 1.586, 1.586, 1.586, 1.587, 1.587, 1.587, 1.587, 1.587, 1.585, 1.584, 1.584, 1.583, 1.583, 1.584, 1.585, 1.586, 1.587, 1.589, 1.589, 1.589, + 1.596, 1.594, 1.592, 1.59, 1.588, 1.587, 1.586, 1.586, 1.586, 1.587, 1.588, 1.588, 1.589, 1.589, 1.589, 1.59, 1.59, 1.59, 1.59, 1.59, 1.588, 1.587, 1.587, 1.587, 1.586, 1.586, 1.587, 1.588, 1.589, 1.59, 1.59, 1.59, + 1.596, 1.595, 1.594, 1.592, 1.59, 1.589, 1.588, 1.588, 1.589, 1.589, 1.59, 1.591, 1.592, 1.592, 1.592, 1.593, 1.593, 1.594, 1.594, 1.593, 1.592, 1.591, 1.59, 1.59, 1.589, 1.589, 1.589, 1.59, 1.591, 1.591, 1.591, 1.591, + 1.597, 1.596, 1.595, 1.593, 1.591, 1.59, 1.589, 1.59, 1.591, 1.592, 1.592, 1.593, 1.594, 1.595, 1.595, 1.596, 1.596, 1.597, 1.597, 1.596, 1.595, 1.595, 1.594, 1.593, 1.592, 1.592, 1.592, 1.592, 1.592, 1.593, 1.593, 1.593, + 1.598, 1.597, 1.596, 1.594, 1.592, 1.591, 1.59, 1.591, 1.591, 1.592, 1.593, 1.594, 1.596, 1.596, 1.597, 1.597, 1.598, 1.599, 1.598, 1.598, 1.597, 1.596, 1.595, 1.594, 1.594, 1.593, 1.593, 1.593, 1.593, 1.594, 1.594, 1.594, + 1.6, 1.598, 1.596, 1.595, 1.593, 1.592, 1.591, 1.592, 1.592, 1.593, 1.594, 1.595, 1.597, 1.597, 1.598, 1.599, 1.6, 1.6, 1.6, 1.599, 1.598, 1.597, 1.596, 1.595, 1.595, 1.594, 1.594, 1.595, 1.595, 1.594, 1.594, 1.594, + 1.601, 1.599, 1.597, 1.595, 1.593, 1.593, 1.592, 1.592, 1.593, 1.594, 1.595, 1.596, 1.597, 1.598, 1.599, 1.6, 1.601, 1.602, 1.601, 1.6, 1.599, 1.598, 1.597, 1.596, 1.595, 1.595, 1.596, 1.596, 1.596, 1.595, 1.595, 1.595, + 1.601, 1.599, 1.598, 1.596, 1.594, 1.593, 1.592, 1.593, 1.593, 1.594, 1.595, 1.596, 1.597, 1.598, 1.599, 1.6, 1.601, 1.602, 1.601, 1.6, 1.599, 1.598, 1.597, 1.596, 1.596, 1.596, 1.596, 1.596, 1.596, 1.596, 1.596, 1.596, + 1.601, 1.6, 1.599, 1.596, 1.594, 1.593, 1.593, 1.593, 1.593, 1.594, 1.595, 1.596, 1.597, 1.598, 1.599, 1.6, 1.601, 1.602, 1.601, 1.6, 1.599, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.596, 1.596, 1.596, + 1.601, 1.6, 1.599, 1.597, 1.594, 1.594, 1.593, 1.593, 1.593, 1.594, 1.594, 1.596, 1.597, 1.598, 1.599, 1.6, 1.601, 1.601, 1.601, 1.6, 1.599, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, + 1.601, 1.6, 1.599, 1.597, 1.594, 1.594, 1.593, 1.593, 1.593, 1.593, 1.594, 1.595, 1.596, 1.597, 1.598, 1.599, 1.599, 1.6, 1.6, 1.599, 1.599, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, + 1.602, 1.6, 1.599, 1.597, 1.594, 1.594, 1.593, 1.593, 1.592, 1.593, 1.593, 1.594, 1.595, 1.596, 1.597, 1.598, 1.598, 1.598, 1.598, 1.598, 1.598, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.598, + 1.602, 1.6, 1.599, 1.597, 1.594, 1.594, 1.593, 1.592, 1.592, 1.592, 1.593, 1.593, 1.594, 1.595, 1.596, 1.597, 1.597, 1.597, 1.598, 1.598, 1.598, 1.598, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.598, 1.598, + 1.6, 1.599, 1.599, 1.596, 1.594, 1.593, 1.593, 1.592, 1.592, 1.592, 1.592, 1.592, 1.593, 1.594, 1.595, 1.596, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.598, 1.599, 1.599, 1.599, + 1.599, 1.599, 1.598, 1.596, 1.594, 1.593, 1.592, 1.592, 1.591, 1.591, 1.591, 1.592, 1.592, 1.593, 1.595, 1.595, 1.596, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.599, 1.598, 1.598, 1.596, 1.594, 1.593, 1.592, 1.592, 1.591, 1.591, 1.591, 1.591, 1.592, 1.593, 1.594, 1.595, 1.596, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.598, 1.598, 1.597, 1.596, 1.594, 1.593, 1.593, 1.592, 1.592, 1.592, 1.592, 1.592, 1.592, 1.593, 1.594, 1.595, 1.595, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.598, 1.597, 1.596, 1.595, 1.594, 1.594, 1.593, 1.593, 1.592, 1.592, 1.592, 1.592, 1.592, 1.593, 1.594, 1.594, 1.595, 1.596, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.598, 1.597, 1.596, 1.595, 1.594, 1.594, 1.593, 1.593, 1.592, 1.592, 1.592, 1.592, 1.592, 1.593, 1.594, 1.594, 1.595, 1.595, 1.596, 1.597, 1.597, 1.597, 1.597, 1.597, 1.598, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.597, 1.596, 1.595, 1.595, 1.594, 1.594, 1.594, 1.593, 1.593, 1.592, 1.592, 1.592, 1.593, 1.593, 1.594, 1.595, 1.595, 1.595, 1.596, 1.597, 1.597, 1.598, 1.598, 1.598, 1.598, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, 1.599, + 1.597, 1.596, 1.595, 1.595, 1.594, 1.594, 1.594, 1.594, 1.593, 1.593, 1.592, 1.592, 1.593, 1.594, 1.595, 1.595, 1.595, 1.595, 1.596, 1.597, 1.598, 1.598, 1.598, 1.598, 1.598, 1.598, 1.599, 1.599, 1.599, 1.599, 1.599, 1.599 + ] + } + ], + "luminance_lut": + [ + 2.887, 2.823, 2.758, 2.586, 2.405, 2.265, 2.132, 2.01, 1.891, 1.795, 1.707, 1.661, 1.635, 1.624, 1.623, 1.623, 1.623, 1.623, 1.624, 1.633, 1.654, 1.698, 1.785, 1.88, 1.998, 2.118, 2.249, 2.385, 2.56, 2.727, 2.782, 2.838, + 2.84, 2.745, 2.65, 2.482, 2.308, 2.18, 2.058, 1.941, 1.826, 1.736, 1.656, 1.609, 1.577, 1.56, 1.552, 1.548, 1.548, 1.551, 1.559, 1.574, 1.603, 1.648, 1.726, 1.814, 1.929, 2.045, 2.164, 2.29, 2.457, 2.619, 2.708, 2.797, + 2.793, 2.667, 2.542, 2.378, 2.212, 2.094, 1.985, 1.873, 1.761, 1.678, 1.606, 1.557, 1.519, 1.495, 1.48, 1.473, 1.473, 1.48, 1.494, 1.516, 1.551, 1.597, 1.667, 1.748, 1.861, 1.972, 2.08, 2.195, 2.354, 2.511, 2.634, 2.756, + 2.734, 2.586, 2.438, 2.279, 2.119, 2.011, 1.91, 1.805, 1.697, 1.62, 1.553, 1.503, 1.461, 1.432, 1.411, 1.401, 1.401, 1.41, 1.43, 1.457, 1.497, 1.545, 1.609, 1.685, 1.792, 1.898, 1.997, 2.103, 2.256, 2.409, 2.556, 2.703, + 2.624, 2.49, 2.357, 2.203, 2.048, 1.936, 1.831, 1.736, 1.644, 1.566, 1.495, 1.443, 1.402, 1.372, 1.352, 1.342, 1.342, 1.351, 1.37, 1.397, 1.437, 1.486, 1.556, 1.632, 1.724, 1.818, 1.922, 2.032, 2.181, 2.33, 2.461, 2.593, + 2.513, 2.394, 2.275, 2.127, 1.976, 1.861, 1.751, 1.667, 1.59, 1.513, 1.436, 1.383, 1.342, 1.313, 1.292, 1.283, 1.283, 1.291, 1.31, 1.337, 1.376, 1.427, 1.503, 1.579, 1.655, 1.738, 1.846, 1.96, 2.106, 2.25, 2.367, 2.483, + 2.427, 2.315, 2.203, 2.058, 1.912, 1.795, 1.683, 1.604, 1.534, 1.461, 1.385, 1.332, 1.289, 1.259, 1.238, 1.228, 1.228, 1.237, 1.255, 1.282, 1.323, 1.376, 1.451, 1.524, 1.592, 1.669, 1.78, 1.895, 2.039, 2.18, 2.288, 2.396, + 2.383, 2.265, 2.147, 2.004, 1.859, 1.744, 1.634, 1.552, 1.477, 1.411, 1.349, 1.295, 1.245, 1.213, 1.191, 1.181, 1.181, 1.19, 1.208, 1.238, 1.286, 1.339, 1.401, 1.467, 1.54, 1.62, 1.73, 1.843, 1.984, 2.124, 2.236, 2.348, + 2.338, 2.215, 2.091, 1.949, 1.807, 1.694, 1.585, 1.499, 1.42, 1.362, 1.313, 1.259, 1.202, 1.167, 1.145, 1.134, 1.134, 1.143, 1.161, 1.194, 1.248, 1.301, 1.352, 1.41, 1.487, 1.571, 1.679, 1.791, 1.93, 2.067, 2.184, 2.3, + 2.307, 2.176, 2.046, 1.906, 1.765, 1.653, 1.545, 1.458, 1.377, 1.321, 1.274, 1.223, 1.17, 1.133, 1.107, 1.095, 1.094, 1.105, 1.127, 1.161, 1.212, 1.262, 1.31, 1.366, 1.446, 1.531, 1.638, 1.749, 1.886, 2.022, 2.145, 2.267, + 2.286, 2.147, 2.009, 1.87, 1.732, 1.62, 1.514, 1.427, 1.345, 1.285, 1.232, 1.187, 1.147, 1.11, 1.077, 1.061, 1.06, 1.074, 1.104, 1.137, 1.178, 1.222, 1.273, 1.333, 1.413, 1.499, 1.605, 1.716, 1.851, 1.986, 2.117, 2.247, + 2.265, 2.119, 1.973, 1.835, 1.698, 1.588, 1.482, 1.395, 1.313, 1.249, 1.19, 1.152, 1.123, 1.087, 1.046, 1.027, 1.027, 1.043, 1.08, 1.114, 1.143, 1.181, 1.237, 1.3, 1.381, 1.467, 1.573, 1.682, 1.816, 1.95, 2.089, 2.227, + 2.258, 2.104, 1.95, 1.813, 1.677, 1.567, 1.462, 1.375, 1.293, 1.227, 1.167, 1.127, 1.096, 1.065, 1.032, 1.016, 1.014, 1.028, 1.058, 1.088, 1.117, 1.157, 1.215, 1.28, 1.361, 1.447, 1.552, 1.661, 1.794, 1.928, 2.075, 2.222, + 2.258, 2.095, 1.933, 1.795, 1.66, 1.551, 1.446, 1.36, 1.278, 1.211, 1.15, 1.105, 1.068, 1.042, 1.023, 1.013, 1.011, 1.018, 1.036, 1.06, 1.095, 1.139, 1.199, 1.265, 1.346, 1.432, 1.536, 1.644, 1.777, 1.912, 2.067, 2.222, + 2.257, 2.086, 1.915, 1.778, 1.643, 1.535, 1.43, 1.344, 1.262, 1.195, 1.133, 1.083, 1.039, 1.019, 1.014, 1.01, 1.007, 1.008, 1.013, 1.033, 1.073, 1.12, 1.183, 1.25, 1.331, 1.417, 1.52, 1.627, 1.76, 1.895, 2.059, 2.222, + 2.257, 2.085, 1.913, 1.776, 1.642, 1.533, 1.429, 1.343, 1.261, 1.194, 1.132, 1.081, 1.036, 1.015, 1.01, 1.007, 1.005, 1.006, 1.009, 1.028, 1.07, 1.119, 1.181, 1.249, 1.33, 1.416, 1.519, 1.626, 1.759, 1.894, 2.058, 2.222, + 2.257, 2.085, 1.913, 1.776, 1.642, 1.533, 1.429, 1.343, 1.261, 1.194, 1.132, 1.081, 1.035, 1.013, 1.007, 1.004, 1.003, 1.004, 1.007, 1.026, 1.069, 1.119, 1.181, 1.249, 1.33, 1.416, 1.519, 1.626, 1.759, 1.894, 2.058, 2.222, + 2.257, 2.086, 1.915, 1.778, 1.643, 1.535, 1.43, 1.344, 1.262, 1.195, 1.133, 1.082, 1.037, 1.014, 1.005, 1.001, 1.001, 1.003, 1.007, 1.027, 1.07, 1.12, 1.183, 1.25, 1.331, 1.417, 1.52, 1.627, 1.76, 1.896, 2.059, 2.222, + 2.257, 2.093, 1.93, 1.793, 1.658, 1.549, 1.444, 1.358, 1.277, 1.21, 1.148, 1.101, 1.062, 1.034, 1.015, 1.005, 1.004, 1.012, 1.029, 1.054, 1.091, 1.136, 1.198, 1.265, 1.346, 1.432, 1.536, 1.644, 1.778, 1.913, 2.068, 2.224, + 2.257, 2.101, 1.945, 1.808, 1.673, 1.564, 1.459, 1.373, 1.292, 1.225, 1.163, 1.12, 1.086, 1.055, 1.024, 1.009, 1.007, 1.02, 1.05, 1.08, 1.111, 1.152, 1.213, 1.28, 1.361, 1.447, 1.552, 1.66, 1.795, 1.931, 2.078, 2.225, + 2.262, 2.114, 1.965, 1.829, 1.693, 1.583, 1.477, 1.391, 1.311, 1.244, 1.184, 1.142, 1.111, 1.076, 1.038, 1.02, 1.017, 1.034, 1.072, 1.106, 1.135, 1.174, 1.234, 1.299, 1.38, 1.466, 1.572, 1.681, 1.817, 1.953, 2.092, 2.232, + 2.28, 2.14, 2.0, 1.862, 1.725, 1.614, 1.507, 1.421, 1.34, 1.276, 1.22, 1.175, 1.136, 1.099, 1.065, 1.048, 1.047, 1.062, 1.094, 1.13, 1.17, 1.215, 1.268, 1.329, 1.411, 1.498, 1.604, 1.714, 1.851, 1.988, 2.12, 2.252, + 2.299, 2.166, 2.034, 1.896, 1.757, 1.645, 1.537, 1.45, 1.369, 1.309, 1.256, 1.207, 1.16, 1.123, 1.092, 1.077, 1.077, 1.089, 1.116, 1.153, 1.206, 1.256, 1.303, 1.359, 1.442, 1.529, 1.636, 1.747, 1.886, 2.024, 2.148, 2.273, + 2.328, 2.203, 2.078, 1.938, 1.797, 1.684, 1.575, 1.489, 1.409, 1.348, 1.294, 1.242, 1.191, 1.153, 1.126, 1.113, 1.113, 1.124, 1.148, 1.185, 1.241, 1.295, 1.344, 1.402, 1.481, 1.567, 1.675, 1.788, 1.929, 2.068, 2.186, 2.305, + 2.369, 2.251, 2.133, 1.991, 1.847, 1.732, 1.621, 1.539, 1.464, 1.397, 1.334, 1.279, 1.228, 1.193, 1.167, 1.155, 1.155, 1.167, 1.191, 1.226, 1.277, 1.331, 1.393, 1.459, 1.533, 1.614, 1.724, 1.838, 1.982, 2.123, 2.237, 2.351, + 2.41, 2.299, 2.188, 2.044, 1.897, 1.78, 1.668, 1.589, 1.518, 1.446, 1.374, 1.316, 1.266, 1.232, 1.209, 1.198, 1.198, 1.21, 1.234, 1.267, 1.312, 1.367, 1.443, 1.516, 1.584, 1.661, 1.773, 1.889, 2.035, 2.178, 2.287, 2.396, + 2.493, 2.375, 2.258, 2.11, 1.96, 1.845, 1.735, 1.65, 1.572, 1.497, 1.423, 1.365, 1.315, 1.282, 1.26, 1.25, 1.25, 1.261, 1.286, 1.318, 1.362, 1.417, 1.494, 1.57, 1.646, 1.729, 1.838, 1.953, 2.102, 2.248, 2.365, 2.483, + 2.599, 2.467, 2.335, 2.183, 2.03, 1.919, 1.814, 1.719, 1.627, 1.549, 1.478, 1.421, 1.372, 1.339, 1.317, 1.306, 1.306, 1.318, 1.343, 1.376, 1.421, 1.475, 1.546, 1.623, 1.715, 1.809, 1.914, 2.025, 2.176, 2.326, 2.459, 2.592, + 2.705, 2.559, 2.413, 2.256, 2.099, 1.992, 1.893, 1.788, 1.681, 1.602, 1.532, 1.477, 1.428, 1.395, 1.373, 1.363, 1.363, 1.374, 1.4, 1.433, 1.479, 1.532, 1.598, 1.675, 1.783, 1.89, 1.99, 2.097, 2.251, 2.405, 2.553, 2.702, + 2.763, 2.639, 2.514, 2.353, 2.19, 2.075, 1.967, 1.856, 1.744, 1.659, 1.584, 1.529, 1.484, 1.456, 1.44, 1.432, 1.432, 1.441, 1.459, 1.487, 1.53, 1.583, 1.655, 1.738, 1.852, 1.965, 2.073, 2.189, 2.35, 2.508, 2.632, 2.757, + 2.81, 2.716, 2.621, 2.456, 2.285, 2.158, 2.039, 1.923, 1.809, 1.718, 1.636, 1.581, 1.54, 1.518, 1.509, 1.505, 1.505, 1.509, 1.519, 1.54, 1.58, 1.633, 1.714, 1.804, 1.92, 2.038, 2.158, 2.286, 2.454, 2.617, 2.708, 2.799, + 2.858, 2.793, 2.728, 2.558, 2.38, 2.242, 2.111, 1.991, 1.873, 1.777, 1.688, 1.633, 1.596, 1.58, 1.578, 1.577, 1.577, 1.577, 1.578, 1.593, 1.629, 1.683, 1.772, 1.87, 1.989, 2.111, 2.244, 2.382, 2.558, 2.726, 2.784, 2.842 + ], + "sigma": 0.00372, + "sigma_Cb": 0.00244 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2890, + "ccm": + [ + 1.36754, -0.18448, -0.18306, + -0.32356, 1.44826, -0.12471, + -0.00412, -0.69936, 1.70348 + ] + }, + { + "ct": 2920, + "ccm": + [ + 1.26704, 0.01624, -0.28328, + -0.28516, 1.38934, -0.10419, + -0.04854, -0.82211, 1.87066 + ] + }, + { + "ct": 3550, + "ccm": + [ + 1.42836, -0.27235, -0.15601, + -0.28751, 1.41075, -0.12325, + -0.01812, -0.54849, 1.56661 + ] + }, + { + "ct": 4500, + "ccm": + [ + 1.36328, -0.19569, -0.16759, + -0.25254, 1.52248, -0.26994, + -0.01575, -0.53155, 1.54729 + ] + }, + { + "ct": 5700, + "ccm": + [ + 1.49207, -0.37245, -0.11963, + -0.21493, 1.40005, -0.18512, + -0.03781, -0.38779, 1.42561 + ] + }, + { + "ct": 7900, + "ccm": + [ + 1.34849, -0.05425, -0.29424, + -0.22182, 1.77684, -0.55502, + -0.07403, -0.55336, 1.62739 + ] + } + ] + } + }, + { + "rpi.sharpen": { } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx708.json b/src/ipa/rpi/pisp/data/imx708.json new file mode 100644 index 00000000..e8d25c21 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx708.json @@ -0,0 +1,1270 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 20716, + "reference_gain": 1.12, + "reference_aperture": 1.0, + "reference_lux": 810, + "reference_Y": 13994 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 1.856 + } + }, + { + "rpi.geq": + { + "offset": 221, + "slope": 0.00226 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 3.2, + "strength": 0.75, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2964.0, 0.7451, 0.3213, + 3610.0, 0.6119, 0.4443, + 4640.0, 0.5168, 0.5419, + 5910.0, 0.4436, 0.6229, + 7590.0, 0.3847, 0.6921 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01752, + "transverse_neg": 0.01831 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.532, 1.534, 1.535, 1.538, 1.538, 1.533, 1.529, 1.515, 1.506, 1.492, 1.477, 1.465, 1.453, 1.444, 1.437, 1.433, 1.433, 1.435, 1.441, 1.449, 1.461, 1.474, 1.485, 1.499, 1.511, 1.519, 1.525, 1.526, 1.526, 1.523, 1.517, 1.516, + 1.532, 1.534, 1.537, 1.538, 1.537, 1.534, 1.525, 1.515, 1.502, 1.486, 1.474, 1.458, 1.449, 1.438, 1.429, 1.427, 1.426, 1.429, 1.436, 1.444, 1.456, 1.468, 1.483, 1.497, 1.509, 1.518, 1.524, 1.526, 1.526, 1.523, 1.521, 1.516, + 1.532, 1.534, 1.537, 1.538, 1.536, 1.533, 1.524, 1.512, 1.499, 1.483, 1.468, 1.453, 1.439, 1.429, 1.421, 1.419, 1.419, 1.419, 1.427, 1.438, 1.451, 1.464, 1.479, 1.494, 1.506, 1.516, 1.523, 1.526, 1.526, 1.524, 1.521, 1.518, + 1.533, 1.536, 1.537, 1.537, 1.535, 1.532, 1.521, 1.507, 1.491, 1.474, 1.456, 1.441, 1.429, 1.418, 1.409, 1.406, 1.406, 1.408, 1.415, 1.426, 1.439, 1.453, 1.471, 1.485, 1.501, 1.511, 1.522, 1.524, 1.526, 1.525, 1.522, 1.519, + 1.537, 1.538, 1.539, 1.538, 1.534, 1.525, 1.513, 1.495, 1.477, 1.459, 1.443, 1.427, 1.413, 1.402, 1.394, 1.391, 1.391, 1.393, 1.399, 1.409, 1.424, 1.439, 1.455, 1.472, 1.489, 1.503, 1.515, 1.523, 1.526, 1.527, 1.525, 1.523, + 1.538, 1.539, 1.541, 1.539, 1.531, 1.519, 1.503, 1.484, 1.466, 1.445, 1.427, 1.413, 1.401, 1.386, 1.378, 1.373, 1.373, 1.376, 1.386, 1.398, 1.409, 1.424, 1.441, 1.459, 1.477, 1.495, 1.509, 1.519, 1.526, 1.528, 1.528, 1.526, + 1.539, 1.541, 1.541, 1.539, 1.529, 1.516, 1.498, 1.479, 1.456, 1.437, 1.417, 1.401, 1.386, 1.378, 1.369, 1.363, 1.363, 1.367, 1.376, 1.386, 1.399, 1.413, 1.432, 1.451, 1.472, 1.491, 1.507, 1.517, 1.525, 1.527, 1.527, 1.527, + 1.539, 1.539, 1.539, 1.538, 1.529, 1.515, 1.497, 1.476, 1.454, 1.433, 1.411, 1.395, 1.381, 1.368, 1.361, 1.356, 1.356, 1.359, 1.367, 1.379, 1.393, 1.409, 1.428, 1.448, 1.471, 1.489, 1.505, 1.516, 1.524, 1.527, 1.527, 1.527, + 1.539, 1.539, 1.539, 1.537, 1.528, 1.513, 1.493, 1.471, 1.449, 1.426, 1.406, 1.387, 1.373, 1.361, 1.352, 1.348, 1.348, 1.351, 1.359, 1.372, 1.387, 1.403, 1.422, 1.443, 1.465, 1.484, 1.503, 1.516, 1.525, 1.527, 1.528, 1.526, + 1.541, 1.542, 1.539, 1.537, 1.524, 1.506, 1.485, 1.461, 1.438, 1.416, 1.395, 1.377, 1.362, 1.352, 1.344, 1.339, 1.339, 1.342, 1.351, 1.362, 1.376, 1.393, 1.412, 1.434, 1.455, 1.477, 1.495, 1.514, 1.524, 1.528, 1.529, 1.529, + 1.543, 1.544, 1.543, 1.534, 1.518, 1.499, 1.476, 1.452, 1.427, 1.405, 1.386, 1.367, 1.354, 1.344, 1.338, 1.329, 1.329, 1.335, 1.342, 1.352, 1.367, 1.382, 1.402, 1.424, 1.445, 1.469, 1.491, 1.507, 1.522, 1.528, 1.529, 1.532, + 1.544, 1.544, 1.542, 1.534, 1.518, 1.499, 1.474, 1.449, 1.425, 1.401, 1.379, 1.362, 1.348, 1.338, 1.329, 1.324, 1.325, 1.329, 1.335, 1.347, 1.361, 1.378, 1.397, 1.421, 1.443, 1.467, 1.489, 1.507, 1.521, 1.529, 1.532, 1.533, + 1.543, 1.543, 1.541, 1.534, 1.519, 1.499, 1.474, 1.448, 1.424, 1.399, 1.377, 1.359, 1.346, 1.333, 1.324, 1.322, 1.321, 1.324, 1.332, 1.344, 1.359, 1.376, 1.397, 1.419, 1.443, 1.467, 1.489, 1.508, 1.521, 1.528, 1.531, 1.532, + 1.543, 1.542, 1.541, 1.533, 1.519, 1.499, 1.474, 1.448, 1.422, 1.399, 1.376, 1.358, 1.344, 1.331, 1.322, 1.319, 1.319, 1.321, 1.331, 1.342, 1.357, 1.375, 1.396, 1.419, 1.443, 1.467, 1.489, 1.508, 1.521, 1.529, 1.531, 1.532, + 1.543, 1.542, 1.541, 1.532, 1.518, 1.496, 1.471, 1.445, 1.418, 1.393, 1.373, 1.354, 1.341, 1.329, 1.319, 1.317, 1.316, 1.319, 1.327, 1.338, 1.353, 1.371, 1.392, 1.415, 1.439, 1.465, 1.485, 1.507, 1.519, 1.529, 1.531, 1.531, + 1.545, 1.544, 1.542, 1.531, 1.515, 1.493, 1.467, 1.441, 1.414, 1.391, 1.369, 1.351, 1.337, 1.326, 1.318, 1.314, 1.314, 1.317, 1.325, 1.335, 1.351, 1.367, 1.388, 1.411, 1.436, 1.461, 1.483, 1.505, 1.519, 1.531, 1.533, 1.533, + 1.545, 1.544, 1.541, 1.531, 1.515, 1.493, 1.467, 1.441, 1.414, 1.391, 1.369, 1.351, 1.337, 1.326, 1.318, 1.314, 1.314, 1.317, 1.325, 1.335, 1.351, 1.367, 1.388, 1.411, 1.436, 1.461, 1.483, 1.505, 1.521, 1.531, 1.534, 1.534, + 1.545, 1.544, 1.541, 1.534, 1.519, 1.496, 1.471, 1.446, 1.419, 1.392, 1.372, 1.354, 1.338, 1.328, 1.319, 1.316, 1.315, 1.319, 1.327, 1.338, 1.353, 1.371, 1.392, 1.416, 1.441, 1.465, 1.489, 1.511, 1.522, 1.531, 1.534, 1.535, + 1.544, 1.544, 1.542, 1.537, 1.524, 1.501, 1.476, 1.449, 1.424, 1.399, 1.377, 1.359, 1.344, 1.332, 1.324, 1.319, 1.319, 1.323, 1.331, 1.343, 1.358, 1.374, 1.396, 1.419, 1.445, 1.471, 1.493, 1.512, 1.525, 1.532, 1.534, 1.534, + 1.545, 1.545, 1.543, 1.538, 1.524, 1.503, 1.479, 1.452, 1.426, 1.402, 1.381, 1.362, 1.348, 1.337, 1.329, 1.324, 1.324, 1.328, 1.335, 1.347, 1.361, 1.379, 1.399, 1.423, 1.447, 1.471, 1.493, 1.513, 1.526, 1.533, 1.534, 1.535, + 1.546, 1.546, 1.544, 1.539, 1.525, 1.504, 1.479, 1.453, 1.428, 1.404, 1.383, 1.365, 1.352, 1.339, 1.333, 1.329, 1.329, 1.333, 1.339, 1.349, 1.363, 1.381, 1.402, 1.424, 1.448, 1.472, 1.494, 1.514, 1.526, 1.534, 1.534, 1.534, + 1.546, 1.546, 1.544, 1.539, 1.526, 1.505, 1.483, 1.457, 1.432, 1.407, 1.389, 1.371, 1.357, 1.347, 1.339, 1.333, 1.333, 1.339, 1.345, 1.354, 1.368, 1.386, 1.406, 1.428, 1.453, 1.475, 1.496, 1.515, 1.527, 1.535, 1.535, 1.535, + 1.545, 1.545, 1.545, 1.541, 1.529, 1.513, 1.491, 1.467, 1.441, 1.418, 1.399, 1.379, 1.366, 1.355, 1.347, 1.341, 1.341, 1.345, 1.354, 1.364, 1.378, 1.395, 1.415, 1.436, 1.459, 1.483, 1.503, 1.519, 1.531, 1.534, 1.535, 1.534, + 1.544, 1.545, 1.545, 1.544, 1.535, 1.519, 1.499, 1.476, 1.451, 1.428, 1.409, 1.391, 1.377, 1.366, 1.356, 1.352, 1.352, 1.355, 1.364, 1.374, 1.388, 1.405, 1.426, 1.447, 1.469, 1.492, 1.509, 1.523, 1.532, 1.535, 1.535, 1.533, + 1.544, 1.545, 1.546, 1.545, 1.537, 1.523, 1.504, 1.482, 1.458, 1.436, 1.418, 1.401, 1.385, 1.377, 1.367, 1.362, 1.362, 1.365, 1.373, 1.385, 1.398, 1.415, 1.434, 1.455, 1.477, 1.495, 1.514, 1.525, 1.533, 1.536, 1.535, 1.533, + 1.545, 1.546, 1.547, 1.545, 1.538, 1.525, 1.508, 1.486, 1.465, 1.444, 1.424, 1.408, 1.394, 1.385, 1.377, 1.371, 1.371, 1.373, 1.384, 1.392, 1.405, 1.421, 1.441, 1.459, 1.481, 1.499, 1.516, 1.528, 1.534, 1.536, 1.536, 1.533, + 1.544, 1.546, 1.547, 1.547, 1.541, 1.531, 1.514, 1.494, 1.474, 1.454, 1.434, 1.421, 1.408, 1.394, 1.386, 1.382, 1.382, 1.385, 1.392, 1.405, 1.416, 1.432, 1.449, 1.468, 1.488, 1.505, 1.519, 1.531, 1.536, 1.537, 1.536, 1.533, + 1.544, 1.546, 1.548, 1.548, 1.545, 1.536, 1.522, 1.506, 1.486, 1.467, 1.451, 1.434, 1.421, 1.408, 1.401, 1.396, 1.396, 1.399, 1.407, 1.416, 1.431, 1.447, 1.463, 1.481, 1.499, 1.513, 1.526, 1.534, 1.537, 1.537, 1.534, 1.531, + 1.543, 1.545, 1.547, 1.549, 1.549, 1.543, 1.531, 1.517, 1.501, 1.483, 1.465, 1.451, 1.438, 1.425, 1.417, 1.412, 1.412, 1.418, 1.423, 1.433, 1.447, 1.462, 1.479, 1.493, 1.511, 1.524, 1.531, 1.536, 1.538, 1.537, 1.533, 1.531, + 1.542, 1.545, 1.548, 1.551, 1.551, 1.546, 1.539, 1.524, 1.511, 1.493, 1.479, 1.464, 1.451, 1.442, 1.433, 1.429, 1.429, 1.434, 1.439, 1.449, 1.462, 1.474, 1.491, 1.505, 1.519, 1.529, 1.536, 1.539, 1.539, 1.537, 1.533, 1.531, + 1.541, 1.546, 1.549, 1.552, 1.553, 1.551, 1.544, 1.533, 1.521, 1.505, 1.489, 1.477, 1.464, 1.455, 1.447, 1.443, 1.443, 1.446, 1.451, 1.462, 1.472, 1.487, 1.499, 1.514, 1.525, 1.535, 1.541, 1.541, 1.541, 1.539, 1.533, 1.531, + 1.541, 1.546, 1.549, 1.553, 1.554, 1.552, 1.546, 1.537, 1.524, 1.512, 1.499, 1.485, 1.474, 1.464, 1.455, 1.451, 1.451, 1.452, 1.461, 1.469, 1.481, 1.495, 1.506, 1.518, 1.529, 1.539, 1.541, 1.542, 1.541, 1.539, 1.533, 1.529 + ] + }, + { + "ct": 5000, + "table": + [ + 2.586, 2.591, 2.597, 2.601, 2.601, 2.599, 2.592, 2.576, 2.561, 2.541, 2.523, 2.503, 2.486, 2.471, 2.459, 2.452, 2.452, 2.454, 2.462, 2.478, 2.495, 2.512, 2.531, 2.555, 2.568, 2.579, 2.587, 2.588, 2.585, 2.579, 2.573, 2.566, + 2.587, 2.592, 2.598, 2.601, 2.601, 2.599, 2.587, 2.574, 2.556, 2.532, 2.512, 2.491, 2.474, 2.462, 2.449, 2.443, 2.439, 2.443, 2.454, 2.464, 2.485, 2.505, 2.525, 2.548, 2.566, 2.578, 2.585, 2.588, 2.586, 2.579, 2.575, 2.567, + 2.587, 2.593, 2.598, 2.602, 2.601, 2.597, 2.584, 2.569, 2.551, 2.527, 2.503, 2.482, 2.464, 2.448, 2.434, 2.428, 2.427, 2.431, 2.439, 2.455, 2.474, 2.498, 2.521, 2.541, 2.564, 2.577, 2.585, 2.588, 2.589, 2.581, 2.576, 2.569, + 2.593, 2.596, 2.601, 2.603, 2.601, 2.594, 2.583, 2.563, 2.539, 2.514, 2.491, 2.466, 2.445, 2.429, 2.417, 2.409, 2.408, 2.411, 2.421, 2.437, 2.457, 2.481, 2.507, 2.531, 2.555, 2.572, 2.583, 2.588, 2.588, 2.585, 2.579, 2.575, + 2.597, 2.599, 2.604, 2.603, 2.599, 2.587, 2.567, 2.548, 2.522, 2.493, 2.467, 2.443, 2.419, 2.406, 2.391, 2.385, 2.385, 2.387, 2.397, 2.413, 2.435, 2.459, 2.486, 2.509, 2.538, 2.559, 2.574, 2.586, 2.588, 2.586, 2.582, 2.579, + 2.601, 2.603, 2.606, 2.604, 2.596, 2.578, 2.556, 2.531, 2.501, 2.471, 2.444, 2.419, 2.402, 2.381, 2.365, 2.359, 2.359, 2.361, 2.374, 2.396, 2.413, 2.435, 2.465, 2.493, 2.517, 2.542, 2.562, 2.582, 2.588, 2.587, 2.586, 2.584, + 2.601, 2.604, 2.605, 2.604, 2.593, 2.575, 2.547, 2.522, 2.488, 2.458, 2.432, 2.402, 2.381, 2.364, 2.349, 2.338, 2.338, 2.345, 2.359, 2.374, 2.396, 2.423, 2.453, 2.481, 2.511, 2.539, 2.561, 2.581, 2.586, 2.588, 2.588, 2.586, + 2.599, 2.602, 2.604, 2.602, 2.592, 2.572, 2.546, 2.516, 2.485, 2.451, 2.422, 2.393, 2.368, 2.349, 2.336, 2.328, 2.328, 2.333, 2.345, 2.365, 2.389, 2.417, 2.447, 2.478, 2.509, 2.537, 2.561, 2.577, 2.585, 2.588, 2.588, 2.587, + 2.601, 2.602, 2.604, 2.601, 2.589, 2.569, 2.539, 2.509, 2.473, 2.442, 2.409, 2.379, 2.357, 2.336, 2.323, 2.315, 2.315, 2.322, 2.334, 2.354, 2.377, 2.406, 2.436, 2.469, 2.503, 2.529, 2.558, 2.574, 2.585, 2.588, 2.589, 2.587, + 2.601, 2.606, 2.606, 2.601, 2.581, 2.557, 2.525, 2.493, 2.459, 2.426, 2.394, 2.365, 2.339, 2.322, 2.308, 2.301, 2.301, 2.305, 2.322, 2.337, 2.361, 2.389, 2.422, 2.454, 2.485, 2.519, 2.546, 2.568, 2.584, 2.589, 2.589, 2.589, + 2.608, 2.608, 2.606, 2.597, 2.576, 2.548, 2.515, 2.481, 2.444, 2.409, 2.376, 2.346, 2.323, 2.308, 2.293, 2.282, 2.281, 2.291, 2.305, 2.322, 2.348, 2.371, 2.403, 2.439, 2.472, 2.508, 2.538, 2.565, 2.582, 2.589, 2.592, 2.593, + 2.608, 2.608, 2.605, 2.596, 2.575, 2.547, 2.511, 2.474, 2.435, 2.401, 2.366, 2.339, 2.312, 2.293, 2.281, 2.274, 2.274, 2.281, 2.291, 2.311, 2.334, 2.364, 2.399, 2.433, 2.471, 2.506, 2.538, 2.564, 2.581, 2.591, 2.594, 2.595, + 2.605, 2.606, 2.605, 2.595, 2.575, 2.547, 2.511, 2.474, 2.433, 2.397, 2.363, 2.333, 2.309, 2.291, 2.274, 2.267, 2.265, 2.272, 2.284, 2.307, 2.331, 2.361, 2.395, 2.431, 2.469, 2.503, 2.539, 2.567, 2.584, 2.591, 2.595, 2.595, + 2.605, 2.606, 2.605, 2.595, 2.575, 2.547, 2.509, 2.473, 2.431, 2.395, 2.361, 2.332, 2.306, 2.285, 2.267, 2.261, 2.262, 2.265, 2.281, 2.302, 2.329, 2.359, 2.395, 2.429, 2.468, 2.503, 2.539, 2.567, 2.583, 2.593, 2.595, 2.595, + 2.608, 2.607, 2.606, 2.592, 2.572, 2.543, 2.506, 2.468, 2.426, 2.389, 2.354, 2.327, 2.299, 2.279, 2.262, 2.258, 2.257, 2.262, 2.276, 2.297, 2.321, 2.352, 2.387, 2.425, 2.464, 2.498, 2.532, 2.565, 2.582, 2.592, 2.595, 2.596, + 2.611, 2.609, 2.605, 2.592, 2.571, 2.538, 2.499, 2.463, 2.421, 2.384, 2.351, 2.322, 2.295, 2.276, 2.259, 2.254, 2.254, 2.256, 2.273, 2.292, 2.318, 2.347, 2.383, 2.418, 2.456, 2.491, 2.529, 2.562, 2.581, 2.593, 2.597, 2.598, + 2.609, 2.609, 2.606, 2.593, 2.571, 2.538, 2.499, 2.463, 2.421, 2.384, 2.351, 2.321, 2.295, 2.276, 2.259, 2.251, 2.251, 2.256, 2.273, 2.292, 2.318, 2.347, 2.383, 2.418, 2.456, 2.491, 2.529, 2.559, 2.582, 2.595, 2.597, 2.599, + 2.609, 2.609, 2.607, 2.597, 2.576, 2.543, 2.507, 2.467, 2.427, 2.388, 2.356, 2.323, 2.297, 2.278, 2.262, 2.256, 2.255, 2.262, 2.275, 2.296, 2.321, 2.351, 2.388, 2.425, 2.464, 2.502, 2.534, 2.563, 2.586, 2.595, 2.598, 2.599, + 2.609, 2.609, 2.608, 2.601, 2.581, 2.547, 2.513, 2.475, 2.434, 2.398, 2.362, 2.332, 2.307, 2.287, 2.269, 2.263, 2.263, 2.269, 2.281, 2.304, 2.328, 2.358, 2.394, 2.429, 2.469, 2.508, 2.538, 2.568, 2.589, 2.597, 2.598, 2.598, + 2.609, 2.611, 2.609, 2.601, 2.583, 2.549, 2.518, 2.478, 2.439, 2.402, 2.367, 2.337, 2.313, 2.293, 2.279, 2.271, 2.269, 2.277, 2.291, 2.311, 2.336, 2.363, 2.399, 2.435, 2.473, 2.509, 2.541, 2.571, 2.591, 2.598, 2.599, 2.599, + 2.611, 2.611, 2.609, 2.602, 2.585, 2.551, 2.519, 2.481, 2.442, 2.406, 2.374, 2.342, 2.318, 2.297, 2.287, 2.279, 2.278, 2.287, 2.297, 2.315, 2.339, 2.368, 2.402, 2.438, 2.476, 2.511, 2.545, 2.571, 2.591, 2.599, 2.601, 2.599, + 2.611, 2.611, 2.609, 2.604, 2.587, 2.557, 2.521, 2.485, 2.447, 2.412, 2.379, 2.352, 2.328, 2.309, 2.297, 2.288, 2.287, 2.297, 2.308, 2.327, 2.349, 2.377, 2.408, 2.446, 2.481, 2.517, 2.547, 2.573, 2.591, 2.599, 2.601, 2.599, + 2.608, 2.609, 2.609, 2.606, 2.592, 2.564, 2.533, 2.498, 2.462, 2.427, 2.394, 2.364, 2.343, 2.326, 2.309, 2.302, 2.302, 2.308, 2.324, 2.341, 2.362, 2.391, 2.425, 2.458, 2.494, 2.526, 2.555, 2.584, 2.593, 2.599, 2.599, 2.599, + 2.608, 2.609, 2.609, 2.609, 2.597, 2.574, 2.547, 2.511, 2.475, 2.438, 2.411, 2.381, 2.359, 2.342, 2.327, 2.318, 2.318, 2.325, 2.341, 2.358, 2.377, 2.404, 2.439, 2.469, 2.507, 2.537, 2.564, 2.587, 2.596, 2.598, 2.598, 2.597, + 2.609, 2.609, 2.611, 2.609, 2.599, 2.579, 2.551, 2.519, 2.486, 2.453, 2.425, 2.397, 2.375, 2.358, 2.345, 2.336, 2.336, 2.341, 2.355, 2.372, 2.393, 2.419, 2.452, 2.481, 2.516, 2.542, 2.571, 2.591, 2.597, 2.599, 2.598, 2.595, + 2.607, 2.611, 2.613, 2.611, 2.605, 2.586, 2.561, 2.529, 2.495, 2.462, 2.435, 2.409, 2.387, 2.374, 2.359, 2.351, 2.351, 2.356, 2.372, 2.385, 2.406, 2.431, 2.462, 2.488, 2.524, 2.551, 2.573, 2.591, 2.598, 2.599, 2.598, 2.596, + 2.606, 2.609, 2.613, 2.613, 2.607, 2.591, 2.565, 2.539, 2.507, 2.477, 2.449, 2.425, 2.409, 2.387, 2.376, 2.369, 2.369, 2.374, 2.385, 2.406, 2.422, 2.446, 2.473, 2.502, 2.534, 2.557, 2.578, 2.595, 2.599, 2.601, 2.598, 2.595, + 2.606, 2.611, 2.613, 2.614, 2.611, 2.598, 2.581, 2.553, 2.523, 2.496, 2.471, 2.449, 2.425, 2.409, 2.398, 2.391, 2.391, 2.395, 2.408, 2.422, 2.445, 2.468, 2.493, 2.522, 2.549, 2.569, 2.589, 2.601, 2.603, 2.602, 2.596, 2.593, + 2.605, 2.609, 2.613, 2.616, 2.614, 2.607, 2.591, 2.571, 2.545, 2.518, 2.494, 2.471, 2.452, 2.435, 2.423, 2.417, 2.417, 2.421, 2.431, 2.449, 2.467, 2.493, 2.516, 2.542, 2.566, 2.585, 2.596, 2.606, 2.605, 2.602, 2.595, 2.593, + 2.604, 2.608, 2.616, 2.617, 2.618, 2.613, 2.602, 2.584, 2.559, 2.536, 2.514, 2.493, 2.476, 2.459, 2.445, 2.439, 2.439, 2.445, 2.456, 2.471, 2.493, 2.511, 2.534, 2.559, 2.579, 2.592, 2.607, 2.608, 2.607, 2.604, 2.595, 2.592, + 2.603, 2.609, 2.615, 2.619, 2.623, 2.619, 2.608, 2.594, 2.573, 2.551, 2.532, 2.512, 2.493, 2.477, 2.468, 2.462, 2.462, 2.468, 2.476, 2.494, 2.509, 2.528, 2.551, 2.574, 2.589, 2.604, 2.611, 2.611, 2.611, 2.604, 2.598, 2.592, + 2.602, 2.607, 2.613, 2.621, 2.624, 2.621, 2.617, 2.601, 2.585, 2.567, 2.544, 2.521, 2.507, 2.493, 2.478, 2.474, 2.475, 2.477, 2.489, 2.505, 2.523, 2.544, 2.563, 2.584, 2.598, 2.609, 2.612, 2.613, 2.613, 2.608, 2.599, 2.591 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.263, 3.263, 3.264, 3.269, 3.274, 3.275, 3.277, 3.281, 3.283, 3.285, 3.289, 3.292, 3.291, 3.291, 3.294, 3.298, 3.302, 3.305, 3.303, 3.301, 3.296, 3.294, 3.293, 3.293, 3.295, 3.295, 3.295, 3.287, 3.285, 3.279, 3.282, 3.285, + 3.259, 3.259, 3.262, 3.268, 3.271, 3.273, 3.273, 3.274, 3.277, 3.278, 3.282, 3.285, 3.286, 3.286, 3.288, 3.289, 3.292, 3.291, 3.293, 3.291, 3.288, 3.288, 3.288, 3.288, 3.288, 3.287, 3.287, 3.283, 3.281, 3.276, 3.277, 3.281, + 3.259, 3.259, 3.262, 3.269, 3.272, 3.274, 3.273, 3.273, 3.273, 3.276, 3.278, 3.279, 3.281, 3.282, 3.283, 3.283, 3.285, 3.286, 3.286, 3.285, 3.283, 3.282, 3.282, 3.286, 3.285, 3.285, 3.285, 3.282, 3.281, 3.276, 3.275, 3.279, + 3.267, 3.266, 3.269, 3.273, 3.274, 3.277, 3.276, 3.275, 3.274, 3.277, 3.277, 3.278, 3.279, 3.279, 3.279, 3.279, 3.281, 3.283, 3.283, 3.281, 3.281, 3.281, 3.282, 3.287, 3.287, 3.288, 3.287, 3.286, 3.283, 3.277, 3.278, 3.281, + 3.268, 3.271, 3.274, 3.277, 3.283, 3.286, 3.284, 3.281, 3.278, 3.278, 3.279, 3.279, 3.281, 3.281, 3.281, 3.281, 3.282, 3.283, 3.284, 3.283, 3.282, 3.282, 3.284, 3.288, 3.289, 3.291, 3.291, 3.288, 3.288, 3.283, 3.286, 3.288, + 3.272, 3.275, 3.279, 3.283, 3.288, 3.289, 3.289, 3.287, 3.282, 3.284, 3.282, 3.284, 3.284, 3.285, 3.284, 3.285, 3.285, 3.288, 3.287, 3.286, 3.283, 3.283, 3.285, 3.289, 3.292, 3.292, 3.293, 3.292, 3.291, 3.288, 3.289, 3.293, + 3.276, 3.278, 3.282, 3.289, 3.293, 3.293, 3.291, 3.289, 3.287, 3.286, 3.285, 3.285, 3.286, 3.286, 3.287, 3.288, 3.289, 3.289, 3.289, 3.288, 3.285, 3.283, 3.286, 3.289, 3.292, 3.294, 3.294, 3.294, 3.293, 3.289, 3.292, 3.293, + 3.279, 3.281, 3.286, 3.293, 3.297, 3.298, 3.295, 3.292, 3.288, 3.287, 3.285, 3.287, 3.288, 3.288, 3.289, 3.291, 3.292, 3.293, 3.291, 3.288, 3.286, 3.285, 3.285, 3.291, 3.294, 3.295, 3.297, 3.297, 3.298, 3.293, 3.294, 3.294, + 3.281, 3.286, 3.291, 3.298, 3.301, 3.301, 3.299, 3.295, 3.289, 3.288, 3.286, 3.288, 3.289, 3.292, 3.293, 3.292, 3.295, 3.296, 3.295, 3.291, 3.287, 3.286, 3.286, 3.289, 3.295, 3.297, 3.298, 3.301, 3.301, 3.298, 3.297, 3.297, + 3.284, 3.289, 3.295, 3.302, 3.303, 3.303, 3.301, 3.298, 3.294, 3.292, 3.289, 3.293, 3.296, 3.297, 3.297, 3.297, 3.297, 3.298, 3.298, 3.296, 3.289, 3.288, 3.287, 3.294, 3.298, 3.301, 3.304, 3.305, 3.304, 3.299, 3.299, 3.302, + 3.291, 3.292, 3.299, 3.305, 3.308, 3.305, 3.304, 3.302, 3.298, 3.295, 3.295, 3.298, 3.299, 3.302, 3.303, 3.302, 3.301, 3.301, 3.301, 3.299, 3.296, 3.291, 3.292, 3.297, 3.301, 3.304, 3.306, 3.309, 3.308, 3.302, 3.301, 3.304, + 3.292, 3.297, 3.303, 3.309, 3.312, 3.311, 3.308, 3.304, 3.301, 3.299, 3.298, 3.299, 3.303, 3.305, 3.306, 3.305, 3.305, 3.303, 3.303, 3.301, 3.299, 3.294, 3.294, 3.297, 3.302, 3.305, 3.309, 3.311, 3.311, 3.305, 3.305, 3.306, + 3.295, 3.298, 3.305, 3.309, 3.313, 3.313, 3.312, 3.307, 3.303, 3.301, 3.299, 3.301, 3.304, 3.307, 3.308, 3.306, 3.306, 3.306, 3.306, 3.302, 3.299, 3.296, 3.295, 3.298, 3.303, 3.306, 3.311, 3.312, 3.312, 3.307, 3.308, 3.309, + 3.297, 3.298, 3.303, 3.309, 3.313, 3.313, 3.311, 3.307, 3.303, 3.301, 3.299, 3.299, 3.305, 3.307, 3.307, 3.306, 3.306, 3.306, 3.305, 3.299, 3.297, 3.294, 3.294, 3.298, 3.303, 3.305, 3.311, 3.312, 3.313, 3.308, 3.311, 3.309, + 3.297, 3.298, 3.304, 3.309, 3.312, 3.313, 3.311, 3.308, 3.304, 3.302, 3.301, 3.301, 3.306, 3.307, 3.308, 3.306, 3.306, 3.307, 3.306, 3.302, 3.297, 3.294, 3.294, 3.299, 3.305, 3.306, 3.309, 3.312, 3.311, 3.306, 3.308, 3.309, + 3.298, 3.299, 3.306, 3.311, 3.315, 3.314, 3.311, 3.308, 3.305, 3.303, 3.303, 3.304, 3.307, 3.309, 3.309, 3.308, 3.308, 3.307, 3.306, 3.302, 3.298, 3.296, 3.296, 3.298, 3.304, 3.306, 3.308, 3.309, 3.314, 3.308, 3.309, 3.308, + 3.299, 3.301, 3.307, 3.313, 3.316, 3.316, 3.313, 3.311, 3.307, 3.305, 3.305, 3.307, 3.309, 3.311, 3.312, 3.311, 3.309, 3.309, 3.308, 3.306, 3.301, 3.298, 3.297, 3.301, 3.305, 3.309, 3.309, 3.311, 3.313, 3.306, 3.308, 3.307, + 3.301, 3.301, 3.307, 3.314, 3.317, 3.318, 3.314, 3.311, 3.308, 3.306, 3.306, 3.308, 3.311, 3.311, 3.312, 3.311, 3.309, 3.309, 3.309, 3.306, 3.302, 3.298, 3.298, 3.301, 3.305, 3.309, 3.311, 3.311, 3.312, 3.309, 3.308, 3.308, + 3.301, 3.302, 3.307, 3.316, 3.319, 3.319, 3.315, 3.312, 3.309, 3.306, 3.307, 3.309, 3.311, 3.312, 3.311, 3.311, 3.309, 3.309, 3.309, 3.306, 3.303, 3.299, 3.298, 3.302, 3.305, 3.308, 3.309, 3.309, 3.309, 3.303, 3.306, 3.307, + 3.301, 3.303, 3.308, 3.315, 3.318, 3.318, 3.316, 3.313, 3.311, 3.307, 3.307, 3.308, 3.311, 3.311, 3.311, 3.308, 3.308, 3.307, 3.307, 3.306, 3.303, 3.299, 3.297, 3.301, 3.303, 3.306, 3.309, 3.308, 3.306, 3.303, 3.304, 3.306, + 3.302, 3.304, 3.306, 3.316, 3.318, 3.318, 3.317, 3.315, 3.311, 3.308, 3.309, 3.311, 3.311, 3.312, 3.311, 3.307, 3.306, 3.307, 3.308, 3.307, 3.304, 3.299, 3.298, 3.301, 3.303, 3.305, 3.306, 3.305, 3.304, 3.302, 3.303, 3.306, + 3.302, 3.304, 3.306, 3.312, 3.316, 3.317, 3.317, 3.313, 3.311, 3.309, 3.309, 3.311, 3.311, 3.312, 3.309, 3.307, 3.306, 3.306, 3.308, 3.307, 3.304, 3.298, 3.297, 3.302, 3.304, 3.305, 3.306, 3.305, 3.304, 3.299, 3.302, 3.303, + 3.299, 3.299, 3.306, 3.309, 3.315, 3.316, 3.316, 3.312, 3.309, 3.308, 3.308, 3.309, 3.311, 3.311, 3.307, 3.305, 3.305, 3.305, 3.306, 3.304, 3.299, 3.297, 3.296, 3.301, 3.302, 3.304, 3.303, 3.302, 3.301, 3.298, 3.299, 3.301, + 3.295, 3.297, 3.305, 3.309, 3.311, 3.311, 3.311, 3.309, 3.307, 3.306, 3.305, 3.305, 3.305, 3.305, 3.304, 3.301, 3.301, 3.301, 3.302, 3.299, 3.296, 3.295, 3.295, 3.298, 3.301, 3.302, 3.303, 3.301, 3.299, 3.295, 3.297, 3.299, + 3.294, 3.296, 3.299, 3.306, 3.308, 3.309, 3.309, 3.307, 3.307, 3.303, 3.302, 3.301, 3.302, 3.303, 3.302, 3.299, 3.298, 3.299, 3.299, 3.298, 3.295, 3.292, 3.292, 3.293, 3.297, 3.299, 3.299, 3.299, 3.297, 3.294, 3.295, 3.299, + 3.291, 3.292, 3.296, 3.302, 3.306, 3.306, 3.307, 3.306, 3.305, 3.303, 3.302, 3.301, 3.301, 3.303, 3.301, 3.299, 3.298, 3.298, 3.298, 3.297, 3.295, 3.292, 3.291, 3.291, 3.295, 3.295, 3.297, 3.298, 3.296, 3.293, 3.292, 3.291, + 3.293, 3.292, 3.294, 3.301, 3.303, 3.305, 3.308, 3.306, 3.306, 3.304, 3.304, 3.303, 3.303, 3.303, 3.302, 3.301, 3.299, 3.299, 3.299, 3.299, 3.294, 3.291, 3.289, 3.291, 3.293, 3.294, 3.294, 3.294, 3.294, 3.289, 3.291, 3.291, + 3.288, 3.289, 3.291, 3.299, 3.303, 3.304, 3.304, 3.304, 3.304, 3.303, 3.303, 3.304, 3.306, 3.305, 3.303, 3.301, 3.301, 3.298, 3.299, 3.298, 3.293, 3.291, 3.289, 3.291, 3.291, 3.292, 3.291, 3.291, 3.291, 3.285, 3.288, 3.291, + 3.285, 3.284, 3.287, 3.291, 3.299, 3.299, 3.299, 3.299, 3.299, 3.301, 3.302, 3.303, 3.303, 3.302, 3.299, 3.298, 3.298, 3.298, 3.298, 3.293, 3.288, 3.286, 3.285, 3.288, 3.288, 3.288, 3.287, 3.285, 3.284, 3.279, 3.281, 3.284, + 3.281, 3.282, 3.282, 3.286, 3.291, 3.294, 3.294, 3.295, 3.295, 3.299, 3.301, 3.304, 3.305, 3.299, 3.299, 3.297, 3.298, 3.298, 3.297, 3.292, 3.288, 3.285, 3.283, 3.284, 3.284, 3.286, 3.284, 3.282, 3.279, 3.275, 3.275, 3.278, + 3.282, 3.282, 3.284, 3.286, 3.291, 3.294, 3.295, 3.295, 3.298, 3.301, 3.304, 3.306, 3.306, 3.304, 3.303, 3.301, 3.302, 3.299, 3.299, 3.295, 3.289, 3.287, 3.284, 3.288, 3.287, 3.287, 3.285, 3.283, 3.282, 3.278, 3.281, 3.286, + 3.292, 3.291, 3.292, 3.298, 3.307, 3.309, 3.308, 3.312, 3.313, 3.317, 3.324, 3.327, 3.327, 3.325, 3.326, 3.322, 3.319, 3.317, 3.317, 3.315, 3.312, 3.305, 3.303, 3.301, 3.299, 3.297, 3.299, 3.293, 3.289, 3.285, 3.287, 3.293 + ] + }, + { + "ct": 5000, + "table": + [ + 1.602, 1.603, 1.605, 1.608, 1.611, 1.612, 1.612, 1.614, 1.615, 1.616, 1.619, 1.621, 1.621, 1.622, 1.622, 1.624, 1.624, 1.626, 1.625, 1.625, 1.623, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.614, 1.612, 1.609, 1.609, 1.611, + 1.601, 1.602, 1.605, 1.608, 1.611, 1.612, 1.612, 1.613, 1.614, 1.615, 1.617, 1.619, 1.621, 1.621, 1.621, 1.621, 1.622, 1.624, 1.624, 1.624, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.615, 1.614, 1.612, 1.609, 1.609, 1.609, + 1.602, 1.603, 1.605, 1.609, 1.611, 1.613, 1.613, 1.613, 1.613, 1.615, 1.616, 1.618, 1.618, 1.619, 1.619, 1.619, 1.621, 1.621, 1.622, 1.622, 1.619, 1.618, 1.617, 1.617, 1.616, 1.615, 1.615, 1.615, 1.612, 1.609, 1.609, 1.609, + 1.604, 1.605, 1.608, 1.612, 1.613, 1.614, 1.614, 1.614, 1.614, 1.614, 1.616, 1.617, 1.618, 1.618, 1.618, 1.619, 1.619, 1.621, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.616, 1.616, 1.616, 1.615, 1.615, 1.612, 1.611, 1.611, + 1.606, 1.608, 1.611, 1.614, 1.615, 1.615, 1.616, 1.616, 1.615, 1.615, 1.617, 1.618, 1.619, 1.619, 1.618, 1.619, 1.621, 1.622, 1.623, 1.622, 1.619, 1.619, 1.617, 1.617, 1.617, 1.618, 1.618, 1.617, 1.617, 1.614, 1.613, 1.613, + 1.608, 1.611, 1.614, 1.617, 1.618, 1.618, 1.618, 1.618, 1.618, 1.618, 1.619, 1.621, 1.621, 1.621, 1.621, 1.621, 1.622, 1.623, 1.624, 1.623, 1.622, 1.619, 1.619, 1.618, 1.618, 1.618, 1.618, 1.618, 1.618, 1.617, 1.615, 1.614, + 1.611, 1.613, 1.616, 1.618, 1.621, 1.621, 1.619, 1.619, 1.619, 1.619, 1.619, 1.621, 1.622, 1.623, 1.623, 1.623, 1.623, 1.624, 1.626, 1.624, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.621, 1.621, 1.619, 1.617, 1.616, 1.616, + 1.611, 1.613, 1.617, 1.621, 1.622, 1.622, 1.621, 1.619, 1.619, 1.619, 1.621, 1.622, 1.624, 1.624, 1.624, 1.624, 1.625, 1.626, 1.626, 1.624, 1.623, 1.621, 1.621, 1.619, 1.619, 1.619, 1.621, 1.621, 1.621, 1.619, 1.618, 1.617, + 1.613, 1.615, 1.618, 1.621, 1.623, 1.623, 1.622, 1.621, 1.619, 1.619, 1.621, 1.622, 1.625, 1.625, 1.626, 1.626, 1.625, 1.626, 1.626, 1.624, 1.622, 1.621, 1.619, 1.619, 1.619, 1.621, 1.622, 1.622, 1.621, 1.621, 1.619, 1.618, + 1.614, 1.617, 1.621, 1.623, 1.624, 1.624, 1.623, 1.621, 1.621, 1.621, 1.622, 1.625, 1.627, 1.627, 1.628, 1.628, 1.628, 1.628, 1.627, 1.626, 1.623, 1.621, 1.621, 1.621, 1.621, 1.623, 1.623, 1.623, 1.623, 1.621, 1.619, 1.619, + 1.616, 1.617, 1.622, 1.624, 1.625, 1.625, 1.624, 1.623, 1.622, 1.623, 1.624, 1.627, 1.629, 1.631, 1.631, 1.631, 1.631, 1.631, 1.631, 1.628, 1.626, 1.623, 1.622, 1.622, 1.623, 1.623, 1.624, 1.624, 1.624, 1.622, 1.621, 1.621, + 1.617, 1.618, 1.623, 1.625, 1.626, 1.626, 1.625, 1.624, 1.623, 1.624, 1.625, 1.629, 1.631, 1.633, 1.634, 1.634, 1.634, 1.633, 1.633, 1.631, 1.628, 1.624, 1.623, 1.623, 1.623, 1.625, 1.625, 1.625, 1.625, 1.623, 1.622, 1.622, + 1.617, 1.619, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.624, 1.625, 1.628, 1.632, 1.634, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.627, 1.624, 1.623, 1.623, 1.623, 1.624, 1.625, 1.626, 1.625, 1.624, 1.623, 1.623, + 1.618, 1.619, 1.623, 1.626, 1.627, 1.626, 1.626, 1.625, 1.624, 1.624, 1.625, 1.628, 1.631, 1.634, 1.634, 1.634, 1.634, 1.634, 1.633, 1.631, 1.628, 1.623, 1.622, 1.622, 1.623, 1.624, 1.625, 1.626, 1.626, 1.624, 1.624, 1.623, + 1.618, 1.619, 1.623, 1.626, 1.627, 1.627, 1.625, 1.624, 1.624, 1.624, 1.625, 1.628, 1.632, 1.633, 1.634, 1.634, 1.634, 1.633, 1.633, 1.631, 1.627, 1.623, 1.622, 1.622, 1.623, 1.624, 1.624, 1.625, 1.625, 1.624, 1.623, 1.623, + 1.619, 1.621, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.624, 1.626, 1.628, 1.632, 1.634, 1.635, 1.634, 1.634, 1.633, 1.633, 1.631, 1.628, 1.625, 1.622, 1.622, 1.622, 1.623, 1.624, 1.625, 1.625, 1.624, 1.623, 1.623, + 1.619, 1.621, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.625, 1.627, 1.629, 1.633, 1.635, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.628, 1.625, 1.623, 1.622, 1.622, 1.623, 1.624, 1.624, 1.624, 1.623, 1.623, 1.622, + 1.619, 1.621, 1.624, 1.626, 1.628, 1.628, 1.627, 1.626, 1.625, 1.626, 1.627, 1.629, 1.633, 1.635, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.628, 1.625, 1.623, 1.623, 1.623, 1.623, 1.624, 1.624, 1.624, 1.622, 1.622, 1.622, + 1.619, 1.621, 1.623, 1.626, 1.628, 1.628, 1.627, 1.626, 1.625, 1.626, 1.627, 1.629, 1.632, 1.634, 1.635, 1.635, 1.634, 1.634, 1.632, 1.631, 1.628, 1.624, 1.622, 1.622, 1.622, 1.623, 1.624, 1.624, 1.624, 1.622, 1.621, 1.621, + 1.619, 1.621, 1.623, 1.627, 1.628, 1.628, 1.627, 1.627, 1.626, 1.627, 1.628, 1.629, 1.631, 1.633, 1.634, 1.633, 1.633, 1.632, 1.631, 1.631, 1.627, 1.624, 1.622, 1.622, 1.622, 1.622, 1.623, 1.623, 1.623, 1.621, 1.621, 1.621, + 1.621, 1.621, 1.624, 1.627, 1.628, 1.628, 1.627, 1.627, 1.627, 1.627, 1.628, 1.631, 1.632, 1.633, 1.633, 1.632, 1.632, 1.632, 1.631, 1.631, 1.628, 1.625, 1.623, 1.622, 1.622, 1.622, 1.623, 1.623, 1.623, 1.621, 1.621, 1.621, + 1.621, 1.621, 1.623, 1.627, 1.628, 1.628, 1.628, 1.627, 1.627, 1.628, 1.628, 1.629, 1.631, 1.632, 1.633, 1.632, 1.631, 1.631, 1.631, 1.629, 1.628, 1.625, 1.624, 1.623, 1.623, 1.623, 1.623, 1.623, 1.623, 1.621, 1.621, 1.619, + 1.619, 1.621, 1.623, 1.626, 1.628, 1.629, 1.627, 1.627, 1.627, 1.627, 1.628, 1.629, 1.631, 1.631, 1.631, 1.631, 1.631, 1.629, 1.629, 1.628, 1.626, 1.624, 1.623, 1.623, 1.623, 1.622, 1.623, 1.623, 1.622, 1.621, 1.619, 1.619, + 1.618, 1.619, 1.623, 1.625, 1.627, 1.627, 1.627, 1.627, 1.626, 1.627, 1.627, 1.628, 1.628, 1.629, 1.628, 1.628, 1.628, 1.628, 1.628, 1.627, 1.625, 1.623, 1.621, 1.621, 1.621, 1.622, 1.622, 1.622, 1.621, 1.619, 1.618, 1.618, + 1.618, 1.619, 1.622, 1.624, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.627, 1.627, 1.627, 1.626, 1.626, 1.626, 1.626, 1.626, 1.624, 1.622, 1.621, 1.621, 1.619, 1.621, 1.621, 1.621, 1.621, 1.618, 1.617, 1.617, + 1.616, 1.618, 1.621, 1.623, 1.624, 1.625, 1.625, 1.625, 1.625, 1.625, 1.626, 1.626, 1.627, 1.627, 1.625, 1.624, 1.624, 1.625, 1.626, 1.625, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.621, 1.621, 1.619, 1.616, 1.616, 1.616, + 1.615, 1.616, 1.619, 1.621, 1.623, 1.624, 1.625, 1.624, 1.624, 1.625, 1.626, 1.627, 1.627, 1.626, 1.626, 1.625, 1.624, 1.625, 1.625, 1.625, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.619, 1.619, 1.618, 1.616, 1.615, 1.614, + 1.614, 1.615, 1.616, 1.621, 1.621, 1.623, 1.624, 1.623, 1.624, 1.624, 1.625, 1.627, 1.627, 1.627, 1.626, 1.625, 1.625, 1.625, 1.625, 1.624, 1.622, 1.621, 1.619, 1.618, 1.617, 1.617, 1.617, 1.617, 1.616, 1.613, 1.612, 1.612, + 1.612, 1.612, 1.615, 1.617, 1.621, 1.621, 1.622, 1.622, 1.622, 1.624, 1.625, 1.626, 1.626, 1.626, 1.625, 1.624, 1.624, 1.624, 1.624, 1.623, 1.621, 1.619, 1.618, 1.616, 1.615, 1.615, 1.615, 1.615, 1.613, 1.611, 1.609, 1.609, + 1.611, 1.611, 1.612, 1.615, 1.618, 1.619, 1.621, 1.621, 1.622, 1.623, 1.624, 1.626, 1.626, 1.626, 1.625, 1.624, 1.624, 1.624, 1.623, 1.622, 1.621, 1.618, 1.617, 1.615, 1.615, 1.614, 1.614, 1.613, 1.611, 1.609, 1.609, 1.609, + 1.611, 1.611, 1.612, 1.615, 1.618, 1.619, 1.621, 1.622, 1.623, 1.625, 1.625, 1.627, 1.627, 1.627, 1.626, 1.626, 1.626, 1.626, 1.624, 1.622, 1.621, 1.618, 1.617, 1.617, 1.616, 1.615, 1.614, 1.613, 1.612, 1.609, 1.609, 1.609, + 1.612, 1.612, 1.614, 1.617, 1.619, 1.621, 1.623, 1.624, 1.625, 1.626, 1.627, 1.629, 1.631, 1.629, 1.629, 1.629, 1.628, 1.629, 1.628, 1.626, 1.624, 1.621, 1.621, 1.619, 1.619, 1.618, 1.616, 1.616, 1.613, 1.611, 1.612, 1.612 + ] + } + ], + "luminance_lut": + [ + 2.977, 2.794, 2.572, 2.375, 2.218, 2.098, 1.995, 1.903, 1.815, 1.731, 1.647, 1.571, 1.516, 1.493, 1.483, 1.481, 1.481, 1.481, 1.489, 1.511, 1.571, 1.643, 1.729, 1.813, 1.901, 1.993, 2.091, 2.208, 2.364, 2.563, 2.785, 2.971, + 2.951, 2.736, 2.512, 2.312, 2.153, 2.031, 1.926, 1.824, 1.736, 1.649, 1.571, 1.506, 1.456, 1.419, 1.396, 1.386, 1.386, 1.392, 1.414, 1.451, 1.505, 1.571, 1.648, 1.733, 1.824, 1.922, 2.025, 2.144, 2.301, 2.499, 2.725, 2.939, + 2.883, 2.701, 2.471, 2.266, 2.102, 1.974, 1.861, 1.753, 1.649, 1.571, 1.502, 1.425, 1.361, 1.322, 1.298, 1.286, 1.286, 1.294, 1.317, 1.359, 1.424, 1.501, 1.571, 1.648, 1.751, 1.857, 1.968, 2.095, 2.254, 2.458, 2.688, 2.872, + 2.788, 2.632, 2.408, 2.209, 2.056, 1.931, 1.816, 1.704, 1.598, 1.503, 1.425, 1.361, 1.322, 1.298, 1.269, 1.245, 1.243, 1.264, 1.293, 1.317, 1.359, 1.424, 1.501, 1.596, 1.702, 1.812, 1.924, 2.046, 2.197, 2.392, 2.619, 2.777, + 2.712, 2.541, 2.327, 2.155, 2.023, 1.908, 1.796, 1.684, 1.578, 1.488, 1.412, 1.351, 1.304, 1.269, 1.245, 1.235, 1.235, 1.243, 1.264, 1.301, 1.349, 1.411, 1.485, 1.577, 1.683, 1.791, 1.902, 2.016, 2.143, 2.312, 2.528, 2.702, + 2.678, 2.469, 2.269, 2.117, 1.998, 1.885, 1.773, 1.661, 1.556, 1.469, 1.397, 1.336, 1.277, 1.245, 1.234, 1.226, 1.226, 1.232, 1.244, 1.273, 1.332, 1.392, 1.465, 1.555, 1.659, 1.768, 1.879, 1.991, 2.109, 2.256, 2.454, 2.665, + 2.659, 2.433, 2.232, 2.081, 1.957, 1.841, 1.722, 1.606, 1.499, 1.409, 1.337, 1.277, 1.232, 1.198, 1.175, 1.166, 1.166, 1.172, 1.193, 1.228, 1.272, 1.334, 1.408, 1.499, 1.608, 1.717, 1.834, 1.951, 2.073, 2.222, 2.419, 2.648, + 2.624, 2.411, 2.204, 2.041, 1.909, 1.784, 1.661, 1.539, 1.431, 1.337, 1.277, 1.219, 1.159, 1.118, 1.096, 1.085, 1.085, 1.092, 1.114, 1.156, 1.219, 1.272, 1.337, 1.429, 1.539, 1.658, 1.779, 1.904, 2.033, 2.193, 2.397, 2.613, + 2.564, 2.377, 2.169, 2.012, 1.879, 1.749, 1.623, 1.501, 1.392, 1.299, 1.227, 1.169, 1.125, 1.097, 1.079, 1.063, 1.063, 1.076, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.501, 1.622, 1.746, 1.875, 2.005, 2.161, 2.362, 2.554, + 2.515, 2.325, 2.138, 1.997, 1.869, 1.742, 1.617, 1.501, 1.392, 1.299, 1.227, 1.169, 1.125, 1.095, 1.079, 1.063, 1.063, 1.076, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.499, 1.615, 1.741, 1.867, 1.991, 2.132, 2.316, 2.505, + 2.498, 2.289, 2.121, 1.988, 1.867, 1.741, 1.616, 1.499, 1.391, 1.299, 1.227, 1.169, 1.125, 1.095, 1.082, 1.065, 1.064, 1.079, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.498, 1.614, 1.738, 1.864, 1.985, 2.116, 2.281, 2.486, + 2.498, 2.272, 2.105, 1.971, 1.846, 1.718, 1.592, 1.475, 1.371, 1.279, 1.211, 1.156, 1.112, 1.083, 1.064, 1.055, 1.055, 1.062, 1.081, 1.109, 1.154, 1.212, 1.285, 1.372, 1.473, 1.589, 1.712, 1.843, 1.967, 2.101, 2.263, 2.486, + 2.497, 2.267, 2.088, 1.946, 1.813, 1.679, 1.549, 1.431, 1.324, 1.231, 1.159, 1.114, 1.079, 1.035, 1.008, 1.001, 1.001, 1.008, 1.032, 1.076, 1.111, 1.161, 1.235, 1.324, 1.429, 1.547, 1.677, 1.811, 1.941, 2.082, 2.257, 2.484, + 2.476, 2.262, 2.077, 1.933, 1.802, 1.671, 1.541, 1.421, 1.317, 1.227, 1.157, 1.101, 1.059, 1.027, 1.004, 1.001, 1.001, 1.004, 1.024, 1.054, 1.098, 1.157, 1.229, 1.317, 1.419, 1.537, 1.667, 1.799, 1.931, 2.071, 2.251, 2.463, + 2.455, 2.246, 2.076, 1.933, 1.802, 1.671, 1.541, 1.421, 1.317, 1.227, 1.157, 1.103, 1.064, 1.035, 1.011, 1.003, 1.003, 1.009, 1.032, 1.062, 1.099, 1.157, 1.229, 1.317, 1.419, 1.537, 1.667, 1.799, 1.931, 2.071, 2.236, 2.446, + 2.454, 2.239, 2.077, 1.946, 1.817, 1.686, 1.561, 1.444, 1.342, 1.255, 1.189, 1.136, 1.093, 1.059, 1.039, 1.038, 1.038, 1.039, 1.056, 1.091, 1.131, 1.187, 1.258, 1.341, 1.441, 1.556, 1.683, 1.813, 1.939, 2.071, 2.229, 2.445, + 2.454, 2.239, 2.079, 1.946, 1.817, 1.686, 1.561, 1.444, 1.342, 1.255, 1.189, 1.136, 1.093, 1.062, 1.039, 1.038, 1.038, 1.039, 1.059, 1.091, 1.131, 1.187, 1.258, 1.341, 1.441, 1.556, 1.683, 1.813, 1.939, 2.071, 2.229, 2.445, + 2.458, 2.251, 2.079, 1.941, 1.807, 1.672, 1.543, 1.424, 1.319, 1.231, 1.162, 1.107, 1.065, 1.045, 1.018, 1.003, 1.003, 1.017, 1.044, 1.062, 1.103, 1.159, 1.232, 1.317, 1.419, 1.539, 1.669, 1.802, 1.933, 2.072, 2.239, 2.445, + 2.479, 2.265, 2.085, 1.941, 1.807, 1.672, 1.543, 1.424, 1.319, 1.231, 1.162, 1.107, 1.064, 1.031, 1.017, 1.003, 1.003, 1.017, 1.031, 1.059, 1.103, 1.159, 1.232, 1.317, 1.419, 1.539, 1.669, 1.802, 1.933, 2.076, 2.252, 2.468, + 2.504, 2.277, 2.099, 1.958, 1.826, 1.695, 1.565, 1.445, 1.338, 1.249, 1.181, 1.129, 1.095, 1.051, 1.027, 1.018, 1.018, 1.028, 1.049, 1.092, 1.127, 1.179, 1.252, 1.339, 1.442, 1.561, 1.691, 1.822, 1.949, 2.089, 2.263, 2.492, + 2.509, 2.288, 2.118, 1.982, 1.858, 1.728, 1.604, 1.486, 1.381, 1.293, 1.227, 1.173, 1.127, 1.098, 1.076, 1.067, 1.067, 1.077, 1.097, 1.121, 1.168, 1.225, 1.296, 1.382, 1.483, 1.598, 1.723, 1.852, 1.975, 2.107, 2.274, 2.496, + 2.515, 2.312, 2.139, 2.002, 1.877, 1.751, 1.629, 1.512, 1.405, 1.318, 1.248, 1.193, 1.149, 1.118, 1.096, 1.085, 1.085, 1.095, 1.114, 1.145, 1.188, 1.246, 1.319, 1.405, 1.508, 1.623, 1.747, 1.873, 1.995, 2.127, 2.297, 2.501, + 2.541, 2.351, 2.161, 2.016, 1.888, 1.762, 1.638, 1.519, 1.411, 1.319, 1.251, 1.197, 1.154, 1.121, 1.099, 1.091, 1.091, 1.099, 1.119, 1.148, 1.192, 1.248, 1.321, 1.411, 1.515, 1.633, 1.758, 1.884, 2.009, 2.149, 2.334, 2.526, + 2.588, 2.394, 2.193, 2.036, 1.905, 1.779, 1.656, 1.537, 1.426, 1.329, 1.255, 1.198, 1.161, 1.139, 1.118, 1.096, 1.095, 1.114, 1.138, 1.158, 1.195, 1.256, 1.333, 1.425, 1.533, 1.651, 1.777, 1.902, 2.028, 2.181, 2.378, 2.571, + 2.639, 2.431, 2.226, 2.067, 1.937, 1.816, 1.695, 1.577, 1.467, 1.368, 1.298, 1.253, 1.198, 1.161, 1.139, 1.129, 1.129, 1.138, 1.158, 1.195, 1.245, 1.296, 1.374, 1.468, 1.574, 1.692, 1.812, 1.934, 2.059, 2.216, 2.418, 2.626, + 2.679, 2.465, 2.261, 2.104, 1.979, 1.862, 1.746, 1.631, 1.522, 1.426, 1.352, 1.297, 1.254, 1.221, 1.201, 1.189, 1.189, 1.198, 1.217, 1.246, 1.293, 1.354, 1.433, 1.526, 1.631, 1.744, 1.859, 1.975, 2.097, 2.252, 2.452, 2.667, + 2.711, 2.511, 2.302, 2.141, 2.018, 1.903, 1.791, 1.678, 1.571, 1.475, 1.401, 1.343, 1.297, 1.268, 1.247, 1.236, 1.236, 1.244, 1.263, 1.291, 1.341, 1.403, 1.484, 1.575, 1.679, 1.791, 1.902, 2.012, 2.136, 2.295, 2.501, 2.698, + 2.759, 2.582, 2.363, 2.184, 2.049, 1.935, 1.824, 1.714, 1.608, 1.511, 1.431, 1.371, 1.325, 1.295, 1.271, 1.259, 1.259, 1.266, 1.291, 1.318, 1.369, 1.436, 1.517, 1.611, 1.716, 1.825, 1.933, 2.047, 2.179, 2.351, 2.571, 2.748, + 2.833, 2.662, 2.433, 2.239, 2.089, 1.968, 1.859, 1.752, 1.646, 1.549, 1.468, 1.411, 1.369, 1.325, 1.296, 1.283, 1.283, 1.292, 1.318, 1.366, 1.411, 1.472, 1.555, 1.651, 1.755, 1.861, 1.969, 2.086, 2.231, 2.422, 2.648, 2.821, + 2.909, 2.729, 2.499, 2.298, 2.141, 2.016, 1.907, 1.805, 1.703, 1.611, 1.539, 1.468, 1.411, 1.375, 1.351, 1.339, 1.339, 1.348, 1.372, 1.411, 1.472, 1.543, 1.613, 1.708, 1.807, 1.909, 2.014, 2.135, 2.288, 2.487, 2.716, 2.897, + 2.981, 2.789, 2.563, 2.358, 2.197, 2.071, 1.968, 1.868, 1.774, 1.684, 1.607, 1.541, 1.489, 1.453, 1.428, 1.417, 1.417, 1.427, 1.451, 1.489, 1.543, 1.611, 1.686, 1.776, 1.871, 1.966, 2.069, 2.191, 2.349, 2.551, 2.775, 2.964, + 3.041, 2.856, 2.629, 2.422, 2.252, 2.127, 2.021, 1.927, 1.834, 1.748, 1.672, 1.604, 1.541, 1.495, 1.483, 1.483, 1.483, 1.483, 1.496, 1.543, 1.608, 1.673, 1.749, 1.835, 1.926, 2.019, 2.122, 2.249, 2.411, 2.614, 2.839, 3.026 + ], + "sigma": 0.00163, + "sigma_Cb": 0.0011 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2964, + "ccm": + [ + 1.72129, -0.45961, -0.26169, + -0.30042, 1.56924, -0.26882, + 0.15133, -1.13293, 1.98161 + ] + }, + { + "ct": 3610, + "ccm": + [ + 1.54474, -0.35082, -0.19391, + -0.36989, 1.67926, -0.30936, + -0.00524, -0.55197, 1.55722 + ] + }, + { + "ct": 4640, + "ccm": + [ + 1.52972, -0.35168, -0.17804, + -0.28309, 1.67098, -0.38788, + 0.01695, -0.57209, 1.55515 + ] + }, + { + "ct": 5910, + "ccm": + [ + 1.56879, -0.42159, -0.14719, + -0.27275, 1.59354, -0.32079, + -0.02862, -0.40662, 1.43525 + ] + }, + { + "ct": 7590, + "ccm": + [ + 1.41424, -0.21092, -0.20332, + -0.17646, 1.71734, -0.54087, + 0.01297, -0.63111, 1.61814 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 3.0, + "max": 15.0, + "default": 4.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 1.0, + "step_fine": 0.25, + "contrast_ratio": 0.75, + "pdaf_gain": -0.02, + "pdaf_squelch": 0.125, + "max_slew": 2.0, + "pdaf_frames": 20, + "dropout_frames": 6, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 16, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 445, 15.0, 925 ] + } + }, + { + "rpi.cac": + { + "strength": 1.0, + "lut_rx": + [ + -0.21, -0.12, -0.06, -0.04, -0.03, -0.0, 0.02, 0.08, 0.21, + -0.2, -0.12, -0.07, -0.05, -0.01, 0.02, 0.02, 0.06, 0.19, + -0.18, -0.12, -0.09, -0.07, -0.01, 0.03, 0.03, 0.04, 0.13, + -0.15, -0.11, -0.1, -0.09, -0.01, 0.04, 0.04, 0.04, 0.09, + -0.15, -0.11, -0.1, -0.09, -0.02, 0.05, 0.05, 0.05, 0.08, + -0.16, -0.11, -0.08, -0.07, -0.02, 0.05, 0.06, 0.07, 0.1, + -0.18, -0.1, -0.07, -0.05, -0.01, 0.03, 0.05, 0.08, 0.15, + -0.21, -0.11, -0.06, -0.04, -0.01, 0.02, 0.04, 0.09, 0.22, + -0.23, -0.14, -0.05, -0.03, -0.01, 0.01, 0.03, 0.1, 0.23 + ], + "lut_ry": + [ + -0.13, -0.08, -0.06, -0.08, -0.08, -0.06, -0.04, -0.06, -0.08, + -0.09, -0.05, -0.05, -0.09, -0.1, -0.08, -0.05, -0.04, -0.06, + -0.04, -0.05, -0.06, -0.1, -0.13, -0.1, -0.06, -0.04, -0.02, + -0.03, -0.04, -0.06, -0.09, -0.11, -0.1, -0.06, -0.03, 0.01, + -0.01, -0.01, -0.03, -0.03, -0.03, -0.04, -0.03, -0.01, 0.02, + 0.03, 0.01, -0.01, 0.0, 0.01, 0.01, -0.0, 0.01, 0.03, + 0.05, 0.02, 0.01, 0.02, 0.03, 0.02, 0.01, 0.03, 0.07, + 0.08, 0.03, 0.01, 0.01, 0.02, 0.02, 0.02, 0.05, 0.12, + 0.11, 0.07, 0.01, 0.0, -0.0, 0.01, 0.03, 0.07, 0.14 + ], + "lut_bx": + [ + 0.27, 0.13, 0.03, -0.01, -0.01, -0.0, -0.04, -0.11, -0.29, + 0.23, 0.1, 0.02, -0.01, -0.02, -0.01, -0.03, -0.1, -0.28, + 0.22, 0.08, 0.0, -0.01, -0.02, -0.02, -0.02, -0.08, -0.25, + 0.2, 0.08, 0.01, 0.0, -0.01, -0.02, -0.01, -0.07, -0.22, + 0.19, 0.08, 0.01, 0.0, -0.01, -0.02, -0.02, -0.06, -0.21, + 0.2, 0.08, 0.01, 0.0, -0.01, -0.02, -0.02, -0.07, -0.22, + 0.21, 0.09, 0.01, -0.01, -0.02, -0.02, -0.03, -0.09, -0.26, + 0.21, 0.11, 0.02, -0.01, -0.01, -0.02, -0.04, -0.11, -0.28, + 0.23, 0.13, 0.04, -0.01, -0.01, -0.01, -0.06, -0.13, -0.31 + ], + "lut_by": + [ + 0.17, 0.11, 0.07, 0.05, 0.04, 0.05, 0.07, 0.12, 0.19, + 0.11, 0.06, 0.04, 0.04, 0.04, 0.03, 0.04, 0.06, 0.13, + 0.06, 0.03, 0.02, 0.04, 0.05, 0.04, 0.03, 0.03, 0.06, + 0.02, 0.02, 0.03, 0.04, 0.06, 0.05, 0.03, 0.02, 0.01, + -0.0, 0.01, 0.03, 0.04, 0.04, 0.04, 0.02, -0.0, -0.03, + -0.04, -0.01, 0.02, 0.02, 0.02, 0.02, 0.01, -0.02, -0.09, + -0.08, -0.01, 0.04, 0.06, 0.06, 0.05, 0.03, -0.03, -0.14, + -0.1, -0.04, 0.04, 0.08, 0.08, 0.06, 0.02, -0.05, -0.18, + -0.15, -0.08, 0.02, 0.09, 0.11, 0.08, 0.01, -0.09, -0.22 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx708_noir.json b/src/ipa/rpi/pisp/data/imx708_noir.json new file mode 100644 index 00000000..e69afb0c --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx708_noir.json @@ -0,0 +1,1233 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 20716, + "reference_gain": 1.12, + "reference_aperture": 1.0, + "reference_lux": 810, + "reference_Y": 13994 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 1.856 + } + }, + { + "rpi.geq": + { + "offset": 221, + "slope": 0.00226 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.532, 1.534, 1.535, 1.538, 1.538, 1.533, 1.529, 1.515, 1.506, 1.492, 1.477, 1.465, 1.453, 1.444, 1.437, 1.433, 1.433, 1.435, 1.441, 1.449, 1.461, 1.474, 1.485, 1.499, 1.511, 1.519, 1.525, 1.526, 1.526, 1.523, 1.517, 1.516, + 1.532, 1.534, 1.537, 1.538, 1.537, 1.534, 1.525, 1.515, 1.502, 1.486, 1.474, 1.458, 1.449, 1.438, 1.429, 1.427, 1.426, 1.429, 1.436, 1.444, 1.456, 1.468, 1.483, 1.497, 1.509, 1.518, 1.524, 1.526, 1.526, 1.523, 1.521, 1.516, + 1.532, 1.534, 1.537, 1.538, 1.536, 1.533, 1.524, 1.512, 1.499, 1.483, 1.468, 1.453, 1.439, 1.429, 1.421, 1.419, 1.419, 1.419, 1.427, 1.438, 1.451, 1.464, 1.479, 1.494, 1.506, 1.516, 1.523, 1.526, 1.526, 1.524, 1.521, 1.518, + 1.533, 1.536, 1.537, 1.537, 1.535, 1.532, 1.521, 1.507, 1.491, 1.474, 1.456, 1.441, 1.429, 1.418, 1.409, 1.406, 1.406, 1.408, 1.415, 1.426, 1.439, 1.453, 1.471, 1.485, 1.501, 1.511, 1.522, 1.524, 1.526, 1.525, 1.522, 1.519, + 1.537, 1.538, 1.539, 1.538, 1.534, 1.525, 1.513, 1.495, 1.477, 1.459, 1.443, 1.427, 1.413, 1.402, 1.394, 1.391, 1.391, 1.393, 1.399, 1.409, 1.424, 1.439, 1.455, 1.472, 1.489, 1.503, 1.515, 1.523, 1.526, 1.527, 1.525, 1.523, + 1.538, 1.539, 1.541, 1.539, 1.531, 1.519, 1.503, 1.484, 1.466, 1.445, 1.427, 1.413, 1.401, 1.386, 1.378, 1.373, 1.373, 1.376, 1.386, 1.398, 1.409, 1.424, 1.441, 1.459, 1.477, 1.495, 1.509, 1.519, 1.526, 1.528, 1.528, 1.526, + 1.539, 1.541, 1.541, 1.539, 1.529, 1.516, 1.498, 1.479, 1.456, 1.437, 1.417, 1.401, 1.386, 1.378, 1.369, 1.363, 1.363, 1.367, 1.376, 1.386, 1.399, 1.413, 1.432, 1.451, 1.472, 1.491, 1.507, 1.517, 1.525, 1.527, 1.527, 1.527, + 1.539, 1.539, 1.539, 1.538, 1.529, 1.515, 1.497, 1.476, 1.454, 1.433, 1.411, 1.395, 1.381, 1.368, 1.361, 1.356, 1.356, 1.359, 1.367, 1.379, 1.393, 1.409, 1.428, 1.448, 1.471, 1.489, 1.505, 1.516, 1.524, 1.527, 1.527, 1.527, + 1.539, 1.539, 1.539, 1.537, 1.528, 1.513, 1.493, 1.471, 1.449, 1.426, 1.406, 1.387, 1.373, 1.361, 1.352, 1.348, 1.348, 1.351, 1.359, 1.372, 1.387, 1.403, 1.422, 1.443, 1.465, 1.484, 1.503, 1.516, 1.525, 1.527, 1.528, 1.526, + 1.541, 1.542, 1.539, 1.537, 1.524, 1.506, 1.485, 1.461, 1.438, 1.416, 1.395, 1.377, 1.362, 1.352, 1.344, 1.339, 1.339, 1.342, 1.351, 1.362, 1.376, 1.393, 1.412, 1.434, 1.455, 1.477, 1.495, 1.514, 1.524, 1.528, 1.529, 1.529, + 1.543, 1.544, 1.543, 1.534, 1.518, 1.499, 1.476, 1.452, 1.427, 1.405, 1.386, 1.367, 1.354, 1.344, 1.338, 1.329, 1.329, 1.335, 1.342, 1.352, 1.367, 1.382, 1.402, 1.424, 1.445, 1.469, 1.491, 1.507, 1.522, 1.528, 1.529, 1.532, + 1.544, 1.544, 1.542, 1.534, 1.518, 1.499, 1.474, 1.449, 1.425, 1.401, 1.379, 1.362, 1.348, 1.338, 1.329, 1.324, 1.325, 1.329, 1.335, 1.347, 1.361, 1.378, 1.397, 1.421, 1.443, 1.467, 1.489, 1.507, 1.521, 1.529, 1.532, 1.533, + 1.543, 1.543, 1.541, 1.534, 1.519, 1.499, 1.474, 1.448, 1.424, 1.399, 1.377, 1.359, 1.346, 1.333, 1.324, 1.322, 1.321, 1.324, 1.332, 1.344, 1.359, 1.376, 1.397, 1.419, 1.443, 1.467, 1.489, 1.508, 1.521, 1.528, 1.531, 1.532, + 1.543, 1.542, 1.541, 1.533, 1.519, 1.499, 1.474, 1.448, 1.422, 1.399, 1.376, 1.358, 1.344, 1.331, 1.322, 1.319, 1.319, 1.321, 1.331, 1.342, 1.357, 1.375, 1.396, 1.419, 1.443, 1.467, 1.489, 1.508, 1.521, 1.529, 1.531, 1.532, + 1.543, 1.542, 1.541, 1.532, 1.518, 1.496, 1.471, 1.445, 1.418, 1.393, 1.373, 1.354, 1.341, 1.329, 1.319, 1.317, 1.316, 1.319, 1.327, 1.338, 1.353, 1.371, 1.392, 1.415, 1.439, 1.465, 1.485, 1.507, 1.519, 1.529, 1.531, 1.531, + 1.545, 1.544, 1.542, 1.531, 1.515, 1.493, 1.467, 1.441, 1.414, 1.391, 1.369, 1.351, 1.337, 1.326, 1.318, 1.314, 1.314, 1.317, 1.325, 1.335, 1.351, 1.367, 1.388, 1.411, 1.436, 1.461, 1.483, 1.505, 1.519, 1.531, 1.533, 1.533, + 1.545, 1.544, 1.541, 1.531, 1.515, 1.493, 1.467, 1.441, 1.414, 1.391, 1.369, 1.351, 1.337, 1.326, 1.318, 1.314, 1.314, 1.317, 1.325, 1.335, 1.351, 1.367, 1.388, 1.411, 1.436, 1.461, 1.483, 1.505, 1.521, 1.531, 1.534, 1.534, + 1.545, 1.544, 1.541, 1.534, 1.519, 1.496, 1.471, 1.446, 1.419, 1.392, 1.372, 1.354, 1.338, 1.328, 1.319, 1.316, 1.315, 1.319, 1.327, 1.338, 1.353, 1.371, 1.392, 1.416, 1.441, 1.465, 1.489, 1.511, 1.522, 1.531, 1.534, 1.535, + 1.544, 1.544, 1.542, 1.537, 1.524, 1.501, 1.476, 1.449, 1.424, 1.399, 1.377, 1.359, 1.344, 1.332, 1.324, 1.319, 1.319, 1.323, 1.331, 1.343, 1.358, 1.374, 1.396, 1.419, 1.445, 1.471, 1.493, 1.512, 1.525, 1.532, 1.534, 1.534, + 1.545, 1.545, 1.543, 1.538, 1.524, 1.503, 1.479, 1.452, 1.426, 1.402, 1.381, 1.362, 1.348, 1.337, 1.329, 1.324, 1.324, 1.328, 1.335, 1.347, 1.361, 1.379, 1.399, 1.423, 1.447, 1.471, 1.493, 1.513, 1.526, 1.533, 1.534, 1.535, + 1.546, 1.546, 1.544, 1.539, 1.525, 1.504, 1.479, 1.453, 1.428, 1.404, 1.383, 1.365, 1.352, 1.339, 1.333, 1.329, 1.329, 1.333, 1.339, 1.349, 1.363, 1.381, 1.402, 1.424, 1.448, 1.472, 1.494, 1.514, 1.526, 1.534, 1.534, 1.534, + 1.546, 1.546, 1.544, 1.539, 1.526, 1.505, 1.483, 1.457, 1.432, 1.407, 1.389, 1.371, 1.357, 1.347, 1.339, 1.333, 1.333, 1.339, 1.345, 1.354, 1.368, 1.386, 1.406, 1.428, 1.453, 1.475, 1.496, 1.515, 1.527, 1.535, 1.535, 1.535, + 1.545, 1.545, 1.545, 1.541, 1.529, 1.513, 1.491, 1.467, 1.441, 1.418, 1.399, 1.379, 1.366, 1.355, 1.347, 1.341, 1.341, 1.345, 1.354, 1.364, 1.378, 1.395, 1.415, 1.436, 1.459, 1.483, 1.503, 1.519, 1.531, 1.534, 1.535, 1.534, + 1.544, 1.545, 1.545, 1.544, 1.535, 1.519, 1.499, 1.476, 1.451, 1.428, 1.409, 1.391, 1.377, 1.366, 1.356, 1.352, 1.352, 1.355, 1.364, 1.374, 1.388, 1.405, 1.426, 1.447, 1.469, 1.492, 1.509, 1.523, 1.532, 1.535, 1.535, 1.533, + 1.544, 1.545, 1.546, 1.545, 1.537, 1.523, 1.504, 1.482, 1.458, 1.436, 1.418, 1.401, 1.385, 1.377, 1.367, 1.362, 1.362, 1.365, 1.373, 1.385, 1.398, 1.415, 1.434, 1.455, 1.477, 1.495, 1.514, 1.525, 1.533, 1.536, 1.535, 1.533, + 1.545, 1.546, 1.547, 1.545, 1.538, 1.525, 1.508, 1.486, 1.465, 1.444, 1.424, 1.408, 1.394, 1.385, 1.377, 1.371, 1.371, 1.373, 1.384, 1.392, 1.405, 1.421, 1.441, 1.459, 1.481, 1.499, 1.516, 1.528, 1.534, 1.536, 1.536, 1.533, + 1.544, 1.546, 1.547, 1.547, 1.541, 1.531, 1.514, 1.494, 1.474, 1.454, 1.434, 1.421, 1.408, 1.394, 1.386, 1.382, 1.382, 1.385, 1.392, 1.405, 1.416, 1.432, 1.449, 1.468, 1.488, 1.505, 1.519, 1.531, 1.536, 1.537, 1.536, 1.533, + 1.544, 1.546, 1.548, 1.548, 1.545, 1.536, 1.522, 1.506, 1.486, 1.467, 1.451, 1.434, 1.421, 1.408, 1.401, 1.396, 1.396, 1.399, 1.407, 1.416, 1.431, 1.447, 1.463, 1.481, 1.499, 1.513, 1.526, 1.534, 1.537, 1.537, 1.534, 1.531, + 1.543, 1.545, 1.547, 1.549, 1.549, 1.543, 1.531, 1.517, 1.501, 1.483, 1.465, 1.451, 1.438, 1.425, 1.417, 1.412, 1.412, 1.418, 1.423, 1.433, 1.447, 1.462, 1.479, 1.493, 1.511, 1.524, 1.531, 1.536, 1.538, 1.537, 1.533, 1.531, + 1.542, 1.545, 1.548, 1.551, 1.551, 1.546, 1.539, 1.524, 1.511, 1.493, 1.479, 1.464, 1.451, 1.442, 1.433, 1.429, 1.429, 1.434, 1.439, 1.449, 1.462, 1.474, 1.491, 1.505, 1.519, 1.529, 1.536, 1.539, 1.539, 1.537, 1.533, 1.531, + 1.541, 1.546, 1.549, 1.552, 1.553, 1.551, 1.544, 1.533, 1.521, 1.505, 1.489, 1.477, 1.464, 1.455, 1.447, 1.443, 1.443, 1.446, 1.451, 1.462, 1.472, 1.487, 1.499, 1.514, 1.525, 1.535, 1.541, 1.541, 1.541, 1.539, 1.533, 1.531, + 1.541, 1.546, 1.549, 1.553, 1.554, 1.552, 1.546, 1.537, 1.524, 1.512, 1.499, 1.485, 1.474, 1.464, 1.455, 1.451, 1.451, 1.452, 1.461, 1.469, 1.481, 1.495, 1.506, 1.518, 1.529, 1.539, 1.541, 1.542, 1.541, 1.539, 1.533, 1.529 + ] + }, + { + "ct": 5000, + "table": + [ + 2.586, 2.591, 2.597, 2.601, 2.601, 2.599, 2.592, 2.576, 2.561, 2.541, 2.523, 2.503, 2.486, 2.471, 2.459, 2.452, 2.452, 2.454, 2.462, 2.478, 2.495, 2.512, 2.531, 2.555, 2.568, 2.579, 2.587, 2.588, 2.585, 2.579, 2.573, 2.566, + 2.587, 2.592, 2.598, 2.601, 2.601, 2.599, 2.587, 2.574, 2.556, 2.532, 2.512, 2.491, 2.474, 2.462, 2.449, 2.443, 2.439, 2.443, 2.454, 2.464, 2.485, 2.505, 2.525, 2.548, 2.566, 2.578, 2.585, 2.588, 2.586, 2.579, 2.575, 2.567, + 2.587, 2.593, 2.598, 2.602, 2.601, 2.597, 2.584, 2.569, 2.551, 2.527, 2.503, 2.482, 2.464, 2.448, 2.434, 2.428, 2.427, 2.431, 2.439, 2.455, 2.474, 2.498, 2.521, 2.541, 2.564, 2.577, 2.585, 2.588, 2.589, 2.581, 2.576, 2.569, + 2.593, 2.596, 2.601, 2.603, 2.601, 2.594, 2.583, 2.563, 2.539, 2.514, 2.491, 2.466, 2.445, 2.429, 2.417, 2.409, 2.408, 2.411, 2.421, 2.437, 2.457, 2.481, 2.507, 2.531, 2.555, 2.572, 2.583, 2.588, 2.588, 2.585, 2.579, 2.575, + 2.597, 2.599, 2.604, 2.603, 2.599, 2.587, 2.567, 2.548, 2.522, 2.493, 2.467, 2.443, 2.419, 2.406, 2.391, 2.385, 2.385, 2.387, 2.397, 2.413, 2.435, 2.459, 2.486, 2.509, 2.538, 2.559, 2.574, 2.586, 2.588, 2.586, 2.582, 2.579, + 2.601, 2.603, 2.606, 2.604, 2.596, 2.578, 2.556, 2.531, 2.501, 2.471, 2.444, 2.419, 2.402, 2.381, 2.365, 2.359, 2.359, 2.361, 2.374, 2.396, 2.413, 2.435, 2.465, 2.493, 2.517, 2.542, 2.562, 2.582, 2.588, 2.587, 2.586, 2.584, + 2.601, 2.604, 2.605, 2.604, 2.593, 2.575, 2.547, 2.522, 2.488, 2.458, 2.432, 2.402, 2.381, 2.364, 2.349, 2.338, 2.338, 2.345, 2.359, 2.374, 2.396, 2.423, 2.453, 2.481, 2.511, 2.539, 2.561, 2.581, 2.586, 2.588, 2.588, 2.586, + 2.599, 2.602, 2.604, 2.602, 2.592, 2.572, 2.546, 2.516, 2.485, 2.451, 2.422, 2.393, 2.368, 2.349, 2.336, 2.328, 2.328, 2.333, 2.345, 2.365, 2.389, 2.417, 2.447, 2.478, 2.509, 2.537, 2.561, 2.577, 2.585, 2.588, 2.588, 2.587, + 2.601, 2.602, 2.604, 2.601, 2.589, 2.569, 2.539, 2.509, 2.473, 2.442, 2.409, 2.379, 2.357, 2.336, 2.323, 2.315, 2.315, 2.322, 2.334, 2.354, 2.377, 2.406, 2.436, 2.469, 2.503, 2.529, 2.558, 2.574, 2.585, 2.588, 2.589, 2.587, + 2.601, 2.606, 2.606, 2.601, 2.581, 2.557, 2.525, 2.493, 2.459, 2.426, 2.394, 2.365, 2.339, 2.322, 2.308, 2.301, 2.301, 2.305, 2.322, 2.337, 2.361, 2.389, 2.422, 2.454, 2.485, 2.519, 2.546, 2.568, 2.584, 2.589, 2.589, 2.589, + 2.608, 2.608, 2.606, 2.597, 2.576, 2.548, 2.515, 2.481, 2.444, 2.409, 2.376, 2.346, 2.323, 2.308, 2.293, 2.282, 2.281, 2.291, 2.305, 2.322, 2.348, 2.371, 2.403, 2.439, 2.472, 2.508, 2.538, 2.565, 2.582, 2.589, 2.592, 2.593, + 2.608, 2.608, 2.605, 2.596, 2.575, 2.547, 2.511, 2.474, 2.435, 2.401, 2.366, 2.339, 2.312, 2.293, 2.281, 2.274, 2.274, 2.281, 2.291, 2.311, 2.334, 2.364, 2.399, 2.433, 2.471, 2.506, 2.538, 2.564, 2.581, 2.591, 2.594, 2.595, + 2.605, 2.606, 2.605, 2.595, 2.575, 2.547, 2.511, 2.474, 2.433, 2.397, 2.363, 2.333, 2.309, 2.291, 2.274, 2.267, 2.265, 2.272, 2.284, 2.307, 2.331, 2.361, 2.395, 2.431, 2.469, 2.503, 2.539, 2.567, 2.584, 2.591, 2.595, 2.595, + 2.605, 2.606, 2.605, 2.595, 2.575, 2.547, 2.509, 2.473, 2.431, 2.395, 2.361, 2.332, 2.306, 2.285, 2.267, 2.261, 2.262, 2.265, 2.281, 2.302, 2.329, 2.359, 2.395, 2.429, 2.468, 2.503, 2.539, 2.567, 2.583, 2.593, 2.595, 2.595, + 2.608, 2.607, 2.606, 2.592, 2.572, 2.543, 2.506, 2.468, 2.426, 2.389, 2.354, 2.327, 2.299, 2.279, 2.262, 2.258, 2.257, 2.262, 2.276, 2.297, 2.321, 2.352, 2.387, 2.425, 2.464, 2.498, 2.532, 2.565, 2.582, 2.592, 2.595, 2.596, + 2.611, 2.609, 2.605, 2.592, 2.571, 2.538, 2.499, 2.463, 2.421, 2.384, 2.351, 2.322, 2.295, 2.276, 2.259, 2.254, 2.254, 2.256, 2.273, 2.292, 2.318, 2.347, 2.383, 2.418, 2.456, 2.491, 2.529, 2.562, 2.581, 2.593, 2.597, 2.598, + 2.609, 2.609, 2.606, 2.593, 2.571, 2.538, 2.499, 2.463, 2.421, 2.384, 2.351, 2.321, 2.295, 2.276, 2.259, 2.251, 2.251, 2.256, 2.273, 2.292, 2.318, 2.347, 2.383, 2.418, 2.456, 2.491, 2.529, 2.559, 2.582, 2.595, 2.597, 2.599, + 2.609, 2.609, 2.607, 2.597, 2.576, 2.543, 2.507, 2.467, 2.427, 2.388, 2.356, 2.323, 2.297, 2.278, 2.262, 2.256, 2.255, 2.262, 2.275, 2.296, 2.321, 2.351, 2.388, 2.425, 2.464, 2.502, 2.534, 2.563, 2.586, 2.595, 2.598, 2.599, + 2.609, 2.609, 2.608, 2.601, 2.581, 2.547, 2.513, 2.475, 2.434, 2.398, 2.362, 2.332, 2.307, 2.287, 2.269, 2.263, 2.263, 2.269, 2.281, 2.304, 2.328, 2.358, 2.394, 2.429, 2.469, 2.508, 2.538, 2.568, 2.589, 2.597, 2.598, 2.598, + 2.609, 2.611, 2.609, 2.601, 2.583, 2.549, 2.518, 2.478, 2.439, 2.402, 2.367, 2.337, 2.313, 2.293, 2.279, 2.271, 2.269, 2.277, 2.291, 2.311, 2.336, 2.363, 2.399, 2.435, 2.473, 2.509, 2.541, 2.571, 2.591, 2.598, 2.599, 2.599, + 2.611, 2.611, 2.609, 2.602, 2.585, 2.551, 2.519, 2.481, 2.442, 2.406, 2.374, 2.342, 2.318, 2.297, 2.287, 2.279, 2.278, 2.287, 2.297, 2.315, 2.339, 2.368, 2.402, 2.438, 2.476, 2.511, 2.545, 2.571, 2.591, 2.599, 2.601, 2.599, + 2.611, 2.611, 2.609, 2.604, 2.587, 2.557, 2.521, 2.485, 2.447, 2.412, 2.379, 2.352, 2.328, 2.309, 2.297, 2.288, 2.287, 2.297, 2.308, 2.327, 2.349, 2.377, 2.408, 2.446, 2.481, 2.517, 2.547, 2.573, 2.591, 2.599, 2.601, 2.599, + 2.608, 2.609, 2.609, 2.606, 2.592, 2.564, 2.533, 2.498, 2.462, 2.427, 2.394, 2.364, 2.343, 2.326, 2.309, 2.302, 2.302, 2.308, 2.324, 2.341, 2.362, 2.391, 2.425, 2.458, 2.494, 2.526, 2.555, 2.584, 2.593, 2.599, 2.599, 2.599, + 2.608, 2.609, 2.609, 2.609, 2.597, 2.574, 2.547, 2.511, 2.475, 2.438, 2.411, 2.381, 2.359, 2.342, 2.327, 2.318, 2.318, 2.325, 2.341, 2.358, 2.377, 2.404, 2.439, 2.469, 2.507, 2.537, 2.564, 2.587, 2.596, 2.598, 2.598, 2.597, + 2.609, 2.609, 2.611, 2.609, 2.599, 2.579, 2.551, 2.519, 2.486, 2.453, 2.425, 2.397, 2.375, 2.358, 2.345, 2.336, 2.336, 2.341, 2.355, 2.372, 2.393, 2.419, 2.452, 2.481, 2.516, 2.542, 2.571, 2.591, 2.597, 2.599, 2.598, 2.595, + 2.607, 2.611, 2.613, 2.611, 2.605, 2.586, 2.561, 2.529, 2.495, 2.462, 2.435, 2.409, 2.387, 2.374, 2.359, 2.351, 2.351, 2.356, 2.372, 2.385, 2.406, 2.431, 2.462, 2.488, 2.524, 2.551, 2.573, 2.591, 2.598, 2.599, 2.598, 2.596, + 2.606, 2.609, 2.613, 2.613, 2.607, 2.591, 2.565, 2.539, 2.507, 2.477, 2.449, 2.425, 2.409, 2.387, 2.376, 2.369, 2.369, 2.374, 2.385, 2.406, 2.422, 2.446, 2.473, 2.502, 2.534, 2.557, 2.578, 2.595, 2.599, 2.601, 2.598, 2.595, + 2.606, 2.611, 2.613, 2.614, 2.611, 2.598, 2.581, 2.553, 2.523, 2.496, 2.471, 2.449, 2.425, 2.409, 2.398, 2.391, 2.391, 2.395, 2.408, 2.422, 2.445, 2.468, 2.493, 2.522, 2.549, 2.569, 2.589, 2.601, 2.603, 2.602, 2.596, 2.593, + 2.605, 2.609, 2.613, 2.616, 2.614, 2.607, 2.591, 2.571, 2.545, 2.518, 2.494, 2.471, 2.452, 2.435, 2.423, 2.417, 2.417, 2.421, 2.431, 2.449, 2.467, 2.493, 2.516, 2.542, 2.566, 2.585, 2.596, 2.606, 2.605, 2.602, 2.595, 2.593, + 2.604, 2.608, 2.616, 2.617, 2.618, 2.613, 2.602, 2.584, 2.559, 2.536, 2.514, 2.493, 2.476, 2.459, 2.445, 2.439, 2.439, 2.445, 2.456, 2.471, 2.493, 2.511, 2.534, 2.559, 2.579, 2.592, 2.607, 2.608, 2.607, 2.604, 2.595, 2.592, + 2.603, 2.609, 2.615, 2.619, 2.623, 2.619, 2.608, 2.594, 2.573, 2.551, 2.532, 2.512, 2.493, 2.477, 2.468, 2.462, 2.462, 2.468, 2.476, 2.494, 2.509, 2.528, 2.551, 2.574, 2.589, 2.604, 2.611, 2.611, 2.611, 2.604, 2.598, 2.592, + 2.602, 2.607, 2.613, 2.621, 2.624, 2.621, 2.617, 2.601, 2.585, 2.567, 2.544, 2.521, 2.507, 2.493, 2.478, 2.474, 2.475, 2.477, 2.489, 2.505, 2.523, 2.544, 2.563, 2.584, 2.598, 2.609, 2.612, 2.613, 2.613, 2.608, 2.599, 2.591 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.263, 3.263, 3.264, 3.269, 3.274, 3.275, 3.277, 3.281, 3.283, 3.285, 3.289, 3.292, 3.291, 3.291, 3.294, 3.298, 3.302, 3.305, 3.303, 3.301, 3.296, 3.294, 3.293, 3.293, 3.295, 3.295, 3.295, 3.287, 3.285, 3.279, 3.282, 3.285, + 3.259, 3.259, 3.262, 3.268, 3.271, 3.273, 3.273, 3.274, 3.277, 3.278, 3.282, 3.285, 3.286, 3.286, 3.288, 3.289, 3.292, 3.291, 3.293, 3.291, 3.288, 3.288, 3.288, 3.288, 3.288, 3.287, 3.287, 3.283, 3.281, 3.276, 3.277, 3.281, + 3.259, 3.259, 3.262, 3.269, 3.272, 3.274, 3.273, 3.273, 3.273, 3.276, 3.278, 3.279, 3.281, 3.282, 3.283, 3.283, 3.285, 3.286, 3.286, 3.285, 3.283, 3.282, 3.282, 3.286, 3.285, 3.285, 3.285, 3.282, 3.281, 3.276, 3.275, 3.279, + 3.267, 3.266, 3.269, 3.273, 3.274, 3.277, 3.276, 3.275, 3.274, 3.277, 3.277, 3.278, 3.279, 3.279, 3.279, 3.279, 3.281, 3.283, 3.283, 3.281, 3.281, 3.281, 3.282, 3.287, 3.287, 3.288, 3.287, 3.286, 3.283, 3.277, 3.278, 3.281, + 3.268, 3.271, 3.274, 3.277, 3.283, 3.286, 3.284, 3.281, 3.278, 3.278, 3.279, 3.279, 3.281, 3.281, 3.281, 3.281, 3.282, 3.283, 3.284, 3.283, 3.282, 3.282, 3.284, 3.288, 3.289, 3.291, 3.291, 3.288, 3.288, 3.283, 3.286, 3.288, + 3.272, 3.275, 3.279, 3.283, 3.288, 3.289, 3.289, 3.287, 3.282, 3.284, 3.282, 3.284, 3.284, 3.285, 3.284, 3.285, 3.285, 3.288, 3.287, 3.286, 3.283, 3.283, 3.285, 3.289, 3.292, 3.292, 3.293, 3.292, 3.291, 3.288, 3.289, 3.293, + 3.276, 3.278, 3.282, 3.289, 3.293, 3.293, 3.291, 3.289, 3.287, 3.286, 3.285, 3.285, 3.286, 3.286, 3.287, 3.288, 3.289, 3.289, 3.289, 3.288, 3.285, 3.283, 3.286, 3.289, 3.292, 3.294, 3.294, 3.294, 3.293, 3.289, 3.292, 3.293, + 3.279, 3.281, 3.286, 3.293, 3.297, 3.298, 3.295, 3.292, 3.288, 3.287, 3.285, 3.287, 3.288, 3.288, 3.289, 3.291, 3.292, 3.293, 3.291, 3.288, 3.286, 3.285, 3.285, 3.291, 3.294, 3.295, 3.297, 3.297, 3.298, 3.293, 3.294, 3.294, + 3.281, 3.286, 3.291, 3.298, 3.301, 3.301, 3.299, 3.295, 3.289, 3.288, 3.286, 3.288, 3.289, 3.292, 3.293, 3.292, 3.295, 3.296, 3.295, 3.291, 3.287, 3.286, 3.286, 3.289, 3.295, 3.297, 3.298, 3.301, 3.301, 3.298, 3.297, 3.297, + 3.284, 3.289, 3.295, 3.302, 3.303, 3.303, 3.301, 3.298, 3.294, 3.292, 3.289, 3.293, 3.296, 3.297, 3.297, 3.297, 3.297, 3.298, 3.298, 3.296, 3.289, 3.288, 3.287, 3.294, 3.298, 3.301, 3.304, 3.305, 3.304, 3.299, 3.299, 3.302, + 3.291, 3.292, 3.299, 3.305, 3.308, 3.305, 3.304, 3.302, 3.298, 3.295, 3.295, 3.298, 3.299, 3.302, 3.303, 3.302, 3.301, 3.301, 3.301, 3.299, 3.296, 3.291, 3.292, 3.297, 3.301, 3.304, 3.306, 3.309, 3.308, 3.302, 3.301, 3.304, + 3.292, 3.297, 3.303, 3.309, 3.312, 3.311, 3.308, 3.304, 3.301, 3.299, 3.298, 3.299, 3.303, 3.305, 3.306, 3.305, 3.305, 3.303, 3.303, 3.301, 3.299, 3.294, 3.294, 3.297, 3.302, 3.305, 3.309, 3.311, 3.311, 3.305, 3.305, 3.306, + 3.295, 3.298, 3.305, 3.309, 3.313, 3.313, 3.312, 3.307, 3.303, 3.301, 3.299, 3.301, 3.304, 3.307, 3.308, 3.306, 3.306, 3.306, 3.306, 3.302, 3.299, 3.296, 3.295, 3.298, 3.303, 3.306, 3.311, 3.312, 3.312, 3.307, 3.308, 3.309, + 3.297, 3.298, 3.303, 3.309, 3.313, 3.313, 3.311, 3.307, 3.303, 3.301, 3.299, 3.299, 3.305, 3.307, 3.307, 3.306, 3.306, 3.306, 3.305, 3.299, 3.297, 3.294, 3.294, 3.298, 3.303, 3.305, 3.311, 3.312, 3.313, 3.308, 3.311, 3.309, + 3.297, 3.298, 3.304, 3.309, 3.312, 3.313, 3.311, 3.308, 3.304, 3.302, 3.301, 3.301, 3.306, 3.307, 3.308, 3.306, 3.306, 3.307, 3.306, 3.302, 3.297, 3.294, 3.294, 3.299, 3.305, 3.306, 3.309, 3.312, 3.311, 3.306, 3.308, 3.309, + 3.298, 3.299, 3.306, 3.311, 3.315, 3.314, 3.311, 3.308, 3.305, 3.303, 3.303, 3.304, 3.307, 3.309, 3.309, 3.308, 3.308, 3.307, 3.306, 3.302, 3.298, 3.296, 3.296, 3.298, 3.304, 3.306, 3.308, 3.309, 3.314, 3.308, 3.309, 3.308, + 3.299, 3.301, 3.307, 3.313, 3.316, 3.316, 3.313, 3.311, 3.307, 3.305, 3.305, 3.307, 3.309, 3.311, 3.312, 3.311, 3.309, 3.309, 3.308, 3.306, 3.301, 3.298, 3.297, 3.301, 3.305, 3.309, 3.309, 3.311, 3.313, 3.306, 3.308, 3.307, + 3.301, 3.301, 3.307, 3.314, 3.317, 3.318, 3.314, 3.311, 3.308, 3.306, 3.306, 3.308, 3.311, 3.311, 3.312, 3.311, 3.309, 3.309, 3.309, 3.306, 3.302, 3.298, 3.298, 3.301, 3.305, 3.309, 3.311, 3.311, 3.312, 3.309, 3.308, 3.308, + 3.301, 3.302, 3.307, 3.316, 3.319, 3.319, 3.315, 3.312, 3.309, 3.306, 3.307, 3.309, 3.311, 3.312, 3.311, 3.311, 3.309, 3.309, 3.309, 3.306, 3.303, 3.299, 3.298, 3.302, 3.305, 3.308, 3.309, 3.309, 3.309, 3.303, 3.306, 3.307, + 3.301, 3.303, 3.308, 3.315, 3.318, 3.318, 3.316, 3.313, 3.311, 3.307, 3.307, 3.308, 3.311, 3.311, 3.311, 3.308, 3.308, 3.307, 3.307, 3.306, 3.303, 3.299, 3.297, 3.301, 3.303, 3.306, 3.309, 3.308, 3.306, 3.303, 3.304, 3.306, + 3.302, 3.304, 3.306, 3.316, 3.318, 3.318, 3.317, 3.315, 3.311, 3.308, 3.309, 3.311, 3.311, 3.312, 3.311, 3.307, 3.306, 3.307, 3.308, 3.307, 3.304, 3.299, 3.298, 3.301, 3.303, 3.305, 3.306, 3.305, 3.304, 3.302, 3.303, 3.306, + 3.302, 3.304, 3.306, 3.312, 3.316, 3.317, 3.317, 3.313, 3.311, 3.309, 3.309, 3.311, 3.311, 3.312, 3.309, 3.307, 3.306, 3.306, 3.308, 3.307, 3.304, 3.298, 3.297, 3.302, 3.304, 3.305, 3.306, 3.305, 3.304, 3.299, 3.302, 3.303, + 3.299, 3.299, 3.306, 3.309, 3.315, 3.316, 3.316, 3.312, 3.309, 3.308, 3.308, 3.309, 3.311, 3.311, 3.307, 3.305, 3.305, 3.305, 3.306, 3.304, 3.299, 3.297, 3.296, 3.301, 3.302, 3.304, 3.303, 3.302, 3.301, 3.298, 3.299, 3.301, + 3.295, 3.297, 3.305, 3.309, 3.311, 3.311, 3.311, 3.309, 3.307, 3.306, 3.305, 3.305, 3.305, 3.305, 3.304, 3.301, 3.301, 3.301, 3.302, 3.299, 3.296, 3.295, 3.295, 3.298, 3.301, 3.302, 3.303, 3.301, 3.299, 3.295, 3.297, 3.299, + 3.294, 3.296, 3.299, 3.306, 3.308, 3.309, 3.309, 3.307, 3.307, 3.303, 3.302, 3.301, 3.302, 3.303, 3.302, 3.299, 3.298, 3.299, 3.299, 3.298, 3.295, 3.292, 3.292, 3.293, 3.297, 3.299, 3.299, 3.299, 3.297, 3.294, 3.295, 3.299, + 3.291, 3.292, 3.296, 3.302, 3.306, 3.306, 3.307, 3.306, 3.305, 3.303, 3.302, 3.301, 3.301, 3.303, 3.301, 3.299, 3.298, 3.298, 3.298, 3.297, 3.295, 3.292, 3.291, 3.291, 3.295, 3.295, 3.297, 3.298, 3.296, 3.293, 3.292, 3.291, + 3.293, 3.292, 3.294, 3.301, 3.303, 3.305, 3.308, 3.306, 3.306, 3.304, 3.304, 3.303, 3.303, 3.303, 3.302, 3.301, 3.299, 3.299, 3.299, 3.299, 3.294, 3.291, 3.289, 3.291, 3.293, 3.294, 3.294, 3.294, 3.294, 3.289, 3.291, 3.291, + 3.288, 3.289, 3.291, 3.299, 3.303, 3.304, 3.304, 3.304, 3.304, 3.303, 3.303, 3.304, 3.306, 3.305, 3.303, 3.301, 3.301, 3.298, 3.299, 3.298, 3.293, 3.291, 3.289, 3.291, 3.291, 3.292, 3.291, 3.291, 3.291, 3.285, 3.288, 3.291, + 3.285, 3.284, 3.287, 3.291, 3.299, 3.299, 3.299, 3.299, 3.299, 3.301, 3.302, 3.303, 3.303, 3.302, 3.299, 3.298, 3.298, 3.298, 3.298, 3.293, 3.288, 3.286, 3.285, 3.288, 3.288, 3.288, 3.287, 3.285, 3.284, 3.279, 3.281, 3.284, + 3.281, 3.282, 3.282, 3.286, 3.291, 3.294, 3.294, 3.295, 3.295, 3.299, 3.301, 3.304, 3.305, 3.299, 3.299, 3.297, 3.298, 3.298, 3.297, 3.292, 3.288, 3.285, 3.283, 3.284, 3.284, 3.286, 3.284, 3.282, 3.279, 3.275, 3.275, 3.278, + 3.282, 3.282, 3.284, 3.286, 3.291, 3.294, 3.295, 3.295, 3.298, 3.301, 3.304, 3.306, 3.306, 3.304, 3.303, 3.301, 3.302, 3.299, 3.299, 3.295, 3.289, 3.287, 3.284, 3.288, 3.287, 3.287, 3.285, 3.283, 3.282, 3.278, 3.281, 3.286, + 3.292, 3.291, 3.292, 3.298, 3.307, 3.309, 3.308, 3.312, 3.313, 3.317, 3.324, 3.327, 3.327, 3.325, 3.326, 3.322, 3.319, 3.317, 3.317, 3.315, 3.312, 3.305, 3.303, 3.301, 3.299, 3.297, 3.299, 3.293, 3.289, 3.285, 3.287, 3.293 + ] + }, + { + "ct": 5000, + "table": + [ + 1.602, 1.603, 1.605, 1.608, 1.611, 1.612, 1.612, 1.614, 1.615, 1.616, 1.619, 1.621, 1.621, 1.622, 1.622, 1.624, 1.624, 1.626, 1.625, 1.625, 1.623, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.614, 1.612, 1.609, 1.609, 1.611, + 1.601, 1.602, 1.605, 1.608, 1.611, 1.612, 1.612, 1.613, 1.614, 1.615, 1.617, 1.619, 1.621, 1.621, 1.621, 1.621, 1.622, 1.624, 1.624, 1.624, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.615, 1.614, 1.612, 1.609, 1.609, 1.609, + 1.602, 1.603, 1.605, 1.609, 1.611, 1.613, 1.613, 1.613, 1.613, 1.615, 1.616, 1.618, 1.618, 1.619, 1.619, 1.619, 1.621, 1.621, 1.622, 1.622, 1.619, 1.618, 1.617, 1.617, 1.616, 1.615, 1.615, 1.615, 1.612, 1.609, 1.609, 1.609, + 1.604, 1.605, 1.608, 1.612, 1.613, 1.614, 1.614, 1.614, 1.614, 1.614, 1.616, 1.617, 1.618, 1.618, 1.618, 1.619, 1.619, 1.621, 1.622, 1.621, 1.619, 1.618, 1.617, 1.616, 1.616, 1.616, 1.616, 1.615, 1.615, 1.612, 1.611, 1.611, + 1.606, 1.608, 1.611, 1.614, 1.615, 1.615, 1.616, 1.616, 1.615, 1.615, 1.617, 1.618, 1.619, 1.619, 1.618, 1.619, 1.621, 1.622, 1.623, 1.622, 1.619, 1.619, 1.617, 1.617, 1.617, 1.618, 1.618, 1.617, 1.617, 1.614, 1.613, 1.613, + 1.608, 1.611, 1.614, 1.617, 1.618, 1.618, 1.618, 1.618, 1.618, 1.618, 1.619, 1.621, 1.621, 1.621, 1.621, 1.621, 1.622, 1.623, 1.624, 1.623, 1.622, 1.619, 1.619, 1.618, 1.618, 1.618, 1.618, 1.618, 1.618, 1.617, 1.615, 1.614, + 1.611, 1.613, 1.616, 1.618, 1.621, 1.621, 1.619, 1.619, 1.619, 1.619, 1.619, 1.621, 1.622, 1.623, 1.623, 1.623, 1.623, 1.624, 1.626, 1.624, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.621, 1.621, 1.619, 1.617, 1.616, 1.616, + 1.611, 1.613, 1.617, 1.621, 1.622, 1.622, 1.621, 1.619, 1.619, 1.619, 1.621, 1.622, 1.624, 1.624, 1.624, 1.624, 1.625, 1.626, 1.626, 1.624, 1.623, 1.621, 1.621, 1.619, 1.619, 1.619, 1.621, 1.621, 1.621, 1.619, 1.618, 1.617, + 1.613, 1.615, 1.618, 1.621, 1.623, 1.623, 1.622, 1.621, 1.619, 1.619, 1.621, 1.622, 1.625, 1.625, 1.626, 1.626, 1.625, 1.626, 1.626, 1.624, 1.622, 1.621, 1.619, 1.619, 1.619, 1.621, 1.622, 1.622, 1.621, 1.621, 1.619, 1.618, + 1.614, 1.617, 1.621, 1.623, 1.624, 1.624, 1.623, 1.621, 1.621, 1.621, 1.622, 1.625, 1.627, 1.627, 1.628, 1.628, 1.628, 1.628, 1.627, 1.626, 1.623, 1.621, 1.621, 1.621, 1.621, 1.623, 1.623, 1.623, 1.623, 1.621, 1.619, 1.619, + 1.616, 1.617, 1.622, 1.624, 1.625, 1.625, 1.624, 1.623, 1.622, 1.623, 1.624, 1.627, 1.629, 1.631, 1.631, 1.631, 1.631, 1.631, 1.631, 1.628, 1.626, 1.623, 1.622, 1.622, 1.623, 1.623, 1.624, 1.624, 1.624, 1.622, 1.621, 1.621, + 1.617, 1.618, 1.623, 1.625, 1.626, 1.626, 1.625, 1.624, 1.623, 1.624, 1.625, 1.629, 1.631, 1.633, 1.634, 1.634, 1.634, 1.633, 1.633, 1.631, 1.628, 1.624, 1.623, 1.623, 1.623, 1.625, 1.625, 1.625, 1.625, 1.623, 1.622, 1.622, + 1.617, 1.619, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.624, 1.625, 1.628, 1.632, 1.634, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.627, 1.624, 1.623, 1.623, 1.623, 1.624, 1.625, 1.626, 1.625, 1.624, 1.623, 1.623, + 1.618, 1.619, 1.623, 1.626, 1.627, 1.626, 1.626, 1.625, 1.624, 1.624, 1.625, 1.628, 1.631, 1.634, 1.634, 1.634, 1.634, 1.634, 1.633, 1.631, 1.628, 1.623, 1.622, 1.622, 1.623, 1.624, 1.625, 1.626, 1.626, 1.624, 1.624, 1.623, + 1.618, 1.619, 1.623, 1.626, 1.627, 1.627, 1.625, 1.624, 1.624, 1.624, 1.625, 1.628, 1.632, 1.633, 1.634, 1.634, 1.634, 1.633, 1.633, 1.631, 1.627, 1.623, 1.622, 1.622, 1.623, 1.624, 1.624, 1.625, 1.625, 1.624, 1.623, 1.623, + 1.619, 1.621, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.624, 1.626, 1.628, 1.632, 1.634, 1.635, 1.634, 1.634, 1.633, 1.633, 1.631, 1.628, 1.625, 1.622, 1.622, 1.622, 1.623, 1.624, 1.625, 1.625, 1.624, 1.623, 1.623, + 1.619, 1.621, 1.623, 1.626, 1.627, 1.627, 1.626, 1.625, 1.624, 1.625, 1.627, 1.629, 1.633, 1.635, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.628, 1.625, 1.623, 1.622, 1.622, 1.623, 1.624, 1.624, 1.624, 1.623, 1.623, 1.622, + 1.619, 1.621, 1.624, 1.626, 1.628, 1.628, 1.627, 1.626, 1.625, 1.626, 1.627, 1.629, 1.633, 1.635, 1.635, 1.635, 1.635, 1.634, 1.633, 1.631, 1.628, 1.625, 1.623, 1.623, 1.623, 1.623, 1.624, 1.624, 1.624, 1.622, 1.622, 1.622, + 1.619, 1.621, 1.623, 1.626, 1.628, 1.628, 1.627, 1.626, 1.625, 1.626, 1.627, 1.629, 1.632, 1.634, 1.635, 1.635, 1.634, 1.634, 1.632, 1.631, 1.628, 1.624, 1.622, 1.622, 1.622, 1.623, 1.624, 1.624, 1.624, 1.622, 1.621, 1.621, + 1.619, 1.621, 1.623, 1.627, 1.628, 1.628, 1.627, 1.627, 1.626, 1.627, 1.628, 1.629, 1.631, 1.633, 1.634, 1.633, 1.633, 1.632, 1.631, 1.631, 1.627, 1.624, 1.622, 1.622, 1.622, 1.622, 1.623, 1.623, 1.623, 1.621, 1.621, 1.621, + 1.621, 1.621, 1.624, 1.627, 1.628, 1.628, 1.627, 1.627, 1.627, 1.627, 1.628, 1.631, 1.632, 1.633, 1.633, 1.632, 1.632, 1.632, 1.631, 1.631, 1.628, 1.625, 1.623, 1.622, 1.622, 1.622, 1.623, 1.623, 1.623, 1.621, 1.621, 1.621, + 1.621, 1.621, 1.623, 1.627, 1.628, 1.628, 1.628, 1.627, 1.627, 1.628, 1.628, 1.629, 1.631, 1.632, 1.633, 1.632, 1.631, 1.631, 1.631, 1.629, 1.628, 1.625, 1.624, 1.623, 1.623, 1.623, 1.623, 1.623, 1.623, 1.621, 1.621, 1.619, + 1.619, 1.621, 1.623, 1.626, 1.628, 1.629, 1.627, 1.627, 1.627, 1.627, 1.628, 1.629, 1.631, 1.631, 1.631, 1.631, 1.631, 1.629, 1.629, 1.628, 1.626, 1.624, 1.623, 1.623, 1.623, 1.622, 1.623, 1.623, 1.622, 1.621, 1.619, 1.619, + 1.618, 1.619, 1.623, 1.625, 1.627, 1.627, 1.627, 1.627, 1.626, 1.627, 1.627, 1.628, 1.628, 1.629, 1.628, 1.628, 1.628, 1.628, 1.628, 1.627, 1.625, 1.623, 1.621, 1.621, 1.621, 1.622, 1.622, 1.622, 1.621, 1.619, 1.618, 1.618, + 1.618, 1.619, 1.622, 1.624, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.626, 1.627, 1.627, 1.627, 1.626, 1.626, 1.626, 1.626, 1.626, 1.624, 1.622, 1.621, 1.621, 1.619, 1.621, 1.621, 1.621, 1.621, 1.618, 1.617, 1.617, + 1.616, 1.618, 1.621, 1.623, 1.624, 1.625, 1.625, 1.625, 1.625, 1.625, 1.626, 1.626, 1.627, 1.627, 1.625, 1.624, 1.624, 1.625, 1.626, 1.625, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.621, 1.621, 1.619, 1.616, 1.616, 1.616, + 1.615, 1.616, 1.619, 1.621, 1.623, 1.624, 1.625, 1.624, 1.624, 1.625, 1.626, 1.627, 1.627, 1.626, 1.626, 1.625, 1.624, 1.625, 1.625, 1.625, 1.623, 1.621, 1.619, 1.619, 1.619, 1.619, 1.619, 1.619, 1.618, 1.616, 1.615, 1.614, + 1.614, 1.615, 1.616, 1.621, 1.621, 1.623, 1.624, 1.623, 1.624, 1.624, 1.625, 1.627, 1.627, 1.627, 1.626, 1.625, 1.625, 1.625, 1.625, 1.624, 1.622, 1.621, 1.619, 1.618, 1.617, 1.617, 1.617, 1.617, 1.616, 1.613, 1.612, 1.612, + 1.612, 1.612, 1.615, 1.617, 1.621, 1.621, 1.622, 1.622, 1.622, 1.624, 1.625, 1.626, 1.626, 1.626, 1.625, 1.624, 1.624, 1.624, 1.624, 1.623, 1.621, 1.619, 1.618, 1.616, 1.615, 1.615, 1.615, 1.615, 1.613, 1.611, 1.609, 1.609, + 1.611, 1.611, 1.612, 1.615, 1.618, 1.619, 1.621, 1.621, 1.622, 1.623, 1.624, 1.626, 1.626, 1.626, 1.625, 1.624, 1.624, 1.624, 1.623, 1.622, 1.621, 1.618, 1.617, 1.615, 1.615, 1.614, 1.614, 1.613, 1.611, 1.609, 1.609, 1.609, + 1.611, 1.611, 1.612, 1.615, 1.618, 1.619, 1.621, 1.622, 1.623, 1.625, 1.625, 1.627, 1.627, 1.627, 1.626, 1.626, 1.626, 1.626, 1.624, 1.622, 1.621, 1.618, 1.617, 1.617, 1.616, 1.615, 1.614, 1.613, 1.612, 1.609, 1.609, 1.609, + 1.612, 1.612, 1.614, 1.617, 1.619, 1.621, 1.623, 1.624, 1.625, 1.626, 1.627, 1.629, 1.631, 1.629, 1.629, 1.629, 1.628, 1.629, 1.628, 1.626, 1.624, 1.621, 1.621, 1.619, 1.619, 1.618, 1.616, 1.616, 1.613, 1.611, 1.612, 1.612 + ] + } + ], + "luminance_lut": + [ + 2.977, 2.794, 2.572, 2.375, 2.218, 2.098, 1.995, 1.903, 1.815, 1.731, 1.647, 1.571, 1.516, 1.493, 1.483, 1.481, 1.481, 1.481, 1.489, 1.511, 1.571, 1.643, 1.729, 1.813, 1.901, 1.993, 2.091, 2.208, 2.364, 2.563, 2.785, 2.971, + 2.951, 2.736, 2.512, 2.312, 2.153, 2.031, 1.926, 1.824, 1.736, 1.649, 1.571, 1.506, 1.456, 1.419, 1.396, 1.386, 1.386, 1.392, 1.414, 1.451, 1.505, 1.571, 1.648, 1.733, 1.824, 1.922, 2.025, 2.144, 2.301, 2.499, 2.725, 2.939, + 2.883, 2.701, 2.471, 2.266, 2.102, 1.974, 1.861, 1.753, 1.649, 1.571, 1.502, 1.425, 1.361, 1.322, 1.298, 1.286, 1.286, 1.294, 1.317, 1.359, 1.424, 1.501, 1.571, 1.648, 1.751, 1.857, 1.968, 2.095, 2.254, 2.458, 2.688, 2.872, + 2.788, 2.632, 2.408, 2.209, 2.056, 1.931, 1.816, 1.704, 1.598, 1.503, 1.425, 1.361, 1.322, 1.298, 1.269, 1.245, 1.243, 1.264, 1.293, 1.317, 1.359, 1.424, 1.501, 1.596, 1.702, 1.812, 1.924, 2.046, 2.197, 2.392, 2.619, 2.777, + 2.712, 2.541, 2.327, 2.155, 2.023, 1.908, 1.796, 1.684, 1.578, 1.488, 1.412, 1.351, 1.304, 1.269, 1.245, 1.235, 1.235, 1.243, 1.264, 1.301, 1.349, 1.411, 1.485, 1.577, 1.683, 1.791, 1.902, 2.016, 2.143, 2.312, 2.528, 2.702, + 2.678, 2.469, 2.269, 2.117, 1.998, 1.885, 1.773, 1.661, 1.556, 1.469, 1.397, 1.336, 1.277, 1.245, 1.234, 1.226, 1.226, 1.232, 1.244, 1.273, 1.332, 1.392, 1.465, 1.555, 1.659, 1.768, 1.879, 1.991, 2.109, 2.256, 2.454, 2.665, + 2.659, 2.433, 2.232, 2.081, 1.957, 1.841, 1.722, 1.606, 1.499, 1.409, 1.337, 1.277, 1.232, 1.198, 1.175, 1.166, 1.166, 1.172, 1.193, 1.228, 1.272, 1.334, 1.408, 1.499, 1.608, 1.717, 1.834, 1.951, 2.073, 2.222, 2.419, 2.648, + 2.624, 2.411, 2.204, 2.041, 1.909, 1.784, 1.661, 1.539, 1.431, 1.337, 1.277, 1.219, 1.159, 1.118, 1.096, 1.085, 1.085, 1.092, 1.114, 1.156, 1.219, 1.272, 1.337, 1.429, 1.539, 1.658, 1.779, 1.904, 2.033, 2.193, 2.397, 2.613, + 2.564, 2.377, 2.169, 2.012, 1.879, 1.749, 1.623, 1.501, 1.392, 1.299, 1.227, 1.169, 1.125, 1.097, 1.079, 1.063, 1.063, 1.076, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.501, 1.622, 1.746, 1.875, 2.005, 2.161, 2.362, 2.554, + 2.515, 2.325, 2.138, 1.997, 1.869, 1.742, 1.617, 1.501, 1.392, 1.299, 1.227, 1.169, 1.125, 1.095, 1.079, 1.063, 1.063, 1.076, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.499, 1.615, 1.741, 1.867, 1.991, 2.132, 2.316, 2.505, + 2.498, 2.289, 2.121, 1.988, 1.867, 1.741, 1.616, 1.499, 1.391, 1.299, 1.227, 1.169, 1.125, 1.095, 1.082, 1.065, 1.064, 1.079, 1.093, 1.124, 1.168, 1.227, 1.302, 1.392, 1.498, 1.614, 1.738, 1.864, 1.985, 2.116, 2.281, 2.486, + 2.498, 2.272, 2.105, 1.971, 1.846, 1.718, 1.592, 1.475, 1.371, 1.279, 1.211, 1.156, 1.112, 1.083, 1.064, 1.055, 1.055, 1.062, 1.081, 1.109, 1.154, 1.212, 1.285, 1.372, 1.473, 1.589, 1.712, 1.843, 1.967, 2.101, 2.263, 2.486, + 2.497, 2.267, 2.088, 1.946, 1.813, 1.679, 1.549, 1.431, 1.324, 1.231, 1.159, 1.114, 1.079, 1.035, 1.008, 1.001, 1.001, 1.008, 1.032, 1.076, 1.111, 1.161, 1.235, 1.324, 1.429, 1.547, 1.677, 1.811, 1.941, 2.082, 2.257, 2.484, + 2.476, 2.262, 2.077, 1.933, 1.802, 1.671, 1.541, 1.421, 1.317, 1.227, 1.157, 1.101, 1.059, 1.027, 1.004, 1.001, 1.001, 1.004, 1.024, 1.054, 1.098, 1.157, 1.229, 1.317, 1.419, 1.537, 1.667, 1.799, 1.931, 2.071, 2.251, 2.463, + 2.455, 2.246, 2.076, 1.933, 1.802, 1.671, 1.541, 1.421, 1.317, 1.227, 1.157, 1.103, 1.064, 1.035, 1.011, 1.003, 1.003, 1.009, 1.032, 1.062, 1.099, 1.157, 1.229, 1.317, 1.419, 1.537, 1.667, 1.799, 1.931, 2.071, 2.236, 2.446, + 2.454, 2.239, 2.077, 1.946, 1.817, 1.686, 1.561, 1.444, 1.342, 1.255, 1.189, 1.136, 1.093, 1.059, 1.039, 1.038, 1.038, 1.039, 1.056, 1.091, 1.131, 1.187, 1.258, 1.341, 1.441, 1.556, 1.683, 1.813, 1.939, 2.071, 2.229, 2.445, + 2.454, 2.239, 2.079, 1.946, 1.817, 1.686, 1.561, 1.444, 1.342, 1.255, 1.189, 1.136, 1.093, 1.062, 1.039, 1.038, 1.038, 1.039, 1.059, 1.091, 1.131, 1.187, 1.258, 1.341, 1.441, 1.556, 1.683, 1.813, 1.939, 2.071, 2.229, 2.445, + 2.458, 2.251, 2.079, 1.941, 1.807, 1.672, 1.543, 1.424, 1.319, 1.231, 1.162, 1.107, 1.065, 1.045, 1.018, 1.003, 1.003, 1.017, 1.044, 1.062, 1.103, 1.159, 1.232, 1.317, 1.419, 1.539, 1.669, 1.802, 1.933, 2.072, 2.239, 2.445, + 2.479, 2.265, 2.085, 1.941, 1.807, 1.672, 1.543, 1.424, 1.319, 1.231, 1.162, 1.107, 1.064, 1.031, 1.017, 1.003, 1.003, 1.017, 1.031, 1.059, 1.103, 1.159, 1.232, 1.317, 1.419, 1.539, 1.669, 1.802, 1.933, 2.076, 2.252, 2.468, + 2.504, 2.277, 2.099, 1.958, 1.826, 1.695, 1.565, 1.445, 1.338, 1.249, 1.181, 1.129, 1.095, 1.051, 1.027, 1.018, 1.018, 1.028, 1.049, 1.092, 1.127, 1.179, 1.252, 1.339, 1.442, 1.561, 1.691, 1.822, 1.949, 2.089, 2.263, 2.492, + 2.509, 2.288, 2.118, 1.982, 1.858, 1.728, 1.604, 1.486, 1.381, 1.293, 1.227, 1.173, 1.127, 1.098, 1.076, 1.067, 1.067, 1.077, 1.097, 1.121, 1.168, 1.225, 1.296, 1.382, 1.483, 1.598, 1.723, 1.852, 1.975, 2.107, 2.274, 2.496, + 2.515, 2.312, 2.139, 2.002, 1.877, 1.751, 1.629, 1.512, 1.405, 1.318, 1.248, 1.193, 1.149, 1.118, 1.096, 1.085, 1.085, 1.095, 1.114, 1.145, 1.188, 1.246, 1.319, 1.405, 1.508, 1.623, 1.747, 1.873, 1.995, 2.127, 2.297, 2.501, + 2.541, 2.351, 2.161, 2.016, 1.888, 1.762, 1.638, 1.519, 1.411, 1.319, 1.251, 1.197, 1.154, 1.121, 1.099, 1.091, 1.091, 1.099, 1.119, 1.148, 1.192, 1.248, 1.321, 1.411, 1.515, 1.633, 1.758, 1.884, 2.009, 2.149, 2.334, 2.526, + 2.588, 2.394, 2.193, 2.036, 1.905, 1.779, 1.656, 1.537, 1.426, 1.329, 1.255, 1.198, 1.161, 1.139, 1.118, 1.096, 1.095, 1.114, 1.138, 1.158, 1.195, 1.256, 1.333, 1.425, 1.533, 1.651, 1.777, 1.902, 2.028, 2.181, 2.378, 2.571, + 2.639, 2.431, 2.226, 2.067, 1.937, 1.816, 1.695, 1.577, 1.467, 1.368, 1.298, 1.253, 1.198, 1.161, 1.139, 1.129, 1.129, 1.138, 1.158, 1.195, 1.245, 1.296, 1.374, 1.468, 1.574, 1.692, 1.812, 1.934, 2.059, 2.216, 2.418, 2.626, + 2.679, 2.465, 2.261, 2.104, 1.979, 1.862, 1.746, 1.631, 1.522, 1.426, 1.352, 1.297, 1.254, 1.221, 1.201, 1.189, 1.189, 1.198, 1.217, 1.246, 1.293, 1.354, 1.433, 1.526, 1.631, 1.744, 1.859, 1.975, 2.097, 2.252, 2.452, 2.667, + 2.711, 2.511, 2.302, 2.141, 2.018, 1.903, 1.791, 1.678, 1.571, 1.475, 1.401, 1.343, 1.297, 1.268, 1.247, 1.236, 1.236, 1.244, 1.263, 1.291, 1.341, 1.403, 1.484, 1.575, 1.679, 1.791, 1.902, 2.012, 2.136, 2.295, 2.501, 2.698, + 2.759, 2.582, 2.363, 2.184, 2.049, 1.935, 1.824, 1.714, 1.608, 1.511, 1.431, 1.371, 1.325, 1.295, 1.271, 1.259, 1.259, 1.266, 1.291, 1.318, 1.369, 1.436, 1.517, 1.611, 1.716, 1.825, 1.933, 2.047, 2.179, 2.351, 2.571, 2.748, + 2.833, 2.662, 2.433, 2.239, 2.089, 1.968, 1.859, 1.752, 1.646, 1.549, 1.468, 1.411, 1.369, 1.325, 1.296, 1.283, 1.283, 1.292, 1.318, 1.366, 1.411, 1.472, 1.555, 1.651, 1.755, 1.861, 1.969, 2.086, 2.231, 2.422, 2.648, 2.821, + 2.909, 2.729, 2.499, 2.298, 2.141, 2.016, 1.907, 1.805, 1.703, 1.611, 1.539, 1.468, 1.411, 1.375, 1.351, 1.339, 1.339, 1.348, 1.372, 1.411, 1.472, 1.543, 1.613, 1.708, 1.807, 1.909, 2.014, 2.135, 2.288, 2.487, 2.716, 2.897, + 2.981, 2.789, 2.563, 2.358, 2.197, 2.071, 1.968, 1.868, 1.774, 1.684, 1.607, 1.541, 1.489, 1.453, 1.428, 1.417, 1.417, 1.427, 1.451, 1.489, 1.543, 1.611, 1.686, 1.776, 1.871, 1.966, 2.069, 2.191, 2.349, 2.551, 2.775, 2.964, + 3.041, 2.856, 2.629, 2.422, 2.252, 2.127, 2.021, 1.927, 1.834, 1.748, 1.672, 1.604, 1.541, 1.495, 1.483, 1.483, 1.483, 1.483, 1.496, 1.543, 1.608, 1.673, 1.749, 1.835, 1.926, 2.019, 2.122, 2.249, 2.411, 2.614, 2.839, 3.026 + ], + "sigma": 0.00163, + "sigma_Cb": 0.0011 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2498, + "ccm": + [ + 1.14912, 0.28638, -0.43551, + -0.49691, 1.60391, -0.10701, + -0.10513, -1.09534, 2.20047 + ] + }, + { + "ct": 2821, + "ccm": + [ + 1.18251, 0.15501, -0.33752, + -0.44304, 1.58495, -0.14191, + -0.05077, -0.96422, 2.01498 + ] + }, + { + "ct": 2925, + "ccm": + [ + 1.18668, 0.00195, -0.18864, + -0.41617, 1.50514, -0.08897, + -0.02675, -0.91143, 1.93818 + ] + }, + { + "ct": 2926, + "ccm": + [ + 1.50948, -0.44421, -0.06527, + -0.37241, 1.41726, -0.04486, + 0.07098, -0.84694, 1.77596 + ] + }, + { + "ct": 2951, + "ccm": + [ + 1.52743, -0.47333, -0.05411, + -0.36485, 1.40764, -0.04279, + 0.08672, -0.90479, 1.81807 + ] + }, + { + "ct": 2954, + "ccm": + [ + 1.51683, -0.46841, -0.04841, + -0.36288, 1.39914, -0.03625, + 0.06421, -0.82034, 1.75613 + ] + }, + { + "ct": 3578, + "ccm": + [ + 1.59888, -0.59105, -0.00784, + -0.29366, 1.32037, -0.02671, + 0.06627, -0.76465, 1.69838 + ] + }, + { + "ct": 3717, + "ccm": + [ + 1.59063, -0.58059, -0.01003, + -0.29583, 1.32715, -0.03132, + 0.03613, -0.67431, 1.63817 + ] + }, + { + "ct": 3784, + "ccm": + [ + 1.59379, -0.58861, -0.00517, + -0.29178, 1.33292, -0.04115, + 0.03541, -0.66162, 1.62622 + ] + }, + { + "ct": 4485, + "ccm": + [ + 1.40761, -0.34561, -0.06201, + -0.32388, 1.57221, -0.24832, + -0.01014, -0.63427, 1.64441 + ] + }, + { + "ct": 4615, + "ccm": + [ + 1.41537, -0.35832, -0.05705, + -0.31429, 1.56019, -0.24591, + -0.01761, -0.61859, 1.63621 + ] + }, + { + "ct": 4671, + "ccm": + [ + 1.42941, -0.38178, -0.04764, + -0.31421, 1.55925, -0.24504, + -0.01141, -0.62987, 1.64129 + ] + }, + { + "ct": 5753, + "ccm": + [ + 1.64549, -0.63329, -0.01221, + -0.22431, 1.36423, -0.13992, + -0.00831, -0.55373, 1.56204 + ] + }, + { + "ct": 5773, + "ccm": + [ + 1.63668, -0.63557, -0.00111, + -0.21919, 1.36234, -0.14315, + -0.00399, -0.57428, 1.57827 + ] + }, + { + "ct": 7433, + "ccm": + [ + 1.36007, -0.09277, -0.26729, + -0.36886, 2.09249, -0.72363, + -0.12573, -0.76761, 1.89334 + ] + }, + { + "ct": 55792, + "ccm": + [ + 1.65091, -0.63689, -0.01401, + -0.22277, 1.35752, -0.13475, + -0.00943, -0.55091, 1.56033 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 3.0, + "max": 15.0, + "default": 4.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 1.0, + "step_fine": 0.25, + "contrast_ratio": 0.75, + "pdaf_gain": -0.02, + "pdaf_squelch": 0.125, + "max_slew": 2.0, + "pdaf_frames": 20, + "dropout_frames": 6, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 16, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 445, 15.0, 925 ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx708_wide.json b/src/ipa/rpi/pisp/data/imx708_wide.json new file mode 100644 index 00000000..9fff05d9 --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx708_wide.json @@ -0,0 +1,1293 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 41985, + "reference_gain": 1.12, + "reference_aperture": 1.0, + "reference_lux": 810, + "reference_Y": 13859 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.9 + } + }, + { + "rpi.geq": + { + "offset": 206, + "slope": 0.00324 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2868.0, 0.6419, 0.3613, + 3603.0, 0.5374, 0.4787, + 4620.0, 0.4482, 0.5813, + 5901.0, 0.3883, 0.6514, + 7610.0, 0.3279, 0.7232 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.01908, + "transverse_neg": 0.01376 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.65, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.717, 1.712, 1.703, 1.692, 1.674, 1.653, 1.638, 1.624, 1.613, 1.601, 1.589, 1.579, 1.575, 1.573, 1.571, 1.571, 1.571, 1.571, 1.572, 1.577, 1.583, 1.593, 1.605, 1.618, 1.636, 1.653, 1.677, 1.699, 1.715, 1.722, 1.731, 1.733, + 1.714, 1.706, 1.696, 1.678, 1.658, 1.639, 1.627, 1.614, 1.602, 1.591, 1.579, 1.572, 1.569, 1.566, 1.565, 1.564, 1.564, 1.565, 1.567, 1.571, 1.578, 1.585, 1.595, 1.607, 1.622, 1.641, 1.661, 1.685, 1.706, 1.717, 1.724, 1.732, + 1.708, 1.698, 1.688, 1.667, 1.647, 1.629, 1.619, 1.606, 1.593, 1.581, 1.572, 1.565, 1.561, 1.559, 1.559, 1.559, 1.559, 1.561, 1.562, 1.566, 1.571, 1.577, 1.587, 1.598, 1.612, 1.629, 1.649, 1.674, 1.697, 1.713, 1.721, 1.728, + 1.706, 1.695, 1.681, 1.655, 1.636, 1.622, 1.613, 1.597, 1.585, 1.572, 1.564, 1.559, 1.558, 1.556, 1.555, 1.555, 1.556, 1.556, 1.558, 1.561, 1.566, 1.571, 1.578, 1.591, 1.605, 1.619, 1.638, 1.662, 1.691, 1.708, 1.719, 1.726, + 1.706, 1.692, 1.675, 1.649, 1.629, 1.615, 1.607, 1.592, 1.575, 1.565, 1.559, 1.554, 1.552, 1.551, 1.551, 1.551, 1.551, 1.552, 1.554, 1.557, 1.561, 1.566, 1.573, 1.582, 1.596, 1.611, 1.627, 1.652, 1.681, 1.705, 1.717, 1.724, + 1.703, 1.686, 1.664, 1.639, 1.625, 1.612, 1.599, 1.585, 1.569, 1.559, 1.554, 1.549, 1.548, 1.548, 1.546, 1.546, 1.546, 1.547, 1.549, 1.553, 1.557, 1.563, 1.569, 1.576, 1.591, 1.603, 1.621, 1.644, 1.674, 1.698, 1.714, 1.724, + 1.702, 1.681, 1.659, 1.635, 1.621, 1.607, 1.594, 1.579, 1.565, 1.554, 1.549, 1.546, 1.544, 1.543, 1.543, 1.542, 1.543, 1.543, 1.544, 1.549, 1.553, 1.558, 1.564, 1.572, 1.584, 1.599, 1.614, 1.639, 1.667, 1.695, 1.712, 1.724, + 1.697, 1.678, 1.655, 1.631, 1.616, 1.602, 1.589, 1.575, 1.559, 1.551, 1.545, 1.543, 1.542, 1.542, 1.541, 1.539, 1.539, 1.539, 1.542, 1.544, 1.551, 1.555, 1.562, 1.571, 1.579, 1.594, 1.611, 1.631, 1.661, 1.691, 1.712, 1.724, + 1.695, 1.674, 1.651, 1.629, 1.615, 1.599, 1.584, 1.568, 1.554, 1.545, 1.542, 1.541, 1.539, 1.539, 1.538, 1.538, 1.538, 1.539, 1.539, 1.543, 1.548, 1.554, 1.559, 1.568, 1.576, 1.592, 1.608, 1.629, 1.655, 1.689, 1.709, 1.723, + 1.691, 1.671, 1.648, 1.627, 1.613, 1.597, 1.581, 1.564, 1.551, 1.543, 1.539, 1.538, 1.538, 1.537, 1.536, 1.535, 1.536, 1.538, 1.539, 1.542, 1.546, 1.551, 1.558, 1.564, 1.575, 1.588, 1.604, 1.627, 1.654, 1.686, 1.709, 1.724, + 1.689, 1.667, 1.643, 1.626, 1.612, 1.594, 1.579, 1.559, 1.549, 1.541, 1.536, 1.535, 1.535, 1.535, 1.534, 1.533, 1.534, 1.536, 1.538, 1.541, 1.545, 1.549, 1.555, 1.563, 1.573, 1.585, 1.602, 1.624, 1.651, 1.683, 1.709, 1.725, + 1.686, 1.665, 1.641, 1.623, 1.609, 1.594, 1.576, 1.559, 1.546, 1.538, 1.535, 1.534, 1.533, 1.532, 1.531, 1.531, 1.532, 1.534, 1.537, 1.539, 1.544, 1.549, 1.554, 1.562, 1.572, 1.585, 1.601, 1.622, 1.651, 1.682, 1.711, 1.726, + 1.686, 1.661, 1.639, 1.623, 1.609, 1.592, 1.574, 1.557, 1.545, 1.537, 1.534, 1.533, 1.532, 1.531, 1.529, 1.528, 1.529, 1.532, 1.537, 1.539, 1.542, 1.548, 1.553, 1.562, 1.571, 1.584, 1.601, 1.621, 1.649, 1.682, 1.711, 1.726, + 1.685, 1.661, 1.638, 1.624, 1.609, 1.592, 1.574, 1.557, 1.544, 1.536, 1.533, 1.532, 1.531, 1.529, 1.527, 1.522, 1.526, 1.531, 1.536, 1.539, 1.542, 1.547, 1.553, 1.562, 1.571, 1.583, 1.601, 1.621, 1.648, 1.682, 1.711, 1.726, + 1.684, 1.658, 1.638, 1.624, 1.611, 1.592, 1.573, 1.556, 1.543, 1.536, 1.532, 1.531, 1.529, 1.528, 1.522, 1.517, 1.519, 1.527, 1.535, 1.539, 1.541, 1.547, 1.553, 1.562, 1.571, 1.583, 1.601, 1.622, 1.647, 1.681, 1.711, 1.727, + 1.681, 1.658, 1.641, 1.624, 1.611, 1.593, 1.573, 1.555, 1.541, 1.535, 1.532, 1.529, 1.529, 1.527, 1.517, 1.506, 1.506, 1.522, 1.534, 1.538, 1.541, 1.546, 1.552, 1.562, 1.569, 1.583, 1.601, 1.622, 1.646, 1.679, 1.709, 1.728, + 1.679, 1.656, 1.639, 1.624, 1.611, 1.595, 1.575, 1.556, 1.541, 1.534, 1.531, 1.529, 1.529, 1.527, 1.517, 1.507, 1.507, 1.522, 1.533, 1.538, 1.539, 1.546, 1.552, 1.561, 1.569, 1.584, 1.601, 1.622, 1.647, 1.681, 1.709, 1.726, + 1.678, 1.656, 1.638, 1.625, 1.612, 1.597, 1.577, 1.557, 1.542, 1.534, 1.529, 1.529, 1.528, 1.527, 1.522, 1.516, 1.519, 1.525, 1.533, 1.537, 1.539, 1.545, 1.552, 1.561, 1.571, 1.584, 1.601, 1.623, 1.649, 1.681, 1.709, 1.726, + 1.679, 1.654, 1.639, 1.626, 1.613, 1.598, 1.578, 1.558, 1.543, 1.534, 1.529, 1.529, 1.529, 1.528, 1.527, 1.522, 1.525, 1.528, 1.533, 1.536, 1.539, 1.546, 1.553, 1.561, 1.571, 1.586, 1.602, 1.623, 1.651, 1.683, 1.712, 1.726, + 1.677, 1.655, 1.641, 1.628, 1.615, 1.599, 1.581, 1.562, 1.545, 1.535, 1.531, 1.529, 1.529, 1.528, 1.527, 1.527, 1.528, 1.531, 1.533, 1.536, 1.539, 1.545, 1.552, 1.561, 1.572, 1.588, 1.607, 1.626, 1.654, 1.686, 1.716, 1.729, + 1.676, 1.655, 1.642, 1.629, 1.617, 1.602, 1.586, 1.564, 1.546, 1.536, 1.531, 1.529, 1.529, 1.529, 1.529, 1.529, 1.529, 1.532, 1.534, 1.536, 1.539, 1.547, 1.553, 1.563, 1.576, 1.591, 1.609, 1.627, 1.655, 1.688, 1.716, 1.729, + 1.676, 1.658, 1.641, 1.631, 1.617, 1.605, 1.588, 1.569, 1.553, 1.539, 1.532, 1.531, 1.529, 1.529, 1.529, 1.529, 1.531, 1.532, 1.534, 1.537, 1.541, 1.547, 1.553, 1.564, 1.578, 1.594, 1.613, 1.632, 1.659, 1.691, 1.717, 1.728, + 1.676, 1.658, 1.642, 1.631, 1.619, 1.608, 1.592, 1.575, 1.556, 1.542, 1.533, 1.531, 1.529, 1.529, 1.529, 1.531, 1.531, 1.532, 1.534, 1.537, 1.542, 1.548, 1.556, 1.567, 1.582, 1.598, 1.616, 1.638, 1.661, 1.693, 1.717, 1.729, + 1.678, 1.661, 1.644, 1.632, 1.621, 1.611, 1.596, 1.579, 1.561, 1.546, 1.536, 1.532, 1.531, 1.531, 1.531, 1.531, 1.532, 1.533, 1.535, 1.538, 1.544, 1.549, 1.559, 1.569, 1.587, 1.604, 1.618, 1.639, 1.669, 1.697, 1.718, 1.731, + 1.679, 1.662, 1.648, 1.635, 1.625, 1.615, 1.602, 1.586, 1.569, 1.552, 1.541, 1.535, 1.532, 1.532, 1.531, 1.532, 1.533, 1.534, 1.537, 1.541, 1.546, 1.552, 1.562, 1.576, 1.592, 1.608, 1.622, 1.647, 1.673, 1.703, 1.721, 1.734, + 1.684, 1.664, 1.649, 1.637, 1.627, 1.618, 1.606, 1.593, 1.576, 1.561, 1.547, 1.539, 1.535, 1.533, 1.533, 1.533, 1.534, 1.536, 1.539, 1.543, 1.549, 1.555, 1.568, 1.583, 1.596, 1.612, 1.629, 1.651, 1.681, 1.706, 1.723, 1.734, + 1.689, 1.669, 1.649, 1.639, 1.629, 1.621, 1.609, 1.597, 1.585, 1.567, 1.554, 1.546, 1.539, 1.536, 1.535, 1.535, 1.537, 1.538, 1.542, 1.546, 1.553, 1.562, 1.572, 1.589, 1.603, 1.619, 1.635, 1.658, 1.686, 1.708, 1.726, 1.736, + 1.692, 1.673, 1.655, 1.644, 1.634, 1.624, 1.614, 1.604, 1.592, 1.577, 1.566, 1.554, 1.546, 1.542, 1.538, 1.538, 1.539, 1.542, 1.546, 1.552, 1.559, 1.568, 1.581, 1.596, 1.609, 1.625, 1.642, 1.664, 1.693, 1.714, 1.727, 1.736, + 1.695, 1.679, 1.662, 1.647, 1.638, 1.631, 1.623, 1.612, 1.601, 1.589, 1.577, 1.565, 1.555, 1.549, 1.546, 1.545, 1.546, 1.548, 1.552, 1.559, 1.568, 1.579, 1.593, 1.604, 1.618, 1.632, 1.648, 1.676, 1.701, 1.718, 1.728, 1.739, + 1.699, 1.684, 1.667, 1.654, 1.644, 1.635, 1.629, 1.621, 1.609, 1.599, 1.589, 1.578, 1.568, 1.559, 1.556, 1.554, 1.554, 1.557, 1.563, 1.569, 1.578, 1.589, 1.599, 1.612, 1.625, 1.641, 1.661, 1.685, 1.707, 1.722, 1.734, 1.742, + 1.703, 1.691, 1.672, 1.658, 1.648, 1.639, 1.634, 1.628, 1.618, 1.606, 1.598, 1.589, 1.579, 1.573, 1.568, 1.567, 1.567, 1.568, 1.571, 1.578, 1.587, 1.597, 1.607, 1.618, 1.632, 1.651, 1.672, 1.694, 1.715, 1.728, 1.737, 1.742, + 1.707, 1.691, 1.676, 1.662, 1.651, 1.643, 1.638, 1.631, 1.622, 1.614, 1.604, 1.596, 1.589, 1.579, 1.575, 1.573, 1.573, 1.574, 1.578, 1.586, 1.589, 1.598, 1.609, 1.625, 1.638, 1.657, 1.679, 1.701, 1.719, 1.728, 1.738, 1.742 + ] + }, + { + "ct": 5000, + "table": + [ + 2.939, 2.935, 2.916, 2.895, 2.856, 2.825, 2.797, 2.777, 2.761, 2.741, 2.726, 2.709, 2.707, 2.704, 2.702, 2.702, 2.703, 2.706, 2.708, 2.709, 2.719, 2.735, 2.753, 2.776, 2.801, 2.832, 2.874, 2.915, 2.939, 2.943, 2.953, 2.961, + 2.936, 2.923, 2.901, 2.863, 2.829, 2.801, 2.781, 2.763, 2.743, 2.732, 2.712, 2.701, 2.696, 2.692, 2.691, 2.691, 2.693, 2.694, 2.696, 2.701, 2.709, 2.725, 2.741, 2.758, 2.779, 2.811, 2.838, 2.879, 2.919, 2.939, 2.948, 2.959, + 2.929, 2.909, 2.887, 2.847, 2.808, 2.783, 2.765, 2.748, 2.732, 2.713, 2.699, 2.691, 2.687, 2.686, 2.685, 2.685, 2.687, 2.689, 2.691, 2.694, 2.701, 2.709, 2.725, 2.745, 2.763, 2.786, 2.818, 2.863, 2.907, 2.933, 2.941, 2.955, + 2.929, 2.903, 2.875, 2.825, 2.791, 2.769, 2.755, 2.737, 2.718, 2.701, 2.688, 2.683, 2.681, 2.679, 2.681, 2.679, 2.681, 2.682, 2.685, 2.689, 2.694, 2.701, 2.711, 2.737, 2.754, 2.772, 2.803, 2.844, 2.894, 2.931, 2.939, 2.953, + 2.926, 2.895, 2.862, 2.816, 2.782, 2.759, 2.744, 2.727, 2.709, 2.691, 2.679, 2.673, 2.671, 2.669, 2.669, 2.669, 2.671, 2.674, 2.678, 2.681, 2.685, 2.694, 2.707, 2.725, 2.739, 2.762, 2.786, 2.829, 2.879, 2.919, 2.942, 2.952, + 2.919, 2.886, 2.846, 2.797, 2.772, 2.751, 2.737, 2.719, 2.694, 2.679, 2.672, 2.666, 2.664, 2.661, 2.659, 2.658, 2.661, 2.664, 2.669, 2.673, 2.678, 2.685, 2.696, 2.715, 2.728, 2.749, 2.774, 2.808, 2.866, 2.909, 2.936, 2.951, + 2.904, 2.877, 2.835, 2.789, 2.763, 2.744, 2.728, 2.712, 2.686, 2.672, 2.664, 2.657, 2.654, 2.654, 2.652, 2.653, 2.654, 2.657, 2.661, 2.666, 2.672, 2.678, 2.688, 2.703, 2.721, 2.742, 2.762, 2.797, 2.851, 2.902, 2.928, 2.949, + 2.901, 2.869, 2.825, 2.781, 2.756, 2.738, 2.721, 2.698, 2.679, 2.665, 2.656, 2.652, 2.649, 2.648, 2.648, 2.648, 2.649, 2.651, 2.654, 2.659, 2.667, 2.675, 2.683, 2.699, 2.711, 2.736, 2.754, 2.789, 2.838, 2.896, 2.926, 2.948, + 2.899, 2.862, 2.815, 2.774, 2.752, 2.734, 2.717, 2.689, 2.669, 2.658, 2.651, 2.646, 2.645, 2.643, 2.643, 2.644, 2.645, 2.646, 2.649, 2.654, 2.661, 2.669, 2.681, 2.693, 2.707, 2.729, 2.751, 2.782, 2.834, 2.887, 2.924, 2.947, + 2.898, 2.853, 2.812, 2.771, 2.751, 2.731, 2.711, 2.686, 2.663, 2.653, 2.646, 2.642, 2.641, 2.642, 2.642, 2.641, 2.641, 2.641, 2.646, 2.651, 2.657, 2.667, 2.678, 2.693, 2.705, 2.728, 2.746, 2.781, 2.829, 2.885, 2.924, 2.951, + 2.896, 2.851, 2.807, 2.771, 2.752, 2.729, 2.709, 2.681, 2.661, 2.649, 2.643, 2.641, 2.639, 2.639, 2.638, 2.636, 2.637, 2.638, 2.644, 2.649, 2.657, 2.666, 2.676, 2.688, 2.705, 2.725, 2.745, 2.777, 2.827, 2.884, 2.927, 2.951, + 2.891, 2.846, 2.803, 2.771, 2.749, 2.728, 2.706, 2.677, 2.658, 2.647, 2.641, 2.637, 2.637, 2.636, 2.636, 2.633, 2.632, 2.635, 2.643, 2.649, 2.656, 2.665, 2.675, 2.688, 2.704, 2.719, 2.744, 2.776, 2.822, 2.881, 2.927, 2.958, + 2.887, 2.841, 2.797, 2.769, 2.749, 2.729, 2.704, 2.674, 2.655, 2.645, 2.638, 2.635, 2.633, 2.632, 2.631, 2.625, 2.627, 2.631, 2.639, 2.649, 2.654, 2.662, 2.673, 2.686, 2.701, 2.718, 2.742, 2.773, 2.822, 2.881, 2.926, 2.958, + 2.883, 2.837, 2.796, 2.769, 2.749, 2.729, 2.701, 2.673, 2.653, 2.641, 2.636, 2.632, 2.631, 2.629, 2.623, 2.612, 2.619, 2.627, 2.637, 2.648, 2.652, 2.659, 2.671, 2.688, 2.699, 2.719, 2.742, 2.774, 2.821, 2.882, 2.927, 2.961, + 2.881, 2.832, 2.795, 2.769, 2.751, 2.729, 2.701, 2.672, 2.652, 2.639, 2.633, 2.631, 2.628, 2.625, 2.611, 2.599, 2.607, 2.619, 2.635, 2.644, 2.652, 2.659, 2.669, 2.686, 2.698, 2.719, 2.743, 2.775, 2.822, 2.881, 2.926, 2.961, + 2.879, 2.829, 2.793, 2.771, 2.751, 2.731, 2.701, 2.672, 2.651, 2.639, 2.632, 2.628, 2.626, 2.621, 2.601, 2.581, 2.581, 2.611, 2.631, 2.642, 2.648, 2.657, 2.669, 2.685, 2.699, 2.721, 2.743, 2.776, 2.819, 2.879, 2.927, 2.961, + 2.876, 2.829, 2.796, 2.773, 2.752, 2.731, 2.705, 2.672, 2.651, 2.637, 2.631, 2.627, 2.625, 2.619, 2.601, 2.581, 2.581, 2.611, 2.629, 2.641, 2.647, 2.658, 2.669, 2.685, 2.697, 2.721, 2.746, 2.777, 2.822, 2.881, 2.929, 2.964, + 2.874, 2.827, 2.796, 2.775, 2.755, 2.733, 2.708, 2.674, 2.649, 2.635, 2.629, 2.626, 2.624, 2.621, 2.609, 2.601, 2.606, 2.615, 2.629, 2.638, 2.645, 2.657, 2.669, 2.682, 2.699, 2.722, 2.747, 2.778, 2.822, 2.881, 2.931, 2.964, + 2.871, 2.827, 2.797, 2.776, 2.761, 2.734, 2.711, 2.679, 2.651, 2.636, 2.628, 2.626, 2.624, 2.621, 2.618, 2.611, 2.614, 2.619, 2.628, 2.639, 2.644, 2.657, 2.668, 2.683, 2.698, 2.723, 2.749, 2.782, 2.824, 2.882, 2.933, 2.965, + 2.869, 2.825, 2.797, 2.777, 2.765, 2.741, 2.718, 2.683, 2.655, 2.638, 2.627, 2.625, 2.624, 2.623, 2.621, 2.618, 2.618, 2.624, 2.629, 2.639, 2.644, 2.657, 2.669, 2.684, 2.701, 2.725, 2.755, 2.782, 2.829, 2.887, 2.937, 2.965, + 2.871, 2.826, 2.799, 2.776, 2.765, 2.744, 2.723, 2.689, 2.659, 2.639, 2.629, 2.626, 2.626, 2.624, 2.624, 2.622, 2.624, 2.627, 2.632, 2.639, 2.646, 2.657, 2.671, 2.687, 2.706, 2.732, 2.757, 2.789, 2.836, 2.893, 2.941, 2.965, + 2.869, 2.831, 2.803, 2.778, 2.766, 2.748, 2.729, 2.697, 2.667, 2.645, 2.632, 2.628, 2.625, 2.625, 2.625, 2.625, 2.627, 2.629, 2.634, 2.638, 2.648, 2.661, 2.673, 2.688, 2.711, 2.741, 2.762, 2.797, 2.843, 2.901, 2.943, 2.964, + 2.872, 2.837, 2.802, 2.781, 2.768, 2.753, 2.734, 2.702, 2.674, 2.647, 2.634, 2.629, 2.626, 2.625, 2.625, 2.627, 2.629, 2.632, 2.635, 2.639, 2.649, 2.663, 2.676, 2.694, 2.719, 2.746, 2.771, 2.799, 2.851, 2.905, 2.947, 2.969, + 2.871, 2.837, 2.805, 2.786, 2.771, 2.755, 2.739, 2.714, 2.685, 2.655, 2.639, 2.631, 2.626, 2.625, 2.626, 2.628, 2.629, 2.632, 2.634, 2.642, 2.651, 2.663, 2.679, 2.701, 2.726, 2.756, 2.773, 2.809, 2.861, 2.913, 2.949, 2.968, + 2.876, 2.841, 2.808, 2.789, 2.775, 2.759, 2.744, 2.719, 2.693, 2.664, 2.648, 2.636, 2.629, 2.627, 2.627, 2.629, 2.631, 2.633, 2.637, 2.645, 2.653, 2.666, 2.682, 2.708, 2.734, 2.759, 2.779, 2.815, 2.868, 2.918, 2.951, 2.971, + 2.882, 2.845, 2.816, 2.791, 2.778, 2.766, 2.748, 2.733, 2.707, 2.681, 2.656, 2.643, 2.636, 2.632, 2.631, 2.632, 2.633, 2.637, 2.643, 2.648, 2.659, 2.672, 2.691, 2.719, 2.747, 2.765, 2.791, 2.829, 2.881, 2.931, 2.952, 2.969, + 2.889, 2.855, 2.819, 2.799, 2.782, 2.769, 2.755, 2.741, 2.717, 2.691, 2.672, 2.652, 2.643, 2.639, 2.636, 2.636, 2.638, 2.642, 2.646, 2.655, 2.665, 2.682, 2.703, 2.729, 2.752, 2.774, 2.798, 2.839, 2.891, 2.933, 2.959, 2.975, + 2.897, 2.862, 2.829, 2.804, 2.789, 2.776, 2.764, 2.749, 2.734, 2.709, 2.689, 2.669, 2.652, 2.644, 2.642, 2.642, 2.644, 2.647, 2.654, 2.664, 2.677, 2.694, 2.714, 2.742, 2.764, 2.782, 2.809, 2.852, 2.899, 2.936, 2.961, 2.976, + 2.902, 2.869, 2.841, 2.811, 2.797, 2.785, 2.776, 2.761, 2.748, 2.727, 2.708, 2.689, 2.671, 2.659, 2.655, 2.654, 2.653, 2.656, 2.666, 2.678, 2.693, 2.713, 2.737, 2.756, 2.775, 2.798, 2.825, 2.871, 2.913, 2.944, 2.966, 2.979, + 2.911, 2.885, 2.848, 2.821, 2.804, 2.793, 2.784, 2.774, 2.759, 2.747, 2.726, 2.709, 2.692, 2.679, 2.673, 2.672, 2.671, 2.672, 2.681, 2.694, 2.712, 2.729, 2.749, 2.768, 2.789, 2.811, 2.844, 2.886, 2.928, 2.956, 2.971, 2.984, + 2.925, 2.893, 2.861, 2.831, 2.813, 2.802, 2.795, 2.783, 2.773, 2.759, 2.744, 2.729, 2.715, 2.701, 2.698, 2.694, 2.693, 2.694, 2.702, 2.714, 2.729, 2.747, 2.761, 2.781, 2.802, 2.828, 2.864, 2.907, 2.942, 2.967, 2.978, 2.989, + 2.932, 2.898, 2.871, 2.843, 2.823, 2.811, 2.802, 2.794, 2.779, 2.772, 2.757, 2.742, 2.729, 2.716, 2.705, 2.704, 2.704, 2.707, 2.715, 2.727, 2.737, 2.754, 2.769, 2.788, 2.812, 2.845, 2.878, 2.923, 2.962, 2.973, 2.979, 2.994 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.018, 3.021, 3.026, 3.052, 3.092, 3.143, 3.181, 3.202, 3.209, 3.212, 3.211, 3.209, 3.197, 3.193, 3.185, 3.184, 3.185, 3.187, 3.191, 3.202, 3.211, 3.213, 3.212, 3.203, 3.189, 3.147, 3.099, 3.051, 3.032, 3.031, 3.048, 3.054, + 3.019, 3.023, 3.033, 3.066, 3.123, 3.163, 3.196, 3.206, 3.212, 3.212, 3.211, 3.203, 3.193, 3.179, 3.168, 3.159, 3.159, 3.163, 3.174, 3.188, 3.203, 3.208, 3.211, 3.209, 3.195, 3.168, 3.114, 3.064, 3.035, 3.033, 3.044, 3.051, + 3.021, 3.028, 3.046, 3.099, 3.156, 3.192, 3.209, 3.215, 3.216, 3.213, 3.203, 3.193, 3.176, 3.159, 3.153, 3.151, 3.149, 3.152, 3.159, 3.171, 3.188, 3.201, 3.209, 3.211, 3.207, 3.189, 3.142, 3.083, 3.042, 3.038, 3.043, 3.046, + 3.022, 3.037, 3.065, 3.124, 3.178, 3.206, 3.215, 3.221, 3.218, 3.217, 3.198, 3.179, 3.162, 3.149, 3.138, 3.133, 3.133, 3.136, 3.145, 3.156, 3.174, 3.192, 3.206, 3.215, 3.214, 3.202, 3.159, 3.105, 3.058, 3.042, 3.043, 3.049, + 3.024, 3.047, 3.084, 3.151, 3.195, 3.211, 3.219, 3.223, 3.218, 3.208, 3.182, 3.164, 3.149, 3.137, 3.127, 3.119, 3.119, 3.124, 3.134, 3.144, 3.157, 3.178, 3.194, 3.213, 3.215, 3.208, 3.166, 3.124, 3.074, 3.044, 3.044, 3.049, + 3.023, 3.058, 3.102, 3.161, 3.201, 3.217, 3.224, 3.223, 3.217, 3.195, 3.174, 3.156, 3.137, 3.125, 3.115, 3.109, 3.109, 3.115, 3.121, 3.131, 3.146, 3.159, 3.186, 3.208, 3.213, 3.211, 3.181, 3.138, 3.084, 3.047, 3.047, 3.049, + 3.031, 3.063, 3.126, 3.183, 3.212, 3.224, 3.225, 3.224, 3.216, 3.191, 3.167, 3.143, 3.129, 3.115, 3.105, 3.103, 3.103, 3.107, 3.114, 3.121, 3.131, 3.148, 3.169, 3.199, 3.211, 3.209, 3.186, 3.151, 3.089, 3.051, 3.049, 3.052, + 3.033, 3.083, 3.141, 3.201, 3.221, 3.226, 3.226, 3.224, 3.212, 3.187, 3.159, 3.138, 3.119, 3.107, 3.101, 3.098, 3.098, 3.102, 3.107, 3.115, 3.124, 3.138, 3.161, 3.185, 3.207, 3.209, 3.197, 3.162, 3.112, 3.059, 3.056, 3.057, + 3.038, 3.092, 3.159, 3.212, 3.225, 3.231, 3.228, 3.224, 3.209, 3.181, 3.152, 3.129, 3.112, 3.103, 3.095, 3.092, 3.093, 3.095, 3.101, 3.108, 3.118, 3.133, 3.152, 3.179, 3.203, 3.209, 3.205, 3.174, 3.124, 3.069, 3.059, 3.058, + 3.049, 3.105, 3.176, 3.223, 3.229, 3.231, 3.229, 3.223, 3.206, 3.171, 3.147, 3.125, 3.109, 3.097, 3.091, 3.089, 3.088, 3.091, 3.094, 3.102, 3.111, 3.124, 3.143, 3.169, 3.196, 3.208, 3.207, 3.181, 3.132, 3.079, 3.064, 3.063, + 3.055, 3.123, 3.189, 3.226, 3.232, 3.232, 3.229, 3.225, 3.204, 3.169, 3.143, 3.122, 3.108, 3.095, 3.092, 3.089, 3.088, 3.088, 3.092, 3.095, 3.105, 3.117, 3.135, 3.159, 3.191, 3.208, 3.208, 3.189, 3.141, 3.084, 3.064, 3.062, + 3.057, 3.127, 3.198, 3.228, 3.233, 3.233, 3.229, 3.225, 3.201, 3.166, 3.139, 3.119, 3.106, 3.096, 3.093, 3.092, 3.088, 3.088, 3.089, 3.093, 3.099, 3.114, 3.129, 3.156, 3.186, 3.208, 3.208, 3.195, 3.143, 3.089, 3.065, 3.064, + 3.066, 3.142, 3.209, 3.232, 3.234, 3.233, 3.231, 3.226, 3.198, 3.166, 3.138, 3.117, 3.103, 3.097, 3.095, 3.095, 3.094, 3.089, 3.089, 3.092, 3.097, 3.109, 3.126, 3.155, 3.183, 3.207, 3.207, 3.198, 3.147, 3.091, 3.069, 3.065, + 3.072, 3.153, 3.216, 3.231, 3.234, 3.234, 3.229, 3.226, 3.194, 3.165, 3.136, 3.114, 3.101, 3.098, 3.098, 3.104, 3.098, 3.091, 3.088, 3.089, 3.093, 3.103, 3.123, 3.151, 3.181, 3.204, 3.204, 3.197, 3.156, 3.095, 3.069, 3.068, + 3.079, 3.159, 3.222, 3.233, 3.236, 3.235, 3.231, 3.226, 3.194, 3.165, 3.133, 3.112, 3.102, 3.099, 3.107, 3.114, 3.111, 3.097, 3.089, 3.089, 3.091, 3.099, 3.121, 3.149, 3.182, 3.202, 3.202, 3.195, 3.156, 3.096, 3.069, 3.068, + 3.081, 3.164, 3.226, 3.233, 3.236, 3.235, 3.233, 3.229, 3.199, 3.165, 3.137, 3.113, 3.102, 3.102, 3.111, 3.134, 3.134, 3.103, 3.091, 3.089, 3.092, 3.101, 3.119, 3.147, 3.182, 3.202, 3.202, 3.194, 3.155, 3.095, 3.069, 3.067, + 3.085, 3.163, 3.225, 3.236, 3.239, 3.235, 3.234, 3.231, 3.203, 3.169, 3.141, 3.115, 3.103, 3.103, 3.111, 3.134, 3.134, 3.106, 3.092, 3.091, 3.093, 3.103, 3.119, 3.149, 3.185, 3.203, 3.203, 3.193, 3.152, 3.095, 3.068, 3.066, + 3.083, 3.168, 3.226, 3.236, 3.241, 3.235, 3.235, 3.231, 3.205, 3.174, 3.144, 3.117, 3.107, 3.103, 3.107, 3.116, 3.109, 3.103, 3.091, 3.091, 3.095, 3.107, 3.123, 3.152, 3.188, 3.204, 3.204, 3.193, 3.151, 3.095, 3.069, 3.066, + 3.082, 3.171, 3.228, 3.237, 3.239, 3.235, 3.234, 3.233, 3.217, 3.184, 3.147, 3.119, 3.108, 3.104, 3.103, 3.105, 3.102, 3.095, 3.091, 3.091, 3.097, 3.111, 3.128, 3.157, 3.191, 3.204, 3.204, 3.185, 3.149, 3.094, 3.069, 3.065, + 3.086, 3.173, 3.226, 3.237, 3.239, 3.235, 3.234, 3.232, 3.221, 3.185, 3.155, 3.124, 3.112, 3.105, 3.102, 3.099, 3.096, 3.094, 3.092, 3.094, 3.102, 3.114, 3.133, 3.163, 3.197, 3.205, 3.204, 3.183, 3.144, 3.089, 3.068, 3.065, + 3.086, 3.166, 3.225, 3.239, 3.239, 3.237, 3.233, 3.231, 3.223, 3.193, 3.165, 3.135, 3.118, 3.108, 3.101, 3.098, 3.095, 3.093, 3.093, 3.099, 3.109, 3.124, 3.145, 3.174, 3.199, 3.204, 3.203, 3.181, 3.132, 3.085, 3.067, 3.062, + 3.086, 3.162, 3.224, 3.239, 3.241, 3.236, 3.232, 3.229, 3.224, 3.201, 3.174, 3.147, 3.128, 3.114, 3.103, 3.099, 3.096, 3.095, 3.097, 3.106, 3.116, 3.134, 3.151, 3.182, 3.201, 3.203, 3.201, 3.176, 3.125, 3.078, 3.065, 3.061, + 3.077, 3.162, 3.221, 3.239, 3.241, 3.234, 3.229, 3.227, 3.225, 3.207, 3.186, 3.161, 3.137, 3.122, 3.112, 3.102, 3.099, 3.098, 3.106, 3.113, 3.127, 3.139, 3.159, 3.192, 3.204, 3.205, 3.198, 3.167, 3.119, 3.073, 3.062, 3.061, + 3.077, 3.161, 3.216, 3.234, 3.236, 3.232, 3.225, 3.225, 3.222, 3.209, 3.194, 3.172, 3.148, 3.132, 3.121, 3.113, 3.107, 3.107, 3.112, 3.124, 3.135, 3.151, 3.175, 3.196, 3.201, 3.201, 3.191, 3.161, 3.114, 3.062, 3.058, 3.057, + 3.073, 3.139, 3.201, 3.227, 3.232, 3.227, 3.223, 3.219, 3.216, 3.212, 3.203, 3.181, 3.161, 3.142, 3.129, 3.121, 3.114, 3.114, 3.124, 3.134, 3.145, 3.161, 3.179, 3.196, 3.199, 3.195, 3.182, 3.145, 3.093, 3.052, 3.051, 3.052, + 3.066, 3.126, 3.192, 3.218, 3.224, 3.221, 3.218, 3.214, 3.214, 3.209, 3.204, 3.191, 3.174, 3.155, 3.142, 3.129, 3.127, 3.127, 3.136, 3.145, 3.157, 3.175, 3.187, 3.194, 3.196, 3.192, 3.171, 3.134, 3.082, 3.043, 3.042, 3.044, + 3.056, 3.114, 3.176, 3.212, 3.219, 3.219, 3.214, 3.209, 3.208, 3.206, 3.203, 3.198, 3.182, 3.171, 3.155, 3.146, 3.144, 3.144, 3.148, 3.156, 3.171, 3.181, 3.188, 3.194, 3.194, 3.187, 3.161, 3.117, 3.066, 3.037, 3.037, 3.044, + 3.054, 3.101, 3.162, 3.203, 3.216, 3.215, 3.211, 3.206, 3.203, 3.201, 3.199, 3.197, 3.191, 3.179, 3.171, 3.161, 3.156, 3.156, 3.161, 3.171, 3.179, 3.184, 3.189, 3.192, 3.191, 3.181, 3.142, 3.097, 3.045, 3.032, 3.033, 3.039, + 3.041, 3.093, 3.149, 3.194, 3.208, 3.211, 3.208, 3.202, 3.197, 3.197, 3.197, 3.195, 3.191, 3.189, 3.181, 3.176, 3.172, 3.173, 3.178, 3.181, 3.185, 3.187, 3.189, 3.191, 3.189, 3.173, 3.133, 3.085, 3.034, 3.029, 3.031, 3.038, + 3.032, 3.079, 3.133, 3.181, 3.197, 3.207, 3.204, 3.198, 3.193, 3.192, 3.189, 3.191, 3.189, 3.187, 3.185, 3.183, 3.183, 3.183, 3.185, 3.188, 3.187, 3.188, 3.189, 3.188, 3.184, 3.164, 3.118, 3.075, 3.031, 3.026, 3.028, 3.039, + 3.025, 3.051, 3.099, 3.149, 3.182, 3.193, 3.193, 3.187, 3.181, 3.178, 3.177, 3.177, 3.182, 3.183, 3.183, 3.183, 3.183, 3.184, 3.187, 3.188, 3.186, 3.184, 3.184, 3.181, 3.167, 3.139, 3.098, 3.053, 3.026, 3.024, 3.029, 3.043, + 3.016, 3.025, 3.081, 3.122, 3.167, 3.182, 3.185, 3.181, 3.176, 3.171, 3.169, 3.171, 3.174, 3.175, 3.178, 3.178, 3.179, 3.181, 3.185, 3.185, 3.181, 3.179, 3.177, 3.173, 3.151, 3.119, 3.076, 3.031, 3.021, 3.018, 3.024, 3.046 + ] + }, + { + "ct": 5000, + "table": + [ + 1.503, 1.503, 1.504, 1.515, 1.541, 1.566, 1.587, 1.599, 1.602, 1.603, 1.602, 1.599, 1.595, 1.589, 1.587, 1.586, 1.586, 1.587, 1.589, 1.594, 1.601, 1.604, 1.604, 1.601, 1.589, 1.571, 1.541, 1.517, 1.512, 1.512, 1.522, 1.526, + 1.501, 1.502, 1.506, 1.523, 1.557, 1.579, 1.596, 1.603, 1.603, 1.603, 1.601, 1.597, 1.591, 1.582, 1.576, 1.575, 1.574, 1.577, 1.581, 1.588, 1.595, 1.601, 1.603, 1.602, 1.597, 1.578, 1.553, 1.526, 1.512, 1.512, 1.519, 1.526, + 1.499, 1.503, 1.512, 1.539, 1.571, 1.593, 1.603, 1.604, 1.604, 1.602, 1.597, 1.591, 1.581, 1.573, 1.568, 1.566, 1.566, 1.568, 1.572, 1.579, 1.587, 1.594, 1.602, 1.603, 1.601, 1.589, 1.566, 1.536, 1.517, 1.516, 1.519, 1.525, + 1.499, 1.505, 1.521, 1.553, 1.582, 1.597, 1.604, 1.604, 1.604, 1.601, 1.592, 1.582, 1.573, 1.564, 1.561, 1.558, 1.557, 1.559, 1.564, 1.571, 1.579, 1.588, 1.597, 1.603, 1.603, 1.596, 1.576, 1.545, 1.519, 1.517, 1.518, 1.526, + 1.499, 1.509, 1.529, 1.565, 1.591, 1.601, 1.605, 1.604, 1.602, 1.597, 1.586, 1.573, 1.565, 1.558, 1.553, 1.551, 1.551, 1.552, 1.555, 1.563, 1.571, 1.581, 1.592, 1.601, 1.602, 1.599, 1.582, 1.556, 1.528, 1.517, 1.517, 1.526, + 1.501, 1.512, 1.539, 1.576, 1.595, 1.603, 1.605, 1.604, 1.601, 1.591, 1.579, 1.567, 1.559, 1.552, 1.548, 1.545, 1.546, 1.548, 1.551, 1.555, 1.563, 1.574, 1.585, 1.598, 1.602, 1.601, 1.589, 1.562, 1.535, 1.519, 1.519, 1.528, + 1.501, 1.517, 1.552, 1.587, 1.601, 1.605, 1.605, 1.605, 1.599, 1.588, 1.574, 1.562, 1.553, 1.548, 1.544, 1.543, 1.543, 1.545, 1.547, 1.551, 1.557, 1.567, 1.578, 1.593, 1.601, 1.601, 1.592, 1.571, 1.539, 1.521, 1.521, 1.529, + 1.503, 1.524, 1.561, 1.593, 1.605, 1.606, 1.605, 1.603, 1.598, 1.585, 1.569, 1.558, 1.551, 1.545, 1.542, 1.541, 1.541, 1.542, 1.545, 1.547, 1.555, 1.561, 1.573, 1.587, 1.598, 1.601, 1.596, 1.577, 1.546, 1.523, 1.523, 1.529, + 1.503, 1.532, 1.568, 1.597, 1.605, 1.606, 1.605, 1.603, 1.596, 1.581, 1.565, 1.555, 1.548, 1.544, 1.541, 1.539, 1.541, 1.541, 1.543, 1.546, 1.549, 1.558, 1.568, 1.583, 1.595, 1.601, 1.599, 1.582, 1.555, 1.525, 1.525, 1.531, + 1.508, 1.539, 1.575, 1.601, 1.605, 1.606, 1.605, 1.602, 1.593, 1.577, 1.563, 1.552, 1.546, 1.543, 1.541, 1.539, 1.539, 1.541, 1.542, 1.544, 1.548, 1.553, 1.564, 1.579, 1.592, 1.599, 1.599, 1.585, 1.559, 1.532, 1.531, 1.531, + 1.511, 1.544, 1.581, 1.603, 1.606, 1.606, 1.604, 1.603, 1.591, 1.574, 1.561, 1.549, 1.545, 1.542, 1.541, 1.541, 1.541, 1.541, 1.542, 1.543, 1.545, 1.551, 1.561, 1.573, 1.591, 1.599, 1.599, 1.588, 1.563, 1.535, 1.531, 1.531, + 1.515, 1.548, 1.589, 1.605, 1.607, 1.607, 1.604, 1.602, 1.591, 1.573, 1.559, 1.549, 1.543, 1.542, 1.541, 1.542, 1.542, 1.542, 1.541, 1.542, 1.543, 1.549, 1.558, 1.571, 1.588, 1.599, 1.599, 1.591, 1.566, 1.537, 1.532, 1.531, + 1.517, 1.558, 1.593, 1.606, 1.607, 1.607, 1.605, 1.602, 1.589, 1.572, 1.557, 1.548, 1.543, 1.543, 1.542, 1.544, 1.543, 1.543, 1.541, 1.541, 1.542, 1.546, 1.554, 1.569, 1.585, 1.599, 1.599, 1.593, 1.568, 1.538, 1.533, 1.531, + 1.521, 1.563, 1.596, 1.607, 1.608, 1.607, 1.606, 1.603, 1.589, 1.572, 1.557, 1.548, 1.543, 1.543, 1.544, 1.549, 1.546, 1.544, 1.541, 1.541, 1.542, 1.545, 1.553, 1.568, 1.585, 1.598, 1.598, 1.594, 1.571, 1.541, 1.534, 1.531, + 1.521, 1.566, 1.599, 1.607, 1.608, 1.607, 1.605, 1.603, 1.591, 1.571, 1.556, 1.547, 1.544, 1.544, 1.551, 1.554, 1.552, 1.546, 1.541, 1.541, 1.541, 1.544, 1.553, 1.567, 1.585, 1.597, 1.598, 1.595, 1.571, 1.541, 1.534, 1.531, + 1.523, 1.568, 1.601, 1.607, 1.608, 1.607, 1.606, 1.604, 1.591, 1.572, 1.557, 1.547, 1.545, 1.545, 1.552, 1.566, 1.566, 1.551, 1.542, 1.541, 1.541, 1.544, 1.553, 1.567, 1.586, 1.596, 1.596, 1.593, 1.571, 1.541, 1.533, 1.531, + 1.524, 1.569, 1.602, 1.607, 1.608, 1.607, 1.606, 1.604, 1.591, 1.573, 1.559, 1.548, 1.545, 1.546, 1.552, 1.565, 1.565, 1.551, 1.542, 1.541, 1.541, 1.545, 1.553, 1.568, 1.586, 1.597, 1.597, 1.593, 1.571, 1.541, 1.532, 1.532, + 1.526, 1.571, 1.602, 1.607, 1.608, 1.606, 1.605, 1.604, 1.593, 1.575, 1.559, 1.549, 1.546, 1.546, 1.549, 1.552, 1.552, 1.546, 1.542, 1.541, 1.542, 1.546, 1.555, 1.569, 1.587, 1.597, 1.597, 1.591, 1.569, 1.539, 1.532, 1.531, + 1.526, 1.571, 1.601, 1.608, 1.609, 1.605, 1.605, 1.603, 1.597, 1.579, 1.562, 1.551, 1.546, 1.545, 1.545, 1.549, 1.546, 1.543, 1.542, 1.541, 1.542, 1.547, 1.557, 1.573, 1.588, 1.597, 1.597, 1.589, 1.566, 1.537, 1.531, 1.529, + 1.526, 1.569, 1.602, 1.609, 1.609, 1.606, 1.605, 1.604, 1.598, 1.582, 1.567, 1.553, 1.547, 1.545, 1.544, 1.544, 1.544, 1.542, 1.542, 1.542, 1.544, 1.552, 1.559, 1.576, 1.591, 1.597, 1.597, 1.588, 1.563, 1.535, 1.531, 1.529, + 1.523, 1.567, 1.601, 1.609, 1.609, 1.606, 1.605, 1.603, 1.599, 1.587, 1.571, 1.558, 1.549, 1.545, 1.544, 1.543, 1.543, 1.542, 1.542, 1.544, 1.548, 1.555, 1.566, 1.581, 1.593, 1.597, 1.597, 1.586, 1.558, 1.534, 1.529, 1.529, + 1.523, 1.564, 1.599, 1.609, 1.609, 1.605, 1.604, 1.603, 1.601, 1.592, 1.576, 1.564, 1.553, 1.547, 1.544, 1.543, 1.542, 1.542, 1.544, 1.548, 1.551, 1.561, 1.572, 1.585, 1.594, 1.596, 1.595, 1.581, 1.555, 1.528, 1.527, 1.528, + 1.522, 1.561, 1.595, 1.608, 1.608, 1.604, 1.602, 1.601, 1.601, 1.595, 1.582, 1.569, 1.559, 1.552, 1.547, 1.545, 1.543, 1.544, 1.546, 1.551, 1.556, 1.563, 1.576, 1.589, 1.595, 1.596, 1.593, 1.576, 1.551, 1.524, 1.524, 1.528, + 1.519, 1.559, 1.591, 1.605, 1.606, 1.603, 1.601, 1.599, 1.601, 1.597, 1.587, 1.576, 1.565, 1.558, 1.552, 1.549, 1.546, 1.547, 1.552, 1.556, 1.561, 1.571, 1.582, 1.593, 1.596, 1.596, 1.591, 1.569, 1.546, 1.521, 1.521, 1.527, + 1.516, 1.553, 1.589, 1.602, 1.604, 1.602, 1.599, 1.598, 1.599, 1.598, 1.594, 1.583, 1.572, 1.564, 1.559, 1.553, 1.552, 1.553, 1.556, 1.561, 1.567, 1.578, 1.588, 1.594, 1.596, 1.594, 1.588, 1.567, 1.539, 1.517, 1.517, 1.525, + 1.511, 1.548, 1.581, 1.599, 1.602, 1.602, 1.598, 1.597, 1.597, 1.597, 1.595, 1.589, 1.581, 1.571, 1.564, 1.559, 1.559, 1.558, 1.561, 1.567, 1.575, 1.583, 1.591, 1.593, 1.594, 1.591, 1.581, 1.557, 1.529, 1.514, 1.514, 1.521, + 1.508, 1.541, 1.576, 1.596, 1.601, 1.601, 1.597, 1.595, 1.594, 1.595, 1.595, 1.592, 1.585, 1.579, 1.571, 1.566, 1.566, 1.566, 1.568, 1.575, 1.582, 1.589, 1.592, 1.593, 1.593, 1.589, 1.575, 1.553, 1.523, 1.511, 1.511, 1.517, + 1.505, 1.535, 1.566, 1.591, 1.599, 1.598, 1.596, 1.594, 1.592, 1.592, 1.593, 1.592, 1.589, 1.585, 1.579, 1.575, 1.574, 1.574, 1.577, 1.582, 1.587, 1.591, 1.592, 1.593, 1.592, 1.585, 1.568, 1.541, 1.516, 1.509, 1.509, 1.517, + 1.501, 1.528, 1.559, 1.585, 1.595, 1.597, 1.595, 1.593, 1.589, 1.588, 1.591, 1.591, 1.591, 1.589, 1.586, 1.583, 1.582, 1.582, 1.585, 1.588, 1.589, 1.591, 1.592, 1.593, 1.592, 1.582, 1.561, 1.536, 1.512, 1.509, 1.511, 1.517, + 1.496, 1.521, 1.549, 1.576, 1.588, 1.594, 1.593, 1.589, 1.586, 1.585, 1.586, 1.588, 1.589, 1.588, 1.588, 1.587, 1.587, 1.587, 1.589, 1.589, 1.591, 1.591, 1.592, 1.592, 1.591, 1.575, 1.555, 1.527, 1.508, 1.507, 1.511, 1.519, + 1.495, 1.505, 1.536, 1.563, 1.581, 1.587, 1.588, 1.584, 1.582, 1.578, 1.578, 1.581, 1.583, 1.584, 1.586, 1.587, 1.587, 1.587, 1.588, 1.589, 1.589, 1.591, 1.591, 1.591, 1.584, 1.566, 1.544, 1.518, 1.505, 1.505, 1.509, 1.519, + 1.493, 1.496, 1.522, 1.547, 1.569, 1.581, 1.582, 1.581, 1.577, 1.575, 1.573, 1.575, 1.579, 1.581, 1.583, 1.584, 1.584, 1.585, 1.587, 1.587, 1.588, 1.588, 1.588, 1.585, 1.573, 1.556, 1.532, 1.511, 1.504, 1.504, 1.508, 1.523 + ] + } + ], + "luminance_lut": + [ + 4.461, 4.088, 3.793, 3.651, 3.557, 3.439, 3.248, 2.999, 2.751, 2.527, 2.341, 2.191, 2.069, 1.956, 1.907, 1.907, 1.907, 1.908, 1.946, 2.056, 2.179, 2.328, 2.517, 2.747, 2.998, 3.219, 3.359, 3.436, 3.494, 3.621, 3.906, 4.251, + 4.297, 3.982, 3.747, 3.634, 3.531, 3.373, 3.136, 2.863, 2.608, 2.386, 2.209, 2.075, 1.957, 1.873, 1.817, 1.789, 1.789, 1.813, 1.865, 1.947, 2.066, 2.198, 2.378, 2.605, 2.872, 3.132, 3.322, 3.431, 3.485, 3.577, 3.802, 4.079, + 4.152, 3.905, 3.717, 3.623, 3.499, 3.296, 3.022, 2.735, 2.478, 2.265, 2.094, 1.957, 1.849, 1.763, 1.709, 1.679, 1.679, 1.703, 1.753, 1.837, 1.947, 2.081, 2.253, 2.472, 2.742, 3.032, 3.271, 3.414, 3.479, 3.545, 3.719, 3.937, + 4.039, 3.835, 3.688, 3.596, 3.442, 3.196, 2.899, 2.609, 2.356, 2.153, 1.987, 1.849, 1.748, 1.659, 1.605, 1.577, 1.577, 1.599, 1.649, 1.734, 1.837, 1.973, 2.139, 2.348, 2.612, 2.911, 3.192, 3.379, 3.467, 3.516, 3.649, 3.815, + 3.952, 3.784, 3.669, 3.562, 3.369, 3.088, 2.778, 2.491, 2.246, 2.049, 1.888, 1.748, 1.657, 1.561, 1.509, 1.481, 1.481, 1.504, 1.552, 1.642, 1.734, 1.869, 2.033, 2.233, 2.489, 2.792, 3.105, 3.331, 3.445, 3.493, 3.591, 3.721, + 3.883, 3.741, 3.648, 3.519, 3.287, 2.977, 2.665, 2.382, 2.148, 1.957, 1.796, 1.659, 1.561, 1.474, 1.422, 1.396, 1.396, 1.415, 1.465, 1.552, 1.643, 1.776, 1.936, 2.131, 2.375, 2.678, 3.004, 3.275, 3.416, 3.469, 3.541, 3.643, + 3.829, 3.716, 3.617, 3.466, 3.202, 2.876, 2.558, 2.282, 2.059, 1.872, 1.713, 1.577, 1.474, 1.399, 1.345, 1.319, 1.319, 1.338, 1.389, 1.465, 1.559, 1.689, 1.849, 2.042, 2.275, 2.568, 2.903, 3.204, 3.383, 3.446, 3.496, 3.579, + 3.793, 3.685, 3.589, 3.411, 3.119, 2.781, 2.466, 2.199, 1.983, 1.798, 1.639, 1.505, 1.399, 1.339, 1.276, 1.253, 1.253, 1.271, 1.327, 1.389, 1.487, 1.612, 1.769, 1.961, 2.189, 2.471, 2.806, 3.133, 3.342, 3.425, 3.459, 3.527, + 3.763, 3.666, 3.561, 3.357, 3.042, 2.698, 2.384, 2.129, 1.918, 1.734, 1.575, 1.443, 1.339, 1.276, 1.217, 1.194, 1.194, 1.214, 1.271, 1.327, 1.423, 1.546, 1.702, 1.891, 2.112, 2.386, 2.718, 3.061, 3.301, 3.402, 3.433, 3.486, + 3.745, 3.647, 3.529, 3.302, 2.971, 2.627, 2.318, 2.067, 1.859, 1.677, 1.521, 1.389, 1.287, 1.217, 1.171, 1.145, 1.145, 1.165, 1.214, 1.276, 1.369, 1.491, 1.643, 1.831, 2.048, 2.313, 2.644, 2.995, 3.262, 3.381, 3.412, 3.453, + 3.731, 3.635, 3.503, 3.249, 2.911, 2.566, 2.259, 2.017, 1.811, 1.629, 1.475, 1.347, 1.246, 1.171, 1.138, 1.103, 1.103, 1.129, 1.165, 1.231, 1.322, 1.443, 1.595, 1.779, 1.993, 2.251, 2.576, 2.936, 3.223, 3.359, 3.392, 3.425, + 3.721, 3.625, 3.481, 3.208, 2.861, 2.515, 2.213, 1.976, 1.773, 1.593, 1.439, 1.313, 1.213, 1.138, 1.103, 1.071, 1.071, 1.101, 1.129, 1.194, 1.286, 1.405, 1.555, 1.736, 1.949, 2.202, 2.521, 2.886, 3.189, 3.338, 3.375, 3.406, + 3.716, 3.616, 3.458, 3.171, 2.819, 2.472, 2.176, 1.942, 1.741, 1.563, 1.411, 1.285, 1.186, 1.112, 1.071, 1.051, 1.049, 1.069, 1.103, 1.165, 1.256, 1.376, 1.523, 1.702, 1.913, 2.163, 2.477, 2.843, 3.155, 3.318, 3.358, 3.389, + 3.712, 3.609, 3.439, 3.142, 2.787, 2.443, 2.147, 1.918, 1.721, 1.541, 1.391, 1.266, 1.167, 1.094, 1.051, 1.035, 1.035, 1.049, 1.085, 1.145, 1.236, 1.355, 1.499, 1.676, 1.886, 2.136, 2.449, 2.814, 3.135, 3.307, 3.351, 3.378, + 3.709, 3.604, 3.422, 3.123, 2.768, 2.419, 2.129, 1.903, 1.706, 1.527, 1.377, 1.253, 1.155, 1.083, 1.035, 1.023, 1.023, 1.035, 1.074, 1.134, 1.224, 1.341, 1.484, 1.661, 1.868, 2.117, 2.429, 2.797, 3.122, 3.301, 3.346, 3.374, + 3.711, 3.597, 3.412, 3.114, 2.758, 2.409, 2.119, 1.895, 1.701, 1.523, 1.373, 1.251, 1.153, 1.081, 1.033, 1.001, 1.001, 1.032, 1.073, 1.133, 1.222, 1.338, 1.479, 1.655, 1.861, 2.107, 2.418, 2.787, 3.115, 3.297, 3.343, 3.373, + 3.713, 3.597, 3.412, 3.113, 2.758, 2.409, 2.119, 1.893, 1.698, 1.523, 1.373, 1.251, 1.153, 1.081, 1.034, 1.011, 1.011, 1.032, 1.074, 1.134, 1.222, 1.338, 1.479, 1.655, 1.861, 2.107, 2.418, 2.787, 3.116, 3.294, 3.341, 3.371, + 3.721, 3.599, 3.414, 3.116, 2.763, 2.418, 2.124, 1.895, 1.704, 1.531, 1.382, 1.259, 1.162, 1.091, 1.048, 1.034, 1.032, 1.046, 1.083, 1.145, 1.232, 1.348, 1.491, 1.664, 1.869, 2.115, 2.428, 2.798, 3.123, 3.294, 3.339, 3.372, + 3.727, 3.604, 3.421, 3.132, 2.784, 2.438, 2.141, 1.908, 1.716, 1.547, 1.399, 1.276, 1.178, 1.107, 1.069, 1.048, 1.046, 1.067, 1.101, 1.162, 1.249, 1.366, 1.509, 1.684, 1.886, 2.134, 2.449, 2.821, 3.135, 3.299, 3.341, 3.375, + 3.739, 3.613, 3.431, 3.154, 2.813, 2.468, 2.166, 1.931, 1.739, 1.569, 1.424, 1.302, 1.203, 1.129, 1.098, 1.069, 1.069, 1.096, 1.123, 1.185, 1.274, 1.391, 1.536, 1.709, 1.914, 2.162, 2.481, 2.851, 3.156, 3.311, 3.342, 3.378, + 3.751, 3.626, 3.449, 3.186, 2.855, 2.509, 2.201, 1.961, 1.768, 1.601, 1.454, 1.333, 1.235, 1.159, 1.129, 1.098, 1.098, 1.123, 1.152, 1.216, 1.307, 1.424, 1.569, 1.744, 1.947, 2.202, 2.526, 2.891, 3.182, 3.322, 3.351, 3.387, + 3.772, 3.641, 3.473, 3.221, 2.902, 2.559, 2.248, 1.999, 1.804, 1.639, 1.496, 1.373, 1.274, 1.201, 1.159, 1.133, 1.133, 1.152, 1.191, 1.254, 1.347, 1.466, 1.611, 1.785, 1.989, 2.253, 2.582, 2.939, 3.209, 3.334, 3.361, 3.402, + 3.797, 3.663, 3.496, 3.263, 2.959, 2.624, 2.308, 2.049, 1.847, 1.684, 1.542, 1.422, 1.321, 1.252, 1.201, 1.175, 1.175, 1.191, 1.239, 1.298, 1.394, 1.516, 1.658, 1.831, 2.041, 2.313, 2.651, 2.998, 3.244, 3.351, 3.375, 3.422, + 3.831, 3.686, 3.523, 3.307, 3.023, 2.698, 2.379, 2.112, 1.902, 1.737, 1.596, 1.476, 1.378, 1.315, 1.252, 1.227, 1.227, 1.239, 1.296, 1.355, 1.451, 1.572, 1.715, 1.888, 2.103, 2.386, 2.731, 3.063, 3.279, 3.367, 3.393, 3.456, + 3.871, 3.714, 3.551, 3.355, 3.091, 2.781, 2.465, 2.186, 1.965, 1.795, 1.654, 1.538, 1.442, 1.378, 1.318, 1.291, 1.291, 1.304, 1.355, 1.424, 1.515, 1.634, 1.778, 1.952, 2.178, 2.479, 2.821, 3.129, 3.314, 3.381, 3.419, 3.491, + 3.925, 3.749, 3.582, 3.401, 3.156, 2.866, 2.559, 2.274, 2.039, 1.859, 1.718, 1.604, 1.513, 1.442, 1.389, 1.363, 1.363, 1.379, 1.424, 1.501, 1.586, 1.702, 1.847, 2.028, 2.269, 2.579, 2.913, 3.193, 3.343, 3.396, 3.447, 3.539, + 3.994, 3.794, 3.619, 3.442, 3.231, 2.961, 2.662, 2.375, 2.129, 1.938, 1.789, 1.675, 1.591, 1.513, 1.465, 1.439, 1.439, 1.457, 1.501, 1.582, 1.661, 1.777, 1.925, 2.118, 2.375, 2.691, 3.008, 3.251, 3.371, 3.414, 3.479, 3.598, + 4.082, 3.845, 3.656, 3.489, 3.298, 3.053, 2.771, 2.485, 2.232, 2.028, 1.871, 1.751, 1.672, 1.591, 1.544, 1.521, 1.521, 1.539, 1.582, 1.661, 1.741, 1.859, 2.014, 2.224, 2.495, 2.806, 3.098, 3.301, 3.392, 3.431, 3.518, 3.677, + 4.196, 3.911, 3.698, 3.534, 3.363, 3.146, 2.881, 2.604, 2.348, 2.132, 1.964, 1.836, 1.751, 1.672, 1.628, 1.606, 1.606, 1.624, 1.665, 1.741, 1.827, 1.951, 2.121, 2.344, 2.624, 2.923, 3.177, 3.336, 3.405, 3.447, 3.567, 3.776, + 4.341, 4.002, 3.744, 3.575, 3.415, 3.229, 2.989, 2.729, 2.475, 2.251, 2.071, 1.936, 1.836, 1.759, 1.713, 1.693, 1.693, 1.711, 1.753, 1.827, 1.925, 2.058, 2.243, 2.481, 2.758, 3.027, 3.238, 3.361, 3.409, 3.466, 3.637, 3.896, + 4.516, 4.123, 3.804, 3.621, 3.468, 3.308, 3.096, 2.855, 2.609, 2.385, 2.194, 2.045, 1.936, 1.857, 1.807, 1.784, 1.784, 1.803, 1.852, 1.925, 2.033, 2.183, 2.382, 2.623, 2.886, 3.121, 3.284, 3.372, 3.413, 3.494, 3.727, 4.048, + 4.716, 4.264, 3.875, 3.674, 3.523, 3.376, 3.189, 2.966, 2.733, 2.511, 2.315, 2.158, 2.039, 1.936, 1.875, 1.872, 1.872, 1.872, 1.925, 2.028, 2.148, 2.308, 2.513, 2.751, 2.994, 3.191, 3.319, 3.384, 3.427, 3.541, 3.838, 4.221 + ], + "sigma": 0.00152, + "sigma_Cb": 0.00172 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2868, + "ccm": + [ + 1.58923, -0.36649, -0.22273, + -0.43591, 1.84858, -0.41268, + 0.02948, -0.77666, 1.74718 + ] + }, + { + "ct": 2965, + "ccm": + [ + 1.73397, -0.42794, -0.30603, + -0.36504, 1.72431, -0.35926, + 0.12765, -1.10933, 1.98168 + ] + }, + { + "ct": 3603, + "ccm": + [ + 1.61787, -0.42704, -0.19084, + -0.37819, 1.74588, -0.36769, + 0.00961, -0.59807, 1.58847 + ] + }, + { + "ct": 4620, + "ccm": + [ + 1.55581, -0.35422, -0.20158, + -0.31805, 1.79309, -0.47505, + -0.01256, -0.54489, 1.55746 + ] + }, + { + "ct": 5901, + "ccm": + [ + 1.64439, -0.48855, -0.15585, + -0.29149, 1.67122, -0.37972, + -0.03111, -0.44052, 1.47163 + ] + }, + { + "ct": 7610, + "ccm": + [ + 1.48667, -0.26072, -0.22595, + -0.21815, 1.86724, -0.64909, + -0.00985, -0.64485, 1.65471 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 4.0, + "max": 32.0, + "default": 6.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 2.0, + "step_fine": 0.5, + "contrast_ratio": 0.75, + "pdaf_gain": -0.03, + "pdaf_squelch": 0.2, + "max_slew": 4.0, + "pdaf_frames": 20, + "dropout_frames": 6, + "step_frames": 4 + }, + "fast": + { + "step_coarse": 2.0, + "step_fine": 0.5, + "contrast_ratio": 0.75, + "pdaf_gain": -0.05, + "pdaf_squelch": 0.2, + "max_slew": 5.0, + "pdaf_frames": 16, + "dropout_frames": 6, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 12, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 420, 35.0, 920 ] + } + }, + { + "rpi.cac": + { + "strength": 1.0, + "lut_rx": + [ + -0.11, -0.11, -0.17, -0.11, -0.0, 0.08, 0.13, 0.1, 0.1, + -0.07, -0.17, -0.16, -0.08, -0.02, 0.06, 0.15, 0.15, 0.07, + -0.11, -0.21, -0.17, -0.07, -0.02, 0.03, 0.14, 0.17, 0.14, + -0.19, -0.22, -0.16, -0.07, -0.01, 0.03, 0.12, 0.19, 0.21, + -0.19, -0.23, -0.16, -0.06, -0.01, 0.04, 0.13, 0.19, 0.24, + -0.18, -0.22, -0.17, -0.05, -0.01, 0.05, 0.15, 0.2, 0.21, + -0.14, -0.19, -0.17, -0.06, 0.0, 0.07, 0.15, 0.18, 0.15, + -0.09, -0.14, -0.17, -0.08, 0.0, 0.09, 0.15, 0.14, 0.06, + -0.09, -0.08, -0.15, -0.12, -0.0, 0.12, 0.16, 0.07, 0.06 + ], + "lut_ry": + [ + -0.11, -0.11, -0.21, -0.21, -0.19, -0.21, -0.19, -0.11, 0.11, + -0.02, -0.1, -0.14, -0.14, -0.13, -0.14, -0.15, -0.11, 0.03, + -0.03, -0.09, -0.12, -0.12, -0.12, -0.11, -0.12, -0.1, -0.02, + -0.05, -0.07, -0.1, -0.11, -0.11, -0.09, -0.08, -0.07, -0.03, + -0.03, -0.02, -0.04, -0.05, -0.05, -0.05, -0.02, -0.01, -0.02, + 0.01, 0.03, 0.0, -0.02, -0.02, -0.01, 0.02, 0.03, 0.01, + 0.01, 0.06, 0.06, 0.0, -0.01, 0.02, 0.06, 0.06, 0.01, + -0.0, 0.08, 0.12, 0.08, 0.05, 0.08, 0.1, 0.08, -0.0, + 0.11, 0.09, 0.19, 0.19, 0.15, 0.19, 0.18, 0.12, 0.11 + ], + "lut_bx": + [ + -0.3, -0.28, -0.34, -0.19, -0.01, 0.13, 0.27, 0.21, 0.2, + -0.24, -0.38, -0.38, -0.24, -0.02, 0.19, 0.31, 0.34, 0.2, + -0.4, -0.47, -0.44, -0.26, -0.03, 0.21, 0.35, 0.39, 0.38, + -0.52, -0.49, -0.46, -0.27, -0.02, 0.22, 0.38, 0.46, 0.54, + -0.56, -0.51, -0.44, -0.27, -0.02, 0.23, 0.39, 0.47, 0.64, + -0.52, -0.49, -0.43, -0.27, -0.02, 0.21, 0.39, 0.45, 0.59, + -0.39, -0.41, -0.39, -0.26, -0.02, 0.2, 0.37, 0.39, 0.47, + -0.2, -0.34, -0.36, -0.23, -0.03, 0.18, 0.33, 0.28, 0.19, + -0.2, -0.21, -0.32, -0.18, -0.04, 0.14, 0.28, 0.17, 0.2 + ], + "lut_by": + [ + -0.25, -0.23, -0.31, -0.36, -0.41, -0.36, -0.32, -0.19, -0.2, + -0.09, -0.18, -0.27, -0.32, -0.35, -0.31, -0.23, -0.17, 0.01, + -0.13, -0.14, -0.19, -0.2, -0.2, -0.21, -0.17, -0.12, -0.04, + -0.1, -0.06, -0.06, -0.07, -0.05, -0.05, -0.05, -0.04, -0.07, + -0.03, 0.05, 0.06, 0.07, 0.07, 0.08, 0.06, 0.03, -0.05, + 0.03, 0.12, 0.18, 0.19, 0.19, 0.2, 0.17, 0.11, -0.03, + 0.04, 0.22, 0.3, 0.37, 0.42, 0.37, 0.3, 0.21, -0.02, + 0.05, 0.27, 0.39, 0.5, 0.57, 0.51, 0.41, 0.25, -0.09, + 0.25, 0.33, 0.52, 0.65, 0.8, 0.72, 0.56, 0.33, -0.25 + ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "power_min": 0.7, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "power_min": 0.7, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/imx708_wide_noir.json b/src/ipa/rpi/pisp/data/imx708_wide_noir.json new file mode 100644 index 00000000..75d1149b --- /dev/null +++ b/src/ipa/rpi/pisp/data/imx708_wide_noir.json @@ -0,0 +1,1148 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 41985, + "reference_gain": 1.12, + "reference_aperture": 1.0, + "reference_lux": 810, + "reference_Y": 13859 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.9 + } + }, + { + "rpi.geq": + { + "offset": 206, + "slope": 0.00324 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.65, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.717, 1.712, 1.703, 1.692, 1.674, 1.653, 1.638, 1.624, 1.613, 1.601, 1.589, 1.579, 1.575, 1.573, 1.571, 1.571, 1.571, 1.571, 1.572, 1.577, 1.583, 1.593, 1.605, 1.618, 1.636, 1.653, 1.677, 1.699, 1.715, 1.722, 1.731, 1.733, + 1.714, 1.706, 1.696, 1.678, 1.658, 1.639, 1.627, 1.614, 1.602, 1.591, 1.579, 1.572, 1.569, 1.566, 1.565, 1.564, 1.564, 1.565, 1.567, 1.571, 1.578, 1.585, 1.595, 1.607, 1.622, 1.641, 1.661, 1.685, 1.706, 1.717, 1.724, 1.732, + 1.708, 1.698, 1.688, 1.667, 1.647, 1.629, 1.619, 1.606, 1.593, 1.581, 1.572, 1.565, 1.561, 1.559, 1.559, 1.559, 1.559, 1.561, 1.562, 1.566, 1.571, 1.577, 1.587, 1.598, 1.612, 1.629, 1.649, 1.674, 1.697, 1.713, 1.721, 1.728, + 1.706, 1.695, 1.681, 1.655, 1.636, 1.622, 1.613, 1.597, 1.585, 1.572, 1.564, 1.559, 1.558, 1.556, 1.555, 1.555, 1.556, 1.556, 1.558, 1.561, 1.566, 1.571, 1.578, 1.591, 1.605, 1.619, 1.638, 1.662, 1.691, 1.708, 1.719, 1.726, + 1.706, 1.692, 1.675, 1.649, 1.629, 1.615, 1.607, 1.592, 1.575, 1.565, 1.559, 1.554, 1.552, 1.551, 1.551, 1.551, 1.551, 1.552, 1.554, 1.557, 1.561, 1.566, 1.573, 1.582, 1.596, 1.611, 1.627, 1.652, 1.681, 1.705, 1.717, 1.724, + 1.703, 1.686, 1.664, 1.639, 1.625, 1.612, 1.599, 1.585, 1.569, 1.559, 1.554, 1.549, 1.548, 1.548, 1.546, 1.546, 1.546, 1.547, 1.549, 1.553, 1.557, 1.563, 1.569, 1.576, 1.591, 1.603, 1.621, 1.644, 1.674, 1.698, 1.714, 1.724, + 1.702, 1.681, 1.659, 1.635, 1.621, 1.607, 1.594, 1.579, 1.565, 1.554, 1.549, 1.546, 1.544, 1.543, 1.543, 1.542, 1.543, 1.543, 1.544, 1.549, 1.553, 1.558, 1.564, 1.572, 1.584, 1.599, 1.614, 1.639, 1.667, 1.695, 1.712, 1.724, + 1.697, 1.678, 1.655, 1.631, 1.616, 1.602, 1.589, 1.575, 1.559, 1.551, 1.545, 1.543, 1.542, 1.542, 1.541, 1.539, 1.539, 1.539, 1.542, 1.544, 1.551, 1.555, 1.562, 1.571, 1.579, 1.594, 1.611, 1.631, 1.661, 1.691, 1.712, 1.724, + 1.695, 1.674, 1.651, 1.629, 1.615, 1.599, 1.584, 1.568, 1.554, 1.545, 1.542, 1.541, 1.539, 1.539, 1.538, 1.538, 1.538, 1.539, 1.539, 1.543, 1.548, 1.554, 1.559, 1.568, 1.576, 1.592, 1.608, 1.629, 1.655, 1.689, 1.709, 1.723, + 1.691, 1.671, 1.648, 1.627, 1.613, 1.597, 1.581, 1.564, 1.551, 1.543, 1.539, 1.538, 1.538, 1.537, 1.536, 1.535, 1.536, 1.538, 1.539, 1.542, 1.546, 1.551, 1.558, 1.564, 1.575, 1.588, 1.604, 1.627, 1.654, 1.686, 1.709, 1.724, + 1.689, 1.667, 1.643, 1.626, 1.612, 1.594, 1.579, 1.559, 1.549, 1.541, 1.536, 1.535, 1.535, 1.535, 1.534, 1.533, 1.534, 1.536, 1.538, 1.541, 1.545, 1.549, 1.555, 1.563, 1.573, 1.585, 1.602, 1.624, 1.651, 1.683, 1.709, 1.725, + 1.686, 1.665, 1.641, 1.623, 1.609, 1.594, 1.576, 1.559, 1.546, 1.538, 1.535, 1.534, 1.533, 1.532, 1.531, 1.531, 1.532, 1.534, 1.537, 1.539, 1.544, 1.549, 1.554, 1.562, 1.572, 1.585, 1.601, 1.622, 1.651, 1.682, 1.711, 1.726, + 1.686, 1.661, 1.639, 1.623, 1.609, 1.592, 1.574, 1.557, 1.545, 1.537, 1.534, 1.533, 1.532, 1.531, 1.529, 1.528, 1.529, 1.532, 1.537, 1.539, 1.542, 1.548, 1.553, 1.562, 1.571, 1.584, 1.601, 1.621, 1.649, 1.682, 1.711, 1.726, + 1.685, 1.661, 1.638, 1.624, 1.609, 1.592, 1.574, 1.557, 1.544, 1.536, 1.533, 1.532, 1.531, 1.529, 1.527, 1.522, 1.526, 1.531, 1.536, 1.539, 1.542, 1.547, 1.553, 1.562, 1.571, 1.583, 1.601, 1.621, 1.648, 1.682, 1.711, 1.726, + 1.684, 1.658, 1.638, 1.624, 1.611, 1.592, 1.573, 1.556, 1.543, 1.536, 1.532, 1.531, 1.529, 1.528, 1.522, 1.517, 1.519, 1.527, 1.535, 1.539, 1.541, 1.547, 1.553, 1.562, 1.571, 1.583, 1.601, 1.622, 1.647, 1.681, 1.711, 1.727, + 1.681, 1.658, 1.641, 1.624, 1.611, 1.593, 1.573, 1.555, 1.541, 1.535, 1.532, 1.529, 1.529, 1.527, 1.517, 1.506, 1.506, 1.522, 1.534, 1.538, 1.541, 1.546, 1.552, 1.562, 1.569, 1.583, 1.601, 1.622, 1.646, 1.679, 1.709, 1.728, + 1.679, 1.656, 1.639, 1.624, 1.611, 1.595, 1.575, 1.556, 1.541, 1.534, 1.531, 1.529, 1.529, 1.527, 1.517, 1.507, 1.507, 1.522, 1.533, 1.538, 1.539, 1.546, 1.552, 1.561, 1.569, 1.584, 1.601, 1.622, 1.647, 1.681, 1.709, 1.726, + 1.678, 1.656, 1.638, 1.625, 1.612, 1.597, 1.577, 1.557, 1.542, 1.534, 1.529, 1.529, 1.528, 1.527, 1.522, 1.516, 1.519, 1.525, 1.533, 1.537, 1.539, 1.545, 1.552, 1.561, 1.571, 1.584, 1.601, 1.623, 1.649, 1.681, 1.709, 1.726, + 1.679, 1.654, 1.639, 1.626, 1.613, 1.598, 1.578, 1.558, 1.543, 1.534, 1.529, 1.529, 1.529, 1.528, 1.527, 1.522, 1.525, 1.528, 1.533, 1.536, 1.539, 1.546, 1.553, 1.561, 1.571, 1.586, 1.602, 1.623, 1.651, 1.683, 1.712, 1.726, + 1.677, 1.655, 1.641, 1.628, 1.615, 1.599, 1.581, 1.562, 1.545, 1.535, 1.531, 1.529, 1.529, 1.528, 1.527, 1.527, 1.528, 1.531, 1.533, 1.536, 1.539, 1.545, 1.552, 1.561, 1.572, 1.588, 1.607, 1.626, 1.654, 1.686, 1.716, 1.729, + 1.676, 1.655, 1.642, 1.629, 1.617, 1.602, 1.586, 1.564, 1.546, 1.536, 1.531, 1.529, 1.529, 1.529, 1.529, 1.529, 1.529, 1.532, 1.534, 1.536, 1.539, 1.547, 1.553, 1.563, 1.576, 1.591, 1.609, 1.627, 1.655, 1.688, 1.716, 1.729, + 1.676, 1.658, 1.641, 1.631, 1.617, 1.605, 1.588, 1.569, 1.553, 1.539, 1.532, 1.531, 1.529, 1.529, 1.529, 1.529, 1.531, 1.532, 1.534, 1.537, 1.541, 1.547, 1.553, 1.564, 1.578, 1.594, 1.613, 1.632, 1.659, 1.691, 1.717, 1.728, + 1.676, 1.658, 1.642, 1.631, 1.619, 1.608, 1.592, 1.575, 1.556, 1.542, 1.533, 1.531, 1.529, 1.529, 1.529, 1.531, 1.531, 1.532, 1.534, 1.537, 1.542, 1.548, 1.556, 1.567, 1.582, 1.598, 1.616, 1.638, 1.661, 1.693, 1.717, 1.729, + 1.678, 1.661, 1.644, 1.632, 1.621, 1.611, 1.596, 1.579, 1.561, 1.546, 1.536, 1.532, 1.531, 1.531, 1.531, 1.531, 1.532, 1.533, 1.535, 1.538, 1.544, 1.549, 1.559, 1.569, 1.587, 1.604, 1.618, 1.639, 1.669, 1.697, 1.718, 1.731, + 1.679, 1.662, 1.648, 1.635, 1.625, 1.615, 1.602, 1.586, 1.569, 1.552, 1.541, 1.535, 1.532, 1.532, 1.531, 1.532, 1.533, 1.534, 1.537, 1.541, 1.546, 1.552, 1.562, 1.576, 1.592, 1.608, 1.622, 1.647, 1.673, 1.703, 1.721, 1.734, + 1.684, 1.664, 1.649, 1.637, 1.627, 1.618, 1.606, 1.593, 1.576, 1.561, 1.547, 1.539, 1.535, 1.533, 1.533, 1.533, 1.534, 1.536, 1.539, 1.543, 1.549, 1.555, 1.568, 1.583, 1.596, 1.612, 1.629, 1.651, 1.681, 1.706, 1.723, 1.734, + 1.689, 1.669, 1.649, 1.639, 1.629, 1.621, 1.609, 1.597, 1.585, 1.567, 1.554, 1.546, 1.539, 1.536, 1.535, 1.535, 1.537, 1.538, 1.542, 1.546, 1.553, 1.562, 1.572, 1.589, 1.603, 1.619, 1.635, 1.658, 1.686, 1.708, 1.726, 1.736, + 1.692, 1.673, 1.655, 1.644, 1.634, 1.624, 1.614, 1.604, 1.592, 1.577, 1.566, 1.554, 1.546, 1.542, 1.538, 1.538, 1.539, 1.542, 1.546, 1.552, 1.559, 1.568, 1.581, 1.596, 1.609, 1.625, 1.642, 1.664, 1.693, 1.714, 1.727, 1.736, + 1.695, 1.679, 1.662, 1.647, 1.638, 1.631, 1.623, 1.612, 1.601, 1.589, 1.577, 1.565, 1.555, 1.549, 1.546, 1.545, 1.546, 1.548, 1.552, 1.559, 1.568, 1.579, 1.593, 1.604, 1.618, 1.632, 1.648, 1.676, 1.701, 1.718, 1.728, 1.739, + 1.699, 1.684, 1.667, 1.654, 1.644, 1.635, 1.629, 1.621, 1.609, 1.599, 1.589, 1.578, 1.568, 1.559, 1.556, 1.554, 1.554, 1.557, 1.563, 1.569, 1.578, 1.589, 1.599, 1.612, 1.625, 1.641, 1.661, 1.685, 1.707, 1.722, 1.734, 1.742, + 1.703, 1.691, 1.672, 1.658, 1.648, 1.639, 1.634, 1.628, 1.618, 1.606, 1.598, 1.589, 1.579, 1.573, 1.568, 1.567, 1.567, 1.568, 1.571, 1.578, 1.587, 1.597, 1.607, 1.618, 1.632, 1.651, 1.672, 1.694, 1.715, 1.728, 1.737, 1.742, + 1.707, 1.691, 1.676, 1.662, 1.651, 1.643, 1.638, 1.631, 1.622, 1.614, 1.604, 1.596, 1.589, 1.579, 1.575, 1.573, 1.573, 1.574, 1.578, 1.586, 1.589, 1.598, 1.609, 1.625, 1.638, 1.657, 1.679, 1.701, 1.719, 1.728, 1.738, 1.742 + ] + }, + { + "ct": 5000, + "table": + [ + 2.939, 2.935, 2.916, 2.895, 2.856, 2.825, 2.797, 2.777, 2.761, 2.741, 2.726, 2.709, 2.707, 2.704, 2.702, 2.702, 2.703, 2.706, 2.708, 2.709, 2.719, 2.735, 2.753, 2.776, 2.801, 2.832, 2.874, 2.915, 2.939, 2.943, 2.953, 2.961, + 2.936, 2.923, 2.901, 2.863, 2.829, 2.801, 2.781, 2.763, 2.743, 2.732, 2.712, 2.701, 2.696, 2.692, 2.691, 2.691, 2.693, 2.694, 2.696, 2.701, 2.709, 2.725, 2.741, 2.758, 2.779, 2.811, 2.838, 2.879, 2.919, 2.939, 2.948, 2.959, + 2.929, 2.909, 2.887, 2.847, 2.808, 2.783, 2.765, 2.748, 2.732, 2.713, 2.699, 2.691, 2.687, 2.686, 2.685, 2.685, 2.687, 2.689, 2.691, 2.694, 2.701, 2.709, 2.725, 2.745, 2.763, 2.786, 2.818, 2.863, 2.907, 2.933, 2.941, 2.955, + 2.929, 2.903, 2.875, 2.825, 2.791, 2.769, 2.755, 2.737, 2.718, 2.701, 2.688, 2.683, 2.681, 2.679, 2.681, 2.679, 2.681, 2.682, 2.685, 2.689, 2.694, 2.701, 2.711, 2.737, 2.754, 2.772, 2.803, 2.844, 2.894, 2.931, 2.939, 2.953, + 2.926, 2.895, 2.862, 2.816, 2.782, 2.759, 2.744, 2.727, 2.709, 2.691, 2.679, 2.673, 2.671, 2.669, 2.669, 2.669, 2.671, 2.674, 2.678, 2.681, 2.685, 2.694, 2.707, 2.725, 2.739, 2.762, 2.786, 2.829, 2.879, 2.919, 2.942, 2.952, + 2.919, 2.886, 2.846, 2.797, 2.772, 2.751, 2.737, 2.719, 2.694, 2.679, 2.672, 2.666, 2.664, 2.661, 2.659, 2.658, 2.661, 2.664, 2.669, 2.673, 2.678, 2.685, 2.696, 2.715, 2.728, 2.749, 2.774, 2.808, 2.866, 2.909, 2.936, 2.951, + 2.904, 2.877, 2.835, 2.789, 2.763, 2.744, 2.728, 2.712, 2.686, 2.672, 2.664, 2.657, 2.654, 2.654, 2.652, 2.653, 2.654, 2.657, 2.661, 2.666, 2.672, 2.678, 2.688, 2.703, 2.721, 2.742, 2.762, 2.797, 2.851, 2.902, 2.928, 2.949, + 2.901, 2.869, 2.825, 2.781, 2.756, 2.738, 2.721, 2.698, 2.679, 2.665, 2.656, 2.652, 2.649, 2.648, 2.648, 2.648, 2.649, 2.651, 2.654, 2.659, 2.667, 2.675, 2.683, 2.699, 2.711, 2.736, 2.754, 2.789, 2.838, 2.896, 2.926, 2.948, + 2.899, 2.862, 2.815, 2.774, 2.752, 2.734, 2.717, 2.689, 2.669, 2.658, 2.651, 2.646, 2.645, 2.643, 2.643, 2.644, 2.645, 2.646, 2.649, 2.654, 2.661, 2.669, 2.681, 2.693, 2.707, 2.729, 2.751, 2.782, 2.834, 2.887, 2.924, 2.947, + 2.898, 2.853, 2.812, 2.771, 2.751, 2.731, 2.711, 2.686, 2.663, 2.653, 2.646, 2.642, 2.641, 2.642, 2.642, 2.641, 2.641, 2.641, 2.646, 2.651, 2.657, 2.667, 2.678, 2.693, 2.705, 2.728, 2.746, 2.781, 2.829, 2.885, 2.924, 2.951, + 2.896, 2.851, 2.807, 2.771, 2.752, 2.729, 2.709, 2.681, 2.661, 2.649, 2.643, 2.641, 2.639, 2.639, 2.638, 2.636, 2.637, 2.638, 2.644, 2.649, 2.657, 2.666, 2.676, 2.688, 2.705, 2.725, 2.745, 2.777, 2.827, 2.884, 2.927, 2.951, + 2.891, 2.846, 2.803, 2.771, 2.749, 2.728, 2.706, 2.677, 2.658, 2.647, 2.641, 2.637, 2.637, 2.636, 2.636, 2.633, 2.632, 2.635, 2.643, 2.649, 2.656, 2.665, 2.675, 2.688, 2.704, 2.719, 2.744, 2.776, 2.822, 2.881, 2.927, 2.958, + 2.887, 2.841, 2.797, 2.769, 2.749, 2.729, 2.704, 2.674, 2.655, 2.645, 2.638, 2.635, 2.633, 2.632, 2.631, 2.625, 2.627, 2.631, 2.639, 2.649, 2.654, 2.662, 2.673, 2.686, 2.701, 2.718, 2.742, 2.773, 2.822, 2.881, 2.926, 2.958, + 2.883, 2.837, 2.796, 2.769, 2.749, 2.729, 2.701, 2.673, 2.653, 2.641, 2.636, 2.632, 2.631, 2.629, 2.623, 2.612, 2.619, 2.627, 2.637, 2.648, 2.652, 2.659, 2.671, 2.688, 2.699, 2.719, 2.742, 2.774, 2.821, 2.882, 2.927, 2.961, + 2.881, 2.832, 2.795, 2.769, 2.751, 2.729, 2.701, 2.672, 2.652, 2.639, 2.633, 2.631, 2.628, 2.625, 2.611, 2.599, 2.607, 2.619, 2.635, 2.644, 2.652, 2.659, 2.669, 2.686, 2.698, 2.719, 2.743, 2.775, 2.822, 2.881, 2.926, 2.961, + 2.879, 2.829, 2.793, 2.771, 2.751, 2.731, 2.701, 2.672, 2.651, 2.639, 2.632, 2.628, 2.626, 2.621, 2.601, 2.581, 2.581, 2.611, 2.631, 2.642, 2.648, 2.657, 2.669, 2.685, 2.699, 2.721, 2.743, 2.776, 2.819, 2.879, 2.927, 2.961, + 2.876, 2.829, 2.796, 2.773, 2.752, 2.731, 2.705, 2.672, 2.651, 2.637, 2.631, 2.627, 2.625, 2.619, 2.601, 2.581, 2.581, 2.611, 2.629, 2.641, 2.647, 2.658, 2.669, 2.685, 2.697, 2.721, 2.746, 2.777, 2.822, 2.881, 2.929, 2.964, + 2.874, 2.827, 2.796, 2.775, 2.755, 2.733, 2.708, 2.674, 2.649, 2.635, 2.629, 2.626, 2.624, 2.621, 2.609, 2.601, 2.606, 2.615, 2.629, 2.638, 2.645, 2.657, 2.669, 2.682, 2.699, 2.722, 2.747, 2.778, 2.822, 2.881, 2.931, 2.964, + 2.871, 2.827, 2.797, 2.776, 2.761, 2.734, 2.711, 2.679, 2.651, 2.636, 2.628, 2.626, 2.624, 2.621, 2.618, 2.611, 2.614, 2.619, 2.628, 2.639, 2.644, 2.657, 2.668, 2.683, 2.698, 2.723, 2.749, 2.782, 2.824, 2.882, 2.933, 2.965, + 2.869, 2.825, 2.797, 2.777, 2.765, 2.741, 2.718, 2.683, 2.655, 2.638, 2.627, 2.625, 2.624, 2.623, 2.621, 2.618, 2.618, 2.624, 2.629, 2.639, 2.644, 2.657, 2.669, 2.684, 2.701, 2.725, 2.755, 2.782, 2.829, 2.887, 2.937, 2.965, + 2.871, 2.826, 2.799, 2.776, 2.765, 2.744, 2.723, 2.689, 2.659, 2.639, 2.629, 2.626, 2.626, 2.624, 2.624, 2.622, 2.624, 2.627, 2.632, 2.639, 2.646, 2.657, 2.671, 2.687, 2.706, 2.732, 2.757, 2.789, 2.836, 2.893, 2.941, 2.965, + 2.869, 2.831, 2.803, 2.778, 2.766, 2.748, 2.729, 2.697, 2.667, 2.645, 2.632, 2.628, 2.625, 2.625, 2.625, 2.625, 2.627, 2.629, 2.634, 2.638, 2.648, 2.661, 2.673, 2.688, 2.711, 2.741, 2.762, 2.797, 2.843, 2.901, 2.943, 2.964, + 2.872, 2.837, 2.802, 2.781, 2.768, 2.753, 2.734, 2.702, 2.674, 2.647, 2.634, 2.629, 2.626, 2.625, 2.625, 2.627, 2.629, 2.632, 2.635, 2.639, 2.649, 2.663, 2.676, 2.694, 2.719, 2.746, 2.771, 2.799, 2.851, 2.905, 2.947, 2.969, + 2.871, 2.837, 2.805, 2.786, 2.771, 2.755, 2.739, 2.714, 2.685, 2.655, 2.639, 2.631, 2.626, 2.625, 2.626, 2.628, 2.629, 2.632, 2.634, 2.642, 2.651, 2.663, 2.679, 2.701, 2.726, 2.756, 2.773, 2.809, 2.861, 2.913, 2.949, 2.968, + 2.876, 2.841, 2.808, 2.789, 2.775, 2.759, 2.744, 2.719, 2.693, 2.664, 2.648, 2.636, 2.629, 2.627, 2.627, 2.629, 2.631, 2.633, 2.637, 2.645, 2.653, 2.666, 2.682, 2.708, 2.734, 2.759, 2.779, 2.815, 2.868, 2.918, 2.951, 2.971, + 2.882, 2.845, 2.816, 2.791, 2.778, 2.766, 2.748, 2.733, 2.707, 2.681, 2.656, 2.643, 2.636, 2.632, 2.631, 2.632, 2.633, 2.637, 2.643, 2.648, 2.659, 2.672, 2.691, 2.719, 2.747, 2.765, 2.791, 2.829, 2.881, 2.931, 2.952, 2.969, + 2.889, 2.855, 2.819, 2.799, 2.782, 2.769, 2.755, 2.741, 2.717, 2.691, 2.672, 2.652, 2.643, 2.639, 2.636, 2.636, 2.638, 2.642, 2.646, 2.655, 2.665, 2.682, 2.703, 2.729, 2.752, 2.774, 2.798, 2.839, 2.891, 2.933, 2.959, 2.975, + 2.897, 2.862, 2.829, 2.804, 2.789, 2.776, 2.764, 2.749, 2.734, 2.709, 2.689, 2.669, 2.652, 2.644, 2.642, 2.642, 2.644, 2.647, 2.654, 2.664, 2.677, 2.694, 2.714, 2.742, 2.764, 2.782, 2.809, 2.852, 2.899, 2.936, 2.961, 2.976, + 2.902, 2.869, 2.841, 2.811, 2.797, 2.785, 2.776, 2.761, 2.748, 2.727, 2.708, 2.689, 2.671, 2.659, 2.655, 2.654, 2.653, 2.656, 2.666, 2.678, 2.693, 2.713, 2.737, 2.756, 2.775, 2.798, 2.825, 2.871, 2.913, 2.944, 2.966, 2.979, + 2.911, 2.885, 2.848, 2.821, 2.804, 2.793, 2.784, 2.774, 2.759, 2.747, 2.726, 2.709, 2.692, 2.679, 2.673, 2.672, 2.671, 2.672, 2.681, 2.694, 2.712, 2.729, 2.749, 2.768, 2.789, 2.811, 2.844, 2.886, 2.928, 2.956, 2.971, 2.984, + 2.925, 2.893, 2.861, 2.831, 2.813, 2.802, 2.795, 2.783, 2.773, 2.759, 2.744, 2.729, 2.715, 2.701, 2.698, 2.694, 2.693, 2.694, 2.702, 2.714, 2.729, 2.747, 2.761, 2.781, 2.802, 2.828, 2.864, 2.907, 2.942, 2.967, 2.978, 2.989, + 2.932, 2.898, 2.871, 2.843, 2.823, 2.811, 2.802, 2.794, 2.779, 2.772, 2.757, 2.742, 2.729, 2.716, 2.705, 2.704, 2.704, 2.707, 2.715, 2.727, 2.737, 2.754, 2.769, 2.788, 2.812, 2.845, 2.878, 2.923, 2.962, 2.973, 2.979, 2.994 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 3.018, 3.021, 3.026, 3.052, 3.092, 3.143, 3.181, 3.202, 3.209, 3.212, 3.211, 3.209, 3.197, 3.193, 3.185, 3.184, 3.185, 3.187, 3.191, 3.202, 3.211, 3.213, 3.212, 3.203, 3.189, 3.147, 3.099, 3.051, 3.032, 3.031, 3.048, 3.054, + 3.019, 3.023, 3.033, 3.066, 3.123, 3.163, 3.196, 3.206, 3.212, 3.212, 3.211, 3.203, 3.193, 3.179, 3.168, 3.159, 3.159, 3.163, 3.174, 3.188, 3.203, 3.208, 3.211, 3.209, 3.195, 3.168, 3.114, 3.064, 3.035, 3.033, 3.044, 3.051, + 3.021, 3.028, 3.046, 3.099, 3.156, 3.192, 3.209, 3.215, 3.216, 3.213, 3.203, 3.193, 3.176, 3.159, 3.153, 3.151, 3.149, 3.152, 3.159, 3.171, 3.188, 3.201, 3.209, 3.211, 3.207, 3.189, 3.142, 3.083, 3.042, 3.038, 3.043, 3.046, + 3.022, 3.037, 3.065, 3.124, 3.178, 3.206, 3.215, 3.221, 3.218, 3.217, 3.198, 3.179, 3.162, 3.149, 3.138, 3.133, 3.133, 3.136, 3.145, 3.156, 3.174, 3.192, 3.206, 3.215, 3.214, 3.202, 3.159, 3.105, 3.058, 3.042, 3.043, 3.049, + 3.024, 3.047, 3.084, 3.151, 3.195, 3.211, 3.219, 3.223, 3.218, 3.208, 3.182, 3.164, 3.149, 3.137, 3.127, 3.119, 3.119, 3.124, 3.134, 3.144, 3.157, 3.178, 3.194, 3.213, 3.215, 3.208, 3.166, 3.124, 3.074, 3.044, 3.044, 3.049, + 3.023, 3.058, 3.102, 3.161, 3.201, 3.217, 3.224, 3.223, 3.217, 3.195, 3.174, 3.156, 3.137, 3.125, 3.115, 3.109, 3.109, 3.115, 3.121, 3.131, 3.146, 3.159, 3.186, 3.208, 3.213, 3.211, 3.181, 3.138, 3.084, 3.047, 3.047, 3.049, + 3.031, 3.063, 3.126, 3.183, 3.212, 3.224, 3.225, 3.224, 3.216, 3.191, 3.167, 3.143, 3.129, 3.115, 3.105, 3.103, 3.103, 3.107, 3.114, 3.121, 3.131, 3.148, 3.169, 3.199, 3.211, 3.209, 3.186, 3.151, 3.089, 3.051, 3.049, 3.052, + 3.033, 3.083, 3.141, 3.201, 3.221, 3.226, 3.226, 3.224, 3.212, 3.187, 3.159, 3.138, 3.119, 3.107, 3.101, 3.098, 3.098, 3.102, 3.107, 3.115, 3.124, 3.138, 3.161, 3.185, 3.207, 3.209, 3.197, 3.162, 3.112, 3.059, 3.056, 3.057, + 3.038, 3.092, 3.159, 3.212, 3.225, 3.231, 3.228, 3.224, 3.209, 3.181, 3.152, 3.129, 3.112, 3.103, 3.095, 3.092, 3.093, 3.095, 3.101, 3.108, 3.118, 3.133, 3.152, 3.179, 3.203, 3.209, 3.205, 3.174, 3.124, 3.069, 3.059, 3.058, + 3.049, 3.105, 3.176, 3.223, 3.229, 3.231, 3.229, 3.223, 3.206, 3.171, 3.147, 3.125, 3.109, 3.097, 3.091, 3.089, 3.088, 3.091, 3.094, 3.102, 3.111, 3.124, 3.143, 3.169, 3.196, 3.208, 3.207, 3.181, 3.132, 3.079, 3.064, 3.063, + 3.055, 3.123, 3.189, 3.226, 3.232, 3.232, 3.229, 3.225, 3.204, 3.169, 3.143, 3.122, 3.108, 3.095, 3.092, 3.089, 3.088, 3.088, 3.092, 3.095, 3.105, 3.117, 3.135, 3.159, 3.191, 3.208, 3.208, 3.189, 3.141, 3.084, 3.064, 3.062, + 3.057, 3.127, 3.198, 3.228, 3.233, 3.233, 3.229, 3.225, 3.201, 3.166, 3.139, 3.119, 3.106, 3.096, 3.093, 3.092, 3.088, 3.088, 3.089, 3.093, 3.099, 3.114, 3.129, 3.156, 3.186, 3.208, 3.208, 3.195, 3.143, 3.089, 3.065, 3.064, + 3.066, 3.142, 3.209, 3.232, 3.234, 3.233, 3.231, 3.226, 3.198, 3.166, 3.138, 3.117, 3.103, 3.097, 3.095, 3.095, 3.094, 3.089, 3.089, 3.092, 3.097, 3.109, 3.126, 3.155, 3.183, 3.207, 3.207, 3.198, 3.147, 3.091, 3.069, 3.065, + 3.072, 3.153, 3.216, 3.231, 3.234, 3.234, 3.229, 3.226, 3.194, 3.165, 3.136, 3.114, 3.101, 3.098, 3.098, 3.104, 3.098, 3.091, 3.088, 3.089, 3.093, 3.103, 3.123, 3.151, 3.181, 3.204, 3.204, 3.197, 3.156, 3.095, 3.069, 3.068, + 3.079, 3.159, 3.222, 3.233, 3.236, 3.235, 3.231, 3.226, 3.194, 3.165, 3.133, 3.112, 3.102, 3.099, 3.107, 3.114, 3.111, 3.097, 3.089, 3.089, 3.091, 3.099, 3.121, 3.149, 3.182, 3.202, 3.202, 3.195, 3.156, 3.096, 3.069, 3.068, + 3.081, 3.164, 3.226, 3.233, 3.236, 3.235, 3.233, 3.229, 3.199, 3.165, 3.137, 3.113, 3.102, 3.102, 3.111, 3.134, 3.134, 3.103, 3.091, 3.089, 3.092, 3.101, 3.119, 3.147, 3.182, 3.202, 3.202, 3.194, 3.155, 3.095, 3.069, 3.067, + 3.085, 3.163, 3.225, 3.236, 3.239, 3.235, 3.234, 3.231, 3.203, 3.169, 3.141, 3.115, 3.103, 3.103, 3.111, 3.134, 3.134, 3.106, 3.092, 3.091, 3.093, 3.103, 3.119, 3.149, 3.185, 3.203, 3.203, 3.193, 3.152, 3.095, 3.068, 3.066, + 3.083, 3.168, 3.226, 3.236, 3.241, 3.235, 3.235, 3.231, 3.205, 3.174, 3.144, 3.117, 3.107, 3.103, 3.107, 3.116, 3.109, 3.103, 3.091, 3.091, 3.095, 3.107, 3.123, 3.152, 3.188, 3.204, 3.204, 3.193, 3.151, 3.095, 3.069, 3.066, + 3.082, 3.171, 3.228, 3.237, 3.239, 3.235, 3.234, 3.233, 3.217, 3.184, 3.147, 3.119, 3.108, 3.104, 3.103, 3.105, 3.102, 3.095, 3.091, 3.091, 3.097, 3.111, 3.128, 3.157, 3.191, 3.204, 3.204, 3.185, 3.149, 3.094, 3.069, 3.065, + 3.086, 3.173, 3.226, 3.237, 3.239, 3.235, 3.234, 3.232, 3.221, 3.185, 3.155, 3.124, 3.112, 3.105, 3.102, 3.099, 3.096, 3.094, 3.092, 3.094, 3.102, 3.114, 3.133, 3.163, 3.197, 3.205, 3.204, 3.183, 3.144, 3.089, 3.068, 3.065, + 3.086, 3.166, 3.225, 3.239, 3.239, 3.237, 3.233, 3.231, 3.223, 3.193, 3.165, 3.135, 3.118, 3.108, 3.101, 3.098, 3.095, 3.093, 3.093, 3.099, 3.109, 3.124, 3.145, 3.174, 3.199, 3.204, 3.203, 3.181, 3.132, 3.085, 3.067, 3.062, + 3.086, 3.162, 3.224, 3.239, 3.241, 3.236, 3.232, 3.229, 3.224, 3.201, 3.174, 3.147, 3.128, 3.114, 3.103, 3.099, 3.096, 3.095, 3.097, 3.106, 3.116, 3.134, 3.151, 3.182, 3.201, 3.203, 3.201, 3.176, 3.125, 3.078, 3.065, 3.061, + 3.077, 3.162, 3.221, 3.239, 3.241, 3.234, 3.229, 3.227, 3.225, 3.207, 3.186, 3.161, 3.137, 3.122, 3.112, 3.102, 3.099, 3.098, 3.106, 3.113, 3.127, 3.139, 3.159, 3.192, 3.204, 3.205, 3.198, 3.167, 3.119, 3.073, 3.062, 3.061, + 3.077, 3.161, 3.216, 3.234, 3.236, 3.232, 3.225, 3.225, 3.222, 3.209, 3.194, 3.172, 3.148, 3.132, 3.121, 3.113, 3.107, 3.107, 3.112, 3.124, 3.135, 3.151, 3.175, 3.196, 3.201, 3.201, 3.191, 3.161, 3.114, 3.062, 3.058, 3.057, + 3.073, 3.139, 3.201, 3.227, 3.232, 3.227, 3.223, 3.219, 3.216, 3.212, 3.203, 3.181, 3.161, 3.142, 3.129, 3.121, 3.114, 3.114, 3.124, 3.134, 3.145, 3.161, 3.179, 3.196, 3.199, 3.195, 3.182, 3.145, 3.093, 3.052, 3.051, 3.052, + 3.066, 3.126, 3.192, 3.218, 3.224, 3.221, 3.218, 3.214, 3.214, 3.209, 3.204, 3.191, 3.174, 3.155, 3.142, 3.129, 3.127, 3.127, 3.136, 3.145, 3.157, 3.175, 3.187, 3.194, 3.196, 3.192, 3.171, 3.134, 3.082, 3.043, 3.042, 3.044, + 3.056, 3.114, 3.176, 3.212, 3.219, 3.219, 3.214, 3.209, 3.208, 3.206, 3.203, 3.198, 3.182, 3.171, 3.155, 3.146, 3.144, 3.144, 3.148, 3.156, 3.171, 3.181, 3.188, 3.194, 3.194, 3.187, 3.161, 3.117, 3.066, 3.037, 3.037, 3.044, + 3.054, 3.101, 3.162, 3.203, 3.216, 3.215, 3.211, 3.206, 3.203, 3.201, 3.199, 3.197, 3.191, 3.179, 3.171, 3.161, 3.156, 3.156, 3.161, 3.171, 3.179, 3.184, 3.189, 3.192, 3.191, 3.181, 3.142, 3.097, 3.045, 3.032, 3.033, 3.039, + 3.041, 3.093, 3.149, 3.194, 3.208, 3.211, 3.208, 3.202, 3.197, 3.197, 3.197, 3.195, 3.191, 3.189, 3.181, 3.176, 3.172, 3.173, 3.178, 3.181, 3.185, 3.187, 3.189, 3.191, 3.189, 3.173, 3.133, 3.085, 3.034, 3.029, 3.031, 3.038, + 3.032, 3.079, 3.133, 3.181, 3.197, 3.207, 3.204, 3.198, 3.193, 3.192, 3.189, 3.191, 3.189, 3.187, 3.185, 3.183, 3.183, 3.183, 3.185, 3.188, 3.187, 3.188, 3.189, 3.188, 3.184, 3.164, 3.118, 3.075, 3.031, 3.026, 3.028, 3.039, + 3.025, 3.051, 3.099, 3.149, 3.182, 3.193, 3.193, 3.187, 3.181, 3.178, 3.177, 3.177, 3.182, 3.183, 3.183, 3.183, 3.183, 3.184, 3.187, 3.188, 3.186, 3.184, 3.184, 3.181, 3.167, 3.139, 3.098, 3.053, 3.026, 3.024, 3.029, 3.043, + 3.016, 3.025, 3.081, 3.122, 3.167, 3.182, 3.185, 3.181, 3.176, 3.171, 3.169, 3.171, 3.174, 3.175, 3.178, 3.178, 3.179, 3.181, 3.185, 3.185, 3.181, 3.179, 3.177, 3.173, 3.151, 3.119, 3.076, 3.031, 3.021, 3.018, 3.024, 3.046 + ] + }, + { + "ct": 5000, + "table": + [ + 1.503, 1.503, 1.504, 1.515, 1.541, 1.566, 1.587, 1.599, 1.602, 1.603, 1.602, 1.599, 1.595, 1.589, 1.587, 1.586, 1.586, 1.587, 1.589, 1.594, 1.601, 1.604, 1.604, 1.601, 1.589, 1.571, 1.541, 1.517, 1.512, 1.512, 1.522, 1.526, + 1.501, 1.502, 1.506, 1.523, 1.557, 1.579, 1.596, 1.603, 1.603, 1.603, 1.601, 1.597, 1.591, 1.582, 1.576, 1.575, 1.574, 1.577, 1.581, 1.588, 1.595, 1.601, 1.603, 1.602, 1.597, 1.578, 1.553, 1.526, 1.512, 1.512, 1.519, 1.526, + 1.499, 1.503, 1.512, 1.539, 1.571, 1.593, 1.603, 1.604, 1.604, 1.602, 1.597, 1.591, 1.581, 1.573, 1.568, 1.566, 1.566, 1.568, 1.572, 1.579, 1.587, 1.594, 1.602, 1.603, 1.601, 1.589, 1.566, 1.536, 1.517, 1.516, 1.519, 1.525, + 1.499, 1.505, 1.521, 1.553, 1.582, 1.597, 1.604, 1.604, 1.604, 1.601, 1.592, 1.582, 1.573, 1.564, 1.561, 1.558, 1.557, 1.559, 1.564, 1.571, 1.579, 1.588, 1.597, 1.603, 1.603, 1.596, 1.576, 1.545, 1.519, 1.517, 1.518, 1.526, + 1.499, 1.509, 1.529, 1.565, 1.591, 1.601, 1.605, 1.604, 1.602, 1.597, 1.586, 1.573, 1.565, 1.558, 1.553, 1.551, 1.551, 1.552, 1.555, 1.563, 1.571, 1.581, 1.592, 1.601, 1.602, 1.599, 1.582, 1.556, 1.528, 1.517, 1.517, 1.526, + 1.501, 1.512, 1.539, 1.576, 1.595, 1.603, 1.605, 1.604, 1.601, 1.591, 1.579, 1.567, 1.559, 1.552, 1.548, 1.545, 1.546, 1.548, 1.551, 1.555, 1.563, 1.574, 1.585, 1.598, 1.602, 1.601, 1.589, 1.562, 1.535, 1.519, 1.519, 1.528, + 1.501, 1.517, 1.552, 1.587, 1.601, 1.605, 1.605, 1.605, 1.599, 1.588, 1.574, 1.562, 1.553, 1.548, 1.544, 1.543, 1.543, 1.545, 1.547, 1.551, 1.557, 1.567, 1.578, 1.593, 1.601, 1.601, 1.592, 1.571, 1.539, 1.521, 1.521, 1.529, + 1.503, 1.524, 1.561, 1.593, 1.605, 1.606, 1.605, 1.603, 1.598, 1.585, 1.569, 1.558, 1.551, 1.545, 1.542, 1.541, 1.541, 1.542, 1.545, 1.547, 1.555, 1.561, 1.573, 1.587, 1.598, 1.601, 1.596, 1.577, 1.546, 1.523, 1.523, 1.529, + 1.503, 1.532, 1.568, 1.597, 1.605, 1.606, 1.605, 1.603, 1.596, 1.581, 1.565, 1.555, 1.548, 1.544, 1.541, 1.539, 1.541, 1.541, 1.543, 1.546, 1.549, 1.558, 1.568, 1.583, 1.595, 1.601, 1.599, 1.582, 1.555, 1.525, 1.525, 1.531, + 1.508, 1.539, 1.575, 1.601, 1.605, 1.606, 1.605, 1.602, 1.593, 1.577, 1.563, 1.552, 1.546, 1.543, 1.541, 1.539, 1.539, 1.541, 1.542, 1.544, 1.548, 1.553, 1.564, 1.579, 1.592, 1.599, 1.599, 1.585, 1.559, 1.532, 1.531, 1.531, + 1.511, 1.544, 1.581, 1.603, 1.606, 1.606, 1.604, 1.603, 1.591, 1.574, 1.561, 1.549, 1.545, 1.542, 1.541, 1.541, 1.541, 1.541, 1.542, 1.543, 1.545, 1.551, 1.561, 1.573, 1.591, 1.599, 1.599, 1.588, 1.563, 1.535, 1.531, 1.531, + 1.515, 1.548, 1.589, 1.605, 1.607, 1.607, 1.604, 1.602, 1.591, 1.573, 1.559, 1.549, 1.543, 1.542, 1.541, 1.542, 1.542, 1.542, 1.541, 1.542, 1.543, 1.549, 1.558, 1.571, 1.588, 1.599, 1.599, 1.591, 1.566, 1.537, 1.532, 1.531, + 1.517, 1.558, 1.593, 1.606, 1.607, 1.607, 1.605, 1.602, 1.589, 1.572, 1.557, 1.548, 1.543, 1.543, 1.542, 1.544, 1.543, 1.543, 1.541, 1.541, 1.542, 1.546, 1.554, 1.569, 1.585, 1.599, 1.599, 1.593, 1.568, 1.538, 1.533, 1.531, + 1.521, 1.563, 1.596, 1.607, 1.608, 1.607, 1.606, 1.603, 1.589, 1.572, 1.557, 1.548, 1.543, 1.543, 1.544, 1.549, 1.546, 1.544, 1.541, 1.541, 1.542, 1.545, 1.553, 1.568, 1.585, 1.598, 1.598, 1.594, 1.571, 1.541, 1.534, 1.531, + 1.521, 1.566, 1.599, 1.607, 1.608, 1.607, 1.605, 1.603, 1.591, 1.571, 1.556, 1.547, 1.544, 1.544, 1.551, 1.554, 1.552, 1.546, 1.541, 1.541, 1.541, 1.544, 1.553, 1.567, 1.585, 1.597, 1.598, 1.595, 1.571, 1.541, 1.534, 1.531, + 1.523, 1.568, 1.601, 1.607, 1.608, 1.607, 1.606, 1.604, 1.591, 1.572, 1.557, 1.547, 1.545, 1.545, 1.552, 1.566, 1.566, 1.551, 1.542, 1.541, 1.541, 1.544, 1.553, 1.567, 1.586, 1.596, 1.596, 1.593, 1.571, 1.541, 1.533, 1.531, + 1.524, 1.569, 1.602, 1.607, 1.608, 1.607, 1.606, 1.604, 1.591, 1.573, 1.559, 1.548, 1.545, 1.546, 1.552, 1.565, 1.565, 1.551, 1.542, 1.541, 1.541, 1.545, 1.553, 1.568, 1.586, 1.597, 1.597, 1.593, 1.571, 1.541, 1.532, 1.532, + 1.526, 1.571, 1.602, 1.607, 1.608, 1.606, 1.605, 1.604, 1.593, 1.575, 1.559, 1.549, 1.546, 1.546, 1.549, 1.552, 1.552, 1.546, 1.542, 1.541, 1.542, 1.546, 1.555, 1.569, 1.587, 1.597, 1.597, 1.591, 1.569, 1.539, 1.532, 1.531, + 1.526, 1.571, 1.601, 1.608, 1.609, 1.605, 1.605, 1.603, 1.597, 1.579, 1.562, 1.551, 1.546, 1.545, 1.545, 1.549, 1.546, 1.543, 1.542, 1.541, 1.542, 1.547, 1.557, 1.573, 1.588, 1.597, 1.597, 1.589, 1.566, 1.537, 1.531, 1.529, + 1.526, 1.569, 1.602, 1.609, 1.609, 1.606, 1.605, 1.604, 1.598, 1.582, 1.567, 1.553, 1.547, 1.545, 1.544, 1.544, 1.544, 1.542, 1.542, 1.542, 1.544, 1.552, 1.559, 1.576, 1.591, 1.597, 1.597, 1.588, 1.563, 1.535, 1.531, 1.529, + 1.523, 1.567, 1.601, 1.609, 1.609, 1.606, 1.605, 1.603, 1.599, 1.587, 1.571, 1.558, 1.549, 1.545, 1.544, 1.543, 1.543, 1.542, 1.542, 1.544, 1.548, 1.555, 1.566, 1.581, 1.593, 1.597, 1.597, 1.586, 1.558, 1.534, 1.529, 1.529, + 1.523, 1.564, 1.599, 1.609, 1.609, 1.605, 1.604, 1.603, 1.601, 1.592, 1.576, 1.564, 1.553, 1.547, 1.544, 1.543, 1.542, 1.542, 1.544, 1.548, 1.551, 1.561, 1.572, 1.585, 1.594, 1.596, 1.595, 1.581, 1.555, 1.528, 1.527, 1.528, + 1.522, 1.561, 1.595, 1.608, 1.608, 1.604, 1.602, 1.601, 1.601, 1.595, 1.582, 1.569, 1.559, 1.552, 1.547, 1.545, 1.543, 1.544, 1.546, 1.551, 1.556, 1.563, 1.576, 1.589, 1.595, 1.596, 1.593, 1.576, 1.551, 1.524, 1.524, 1.528, + 1.519, 1.559, 1.591, 1.605, 1.606, 1.603, 1.601, 1.599, 1.601, 1.597, 1.587, 1.576, 1.565, 1.558, 1.552, 1.549, 1.546, 1.547, 1.552, 1.556, 1.561, 1.571, 1.582, 1.593, 1.596, 1.596, 1.591, 1.569, 1.546, 1.521, 1.521, 1.527, + 1.516, 1.553, 1.589, 1.602, 1.604, 1.602, 1.599, 1.598, 1.599, 1.598, 1.594, 1.583, 1.572, 1.564, 1.559, 1.553, 1.552, 1.553, 1.556, 1.561, 1.567, 1.578, 1.588, 1.594, 1.596, 1.594, 1.588, 1.567, 1.539, 1.517, 1.517, 1.525, + 1.511, 1.548, 1.581, 1.599, 1.602, 1.602, 1.598, 1.597, 1.597, 1.597, 1.595, 1.589, 1.581, 1.571, 1.564, 1.559, 1.559, 1.558, 1.561, 1.567, 1.575, 1.583, 1.591, 1.593, 1.594, 1.591, 1.581, 1.557, 1.529, 1.514, 1.514, 1.521, + 1.508, 1.541, 1.576, 1.596, 1.601, 1.601, 1.597, 1.595, 1.594, 1.595, 1.595, 1.592, 1.585, 1.579, 1.571, 1.566, 1.566, 1.566, 1.568, 1.575, 1.582, 1.589, 1.592, 1.593, 1.593, 1.589, 1.575, 1.553, 1.523, 1.511, 1.511, 1.517, + 1.505, 1.535, 1.566, 1.591, 1.599, 1.598, 1.596, 1.594, 1.592, 1.592, 1.593, 1.592, 1.589, 1.585, 1.579, 1.575, 1.574, 1.574, 1.577, 1.582, 1.587, 1.591, 1.592, 1.593, 1.592, 1.585, 1.568, 1.541, 1.516, 1.509, 1.509, 1.517, + 1.501, 1.528, 1.559, 1.585, 1.595, 1.597, 1.595, 1.593, 1.589, 1.588, 1.591, 1.591, 1.591, 1.589, 1.586, 1.583, 1.582, 1.582, 1.585, 1.588, 1.589, 1.591, 1.592, 1.593, 1.592, 1.582, 1.561, 1.536, 1.512, 1.509, 1.511, 1.517, + 1.496, 1.521, 1.549, 1.576, 1.588, 1.594, 1.593, 1.589, 1.586, 1.585, 1.586, 1.588, 1.589, 1.588, 1.588, 1.587, 1.587, 1.587, 1.589, 1.589, 1.591, 1.591, 1.592, 1.592, 1.591, 1.575, 1.555, 1.527, 1.508, 1.507, 1.511, 1.519, + 1.495, 1.505, 1.536, 1.563, 1.581, 1.587, 1.588, 1.584, 1.582, 1.578, 1.578, 1.581, 1.583, 1.584, 1.586, 1.587, 1.587, 1.587, 1.588, 1.589, 1.589, 1.591, 1.591, 1.591, 1.584, 1.566, 1.544, 1.518, 1.505, 1.505, 1.509, 1.519, + 1.493, 1.496, 1.522, 1.547, 1.569, 1.581, 1.582, 1.581, 1.577, 1.575, 1.573, 1.575, 1.579, 1.581, 1.583, 1.584, 1.584, 1.585, 1.587, 1.587, 1.588, 1.588, 1.588, 1.585, 1.573, 1.556, 1.532, 1.511, 1.504, 1.504, 1.508, 1.523 + ] + } + ], + "luminance_lut": + [ + 4.461, 4.088, 3.793, 3.651, 3.557, 3.439, 3.248, 2.999, 2.751, 2.527, 2.341, 2.191, 2.069, 1.956, 1.907, 1.907, 1.907, 1.908, 1.946, 2.056, 2.179, 2.328, 2.517, 2.747, 2.998, 3.219, 3.359, 3.436, 3.494, 3.621, 3.906, 4.251, + 4.297, 3.982, 3.747, 3.634, 3.531, 3.373, 3.136, 2.863, 2.608, 2.386, 2.209, 2.075, 1.957, 1.873, 1.817, 1.789, 1.789, 1.813, 1.865, 1.947, 2.066, 2.198, 2.378, 2.605, 2.872, 3.132, 3.322, 3.431, 3.485, 3.577, 3.802, 4.079, + 4.152, 3.905, 3.717, 3.623, 3.499, 3.296, 3.022, 2.735, 2.478, 2.265, 2.094, 1.957, 1.849, 1.763, 1.709, 1.679, 1.679, 1.703, 1.753, 1.837, 1.947, 2.081, 2.253, 2.472, 2.742, 3.032, 3.271, 3.414, 3.479, 3.545, 3.719, 3.937, + 4.039, 3.835, 3.688, 3.596, 3.442, 3.196, 2.899, 2.609, 2.356, 2.153, 1.987, 1.849, 1.748, 1.659, 1.605, 1.577, 1.577, 1.599, 1.649, 1.734, 1.837, 1.973, 2.139, 2.348, 2.612, 2.911, 3.192, 3.379, 3.467, 3.516, 3.649, 3.815, + 3.952, 3.784, 3.669, 3.562, 3.369, 3.088, 2.778, 2.491, 2.246, 2.049, 1.888, 1.748, 1.657, 1.561, 1.509, 1.481, 1.481, 1.504, 1.552, 1.642, 1.734, 1.869, 2.033, 2.233, 2.489, 2.792, 3.105, 3.331, 3.445, 3.493, 3.591, 3.721, + 3.883, 3.741, 3.648, 3.519, 3.287, 2.977, 2.665, 2.382, 2.148, 1.957, 1.796, 1.659, 1.561, 1.474, 1.422, 1.396, 1.396, 1.415, 1.465, 1.552, 1.643, 1.776, 1.936, 2.131, 2.375, 2.678, 3.004, 3.275, 3.416, 3.469, 3.541, 3.643, + 3.829, 3.716, 3.617, 3.466, 3.202, 2.876, 2.558, 2.282, 2.059, 1.872, 1.713, 1.577, 1.474, 1.399, 1.345, 1.319, 1.319, 1.338, 1.389, 1.465, 1.559, 1.689, 1.849, 2.042, 2.275, 2.568, 2.903, 3.204, 3.383, 3.446, 3.496, 3.579, + 3.793, 3.685, 3.589, 3.411, 3.119, 2.781, 2.466, 2.199, 1.983, 1.798, 1.639, 1.505, 1.399, 1.339, 1.276, 1.253, 1.253, 1.271, 1.327, 1.389, 1.487, 1.612, 1.769, 1.961, 2.189, 2.471, 2.806, 3.133, 3.342, 3.425, 3.459, 3.527, + 3.763, 3.666, 3.561, 3.357, 3.042, 2.698, 2.384, 2.129, 1.918, 1.734, 1.575, 1.443, 1.339, 1.276, 1.217, 1.194, 1.194, 1.214, 1.271, 1.327, 1.423, 1.546, 1.702, 1.891, 2.112, 2.386, 2.718, 3.061, 3.301, 3.402, 3.433, 3.486, + 3.745, 3.647, 3.529, 3.302, 2.971, 2.627, 2.318, 2.067, 1.859, 1.677, 1.521, 1.389, 1.287, 1.217, 1.171, 1.145, 1.145, 1.165, 1.214, 1.276, 1.369, 1.491, 1.643, 1.831, 2.048, 2.313, 2.644, 2.995, 3.262, 3.381, 3.412, 3.453, + 3.731, 3.635, 3.503, 3.249, 2.911, 2.566, 2.259, 2.017, 1.811, 1.629, 1.475, 1.347, 1.246, 1.171, 1.138, 1.103, 1.103, 1.129, 1.165, 1.231, 1.322, 1.443, 1.595, 1.779, 1.993, 2.251, 2.576, 2.936, 3.223, 3.359, 3.392, 3.425, + 3.721, 3.625, 3.481, 3.208, 2.861, 2.515, 2.213, 1.976, 1.773, 1.593, 1.439, 1.313, 1.213, 1.138, 1.103, 1.071, 1.071, 1.101, 1.129, 1.194, 1.286, 1.405, 1.555, 1.736, 1.949, 2.202, 2.521, 2.886, 3.189, 3.338, 3.375, 3.406, + 3.716, 3.616, 3.458, 3.171, 2.819, 2.472, 2.176, 1.942, 1.741, 1.563, 1.411, 1.285, 1.186, 1.112, 1.071, 1.051, 1.049, 1.069, 1.103, 1.165, 1.256, 1.376, 1.523, 1.702, 1.913, 2.163, 2.477, 2.843, 3.155, 3.318, 3.358, 3.389, + 3.712, 3.609, 3.439, 3.142, 2.787, 2.443, 2.147, 1.918, 1.721, 1.541, 1.391, 1.266, 1.167, 1.094, 1.051, 1.035, 1.035, 1.049, 1.085, 1.145, 1.236, 1.355, 1.499, 1.676, 1.886, 2.136, 2.449, 2.814, 3.135, 3.307, 3.351, 3.378, + 3.709, 3.604, 3.422, 3.123, 2.768, 2.419, 2.129, 1.903, 1.706, 1.527, 1.377, 1.253, 1.155, 1.083, 1.035, 1.023, 1.023, 1.035, 1.074, 1.134, 1.224, 1.341, 1.484, 1.661, 1.868, 2.117, 2.429, 2.797, 3.122, 3.301, 3.346, 3.374, + 3.711, 3.597, 3.412, 3.114, 2.758, 2.409, 2.119, 1.895, 1.701, 1.523, 1.373, 1.251, 1.153, 1.081, 1.033, 1.001, 1.001, 1.032, 1.073, 1.133, 1.222, 1.338, 1.479, 1.655, 1.861, 2.107, 2.418, 2.787, 3.115, 3.297, 3.343, 3.373, + 3.713, 3.597, 3.412, 3.113, 2.758, 2.409, 2.119, 1.893, 1.698, 1.523, 1.373, 1.251, 1.153, 1.081, 1.034, 1.011, 1.011, 1.032, 1.074, 1.134, 1.222, 1.338, 1.479, 1.655, 1.861, 2.107, 2.418, 2.787, 3.116, 3.294, 3.341, 3.371, + 3.721, 3.599, 3.414, 3.116, 2.763, 2.418, 2.124, 1.895, 1.704, 1.531, 1.382, 1.259, 1.162, 1.091, 1.048, 1.034, 1.032, 1.046, 1.083, 1.145, 1.232, 1.348, 1.491, 1.664, 1.869, 2.115, 2.428, 2.798, 3.123, 3.294, 3.339, 3.372, + 3.727, 3.604, 3.421, 3.132, 2.784, 2.438, 2.141, 1.908, 1.716, 1.547, 1.399, 1.276, 1.178, 1.107, 1.069, 1.048, 1.046, 1.067, 1.101, 1.162, 1.249, 1.366, 1.509, 1.684, 1.886, 2.134, 2.449, 2.821, 3.135, 3.299, 3.341, 3.375, + 3.739, 3.613, 3.431, 3.154, 2.813, 2.468, 2.166, 1.931, 1.739, 1.569, 1.424, 1.302, 1.203, 1.129, 1.098, 1.069, 1.069, 1.096, 1.123, 1.185, 1.274, 1.391, 1.536, 1.709, 1.914, 2.162, 2.481, 2.851, 3.156, 3.311, 3.342, 3.378, + 3.751, 3.626, 3.449, 3.186, 2.855, 2.509, 2.201, 1.961, 1.768, 1.601, 1.454, 1.333, 1.235, 1.159, 1.129, 1.098, 1.098, 1.123, 1.152, 1.216, 1.307, 1.424, 1.569, 1.744, 1.947, 2.202, 2.526, 2.891, 3.182, 3.322, 3.351, 3.387, + 3.772, 3.641, 3.473, 3.221, 2.902, 2.559, 2.248, 1.999, 1.804, 1.639, 1.496, 1.373, 1.274, 1.201, 1.159, 1.133, 1.133, 1.152, 1.191, 1.254, 1.347, 1.466, 1.611, 1.785, 1.989, 2.253, 2.582, 2.939, 3.209, 3.334, 3.361, 3.402, + 3.797, 3.663, 3.496, 3.263, 2.959, 2.624, 2.308, 2.049, 1.847, 1.684, 1.542, 1.422, 1.321, 1.252, 1.201, 1.175, 1.175, 1.191, 1.239, 1.298, 1.394, 1.516, 1.658, 1.831, 2.041, 2.313, 2.651, 2.998, 3.244, 3.351, 3.375, 3.422, + 3.831, 3.686, 3.523, 3.307, 3.023, 2.698, 2.379, 2.112, 1.902, 1.737, 1.596, 1.476, 1.378, 1.315, 1.252, 1.227, 1.227, 1.239, 1.296, 1.355, 1.451, 1.572, 1.715, 1.888, 2.103, 2.386, 2.731, 3.063, 3.279, 3.367, 3.393, 3.456, + 3.871, 3.714, 3.551, 3.355, 3.091, 2.781, 2.465, 2.186, 1.965, 1.795, 1.654, 1.538, 1.442, 1.378, 1.318, 1.291, 1.291, 1.304, 1.355, 1.424, 1.515, 1.634, 1.778, 1.952, 2.178, 2.479, 2.821, 3.129, 3.314, 3.381, 3.419, 3.491, + 3.925, 3.749, 3.582, 3.401, 3.156, 2.866, 2.559, 2.274, 2.039, 1.859, 1.718, 1.604, 1.513, 1.442, 1.389, 1.363, 1.363, 1.379, 1.424, 1.501, 1.586, 1.702, 1.847, 2.028, 2.269, 2.579, 2.913, 3.193, 3.343, 3.396, 3.447, 3.539, + 3.994, 3.794, 3.619, 3.442, 3.231, 2.961, 2.662, 2.375, 2.129, 1.938, 1.789, 1.675, 1.591, 1.513, 1.465, 1.439, 1.439, 1.457, 1.501, 1.582, 1.661, 1.777, 1.925, 2.118, 2.375, 2.691, 3.008, 3.251, 3.371, 3.414, 3.479, 3.598, + 4.082, 3.845, 3.656, 3.489, 3.298, 3.053, 2.771, 2.485, 2.232, 2.028, 1.871, 1.751, 1.672, 1.591, 1.544, 1.521, 1.521, 1.539, 1.582, 1.661, 1.741, 1.859, 2.014, 2.224, 2.495, 2.806, 3.098, 3.301, 3.392, 3.431, 3.518, 3.677, + 4.196, 3.911, 3.698, 3.534, 3.363, 3.146, 2.881, 2.604, 2.348, 2.132, 1.964, 1.836, 1.751, 1.672, 1.628, 1.606, 1.606, 1.624, 1.665, 1.741, 1.827, 1.951, 2.121, 2.344, 2.624, 2.923, 3.177, 3.336, 3.405, 3.447, 3.567, 3.776, + 4.341, 4.002, 3.744, 3.575, 3.415, 3.229, 2.989, 2.729, 2.475, 2.251, 2.071, 1.936, 1.836, 1.759, 1.713, 1.693, 1.693, 1.711, 1.753, 1.827, 1.925, 2.058, 2.243, 2.481, 2.758, 3.027, 3.238, 3.361, 3.409, 3.466, 3.637, 3.896, + 4.516, 4.123, 3.804, 3.621, 3.468, 3.308, 3.096, 2.855, 2.609, 2.385, 2.194, 2.045, 1.936, 1.857, 1.807, 1.784, 1.784, 1.803, 1.852, 1.925, 2.033, 2.183, 2.382, 2.623, 2.886, 3.121, 3.284, 3.372, 3.413, 3.494, 3.727, 4.048, + 4.716, 4.264, 3.875, 3.674, 3.523, 3.376, 3.189, 2.966, 2.733, 2.511, 2.315, 2.158, 2.039, 1.936, 1.875, 1.872, 1.872, 1.872, 1.925, 2.028, 2.148, 2.308, 2.513, 2.751, 2.994, 3.191, 3.319, 3.384, 3.427, 3.541, 3.838, 4.221 + ], + "sigma": 0.00152, + "sigma_Cb": 0.00172 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2750, + "ccm": + [ + 1.13004, 0.36392, -0.49396, + -0.45885, 1.68171, -0.22286, + -0.06473, -0.86962, 1.93435 + ] + }, + { + "ct": 2940, + "ccm": + [ + 1.29876, 0.09627, -0.39503, + -0.43085, 1.60258, -0.17172, + -0.02638, -0.92581, 1.95218 + ] + }, + { + "ct": 3650, + "ccm": + [ + 1.57729, -0.29734, -0.27995, + -0.42965, 1.66231, -0.23265, + -0.02183, -0.62331, 1.64514 + ] + }, + { + "ct": 4625, + "ccm": + [ + 1.52145, -0.22382, -0.29763, + -0.40445, 1.82186, -0.41742, + -0.05732, -0.56222, 1.61954 + ] + }, + { + "ct": 5715, + "ccm": + [ + 1.67851, -0.39193, -0.28658, + -0.37169, 1.72949, -0.35781, + -0.09556, -0.41951, 1.51508 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 4.0, + "max": 32.0, + "default": 6.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 2.0, + "step_fine": 0.5, + "contrast_ratio": 0.75, + "pdaf_gain": -0.03, + "pdaf_squelch": 0.2, + "max_slew": 4.0, + "pdaf_frames": 20, + "dropout_frames": 6, + "step_frames": 4 + }, + "fast": + { + "step_coarse": 2.0, + "step_fine": 0.5, + "contrast_ratio": 0.75, + "pdaf_gain": -0.05, + "pdaf_squelch": 0.2, + "max_slew": 5.0, + "pdaf_frames": 16, + "dropout_frames": 6, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 12, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 420, 35.0, 920 ] + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "power_min": 0.7, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "power_min": 0.7, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/meson.build b/src/ipa/rpi/pisp/data/meson.build new file mode 100644 index 00000000..6b8b4e94 --- /dev/null +++ b/src/ipa/rpi/pisp/data/meson.build @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: CC0-1.0 + +conf_files = files([ + 'imx219.json', + 'imx219_noir.json', + 'imx283.json', + 'imx290.json', + 'imx296.json', + 'imx296_mono.json', + 'imx378.json', + 'imx415.json', + 'imx462.json', + 'imx477.json', + 'imx477_noir.json', + 'imx477_scientific.json', + 'imx519.json', + 'imx708.json', + 'imx708_noir.json', + 'imx708_wide.json', + 'imx708_wide_noir.json', + 'ov5647.json', + 'ov5647_noir.json', + 'ov64a40.json', + 'ov9281_mono.json', + 'se327m12.json', + 'uncalibrated.json', +]) + +install_data(conf_files, + install_dir : ipa_data_dir / 'rpi' / 'pisp') diff --git a/src/ipa/rpi/pisp/data/ov5647.json b/src/ipa/rpi/pisp/data/ov5647.json new file mode 100644 index 00000000..d5156767 --- /dev/null +++ b/src/ipa/rpi/pisp/data/ov5647.json @@ -0,0 +1,1186 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 1024 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 29381, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 870, + "reference_Y": 12388 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 4.371 + } + }, + { + "rpi.geq": + { + "offset": 280, + "slope": 0.02153 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2873.0, 1.0463, 0.5142, + 2965.0, 1.0233, 0.5284, + 3606.0, 0.8947, 0.6314, + 4700.0, 0.7665, 0.7897, + 5890.0, 0.7055, 0.8933, + 7600.0, 0.6482, 1.0119 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.04072, + "transverse_neg": 0.03906 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.238, 1.238, 1.238, 1.234, 1.227, 1.216, 1.207, 1.198, 1.191, 1.179, 1.169, 1.162, 1.155, 1.153, 1.152, 1.152, 1.152, 1.153, 1.154, 1.157, 1.166, 1.176, 1.183, 1.191, 1.204, 1.216, 1.226, 1.232, 1.239, 1.241, 1.241, 1.242, + 1.235, 1.234, 1.227, 1.222, 1.214, 1.203, 1.193, 1.184, 1.169, 1.161, 1.149, 1.139, 1.131, 1.126, 1.122, 1.121, 1.121, 1.123, 1.129, 1.136, 1.145, 1.157, 1.163, 1.175, 1.189, 1.199, 1.212, 1.221, 1.225, 1.231, 1.241, 1.242, + 1.234, 1.227, 1.222, 1.214, 1.203, 1.193, 1.183, 1.169, 1.158, 1.145, 1.133, 1.123, 1.113, 1.106, 1.101, 1.101, 1.101, 1.105, 1.111, 1.116, 1.128, 1.137, 1.149, 1.163, 1.174, 1.189, 1.199, 1.212, 1.221, 1.226, 1.234, 1.241, + 1.234, 1.226, 1.217, 1.209, 1.195, 1.183, 1.171, 1.158, 1.145, 1.131, 1.119, 1.108, 1.097, 1.088, 1.088, 1.085, 1.085, 1.087, 1.095, 1.102, 1.114, 1.124, 1.137, 1.149, 1.165, 1.176, 1.194, 1.207, 1.214, 1.224, 1.235, 1.247, + 1.238, 1.224, 1.213, 1.202, 1.187, 1.175, 1.161, 1.146, 1.132, 1.117, 1.105, 1.094, 1.082, 1.074, 1.071, 1.071, 1.071, 1.073, 1.079, 1.089, 1.099, 1.112, 1.124, 1.137, 1.152, 1.167, 1.183, 1.198, 1.211, 1.222, 1.235, 1.249, + 1.232, 1.221, 1.209, 1.195, 1.178, 1.163, 1.149, 1.134, 1.118, 1.104, 1.093, 1.079, 1.069, 1.061, 1.057, 1.056, 1.056, 1.059, 1.066, 1.073, 1.086, 1.098, 1.111, 1.124, 1.141, 1.157, 1.173, 1.188, 1.203, 1.219, 1.234, 1.251, + 1.231, 1.213, 1.197, 1.186, 1.169, 1.151, 1.137, 1.121, 1.104, 1.093, 1.079, 1.068, 1.056, 1.048, 1.045, 1.042, 1.042, 1.045, 1.051, 1.061, 1.071, 1.085, 1.098, 1.111, 1.129, 1.145, 1.161, 1.179, 1.197, 1.215, 1.231, 1.249, + 1.224, 1.211, 1.194, 1.178, 1.161, 1.141, 1.127, 1.109, 1.094, 1.081, 1.068, 1.055, 1.047, 1.038, 1.034, 1.032, 1.032, 1.035, 1.039, 1.048, 1.059, 1.071, 1.086, 1.098, 1.116, 1.134, 1.154, 1.172, 1.191, 1.209, 1.228, 1.249, + 1.223, 1.206, 1.187, 1.171, 1.152, 1.132, 1.117, 1.098, 1.082, 1.069, 1.056, 1.045, 1.037, 1.028, 1.024, 1.022, 1.022, 1.025, 1.031, 1.039, 1.048, 1.059, 1.074, 1.091, 1.106, 1.126, 1.144, 1.163, 1.186, 1.205, 1.227, 1.247, + 1.222, 1.199, 1.183, 1.164, 1.143, 1.126, 1.108, 1.091, 1.075, 1.059, 1.045, 1.037, 1.028, 1.019, 1.015, 1.014, 1.014, 1.018, 1.023, 1.031, 1.042, 1.051, 1.065, 1.081, 1.098, 1.118, 1.137, 1.158, 1.181, 1.201, 1.224, 1.245, + 1.221, 1.198, 1.179, 1.163, 1.141, 1.119, 1.101, 1.083, 1.066, 1.051, 1.038, 1.028, 1.019, 1.012, 1.009, 1.008, 1.007, 1.008, 1.015, 1.023, 1.033, 1.044, 1.058, 1.072, 1.089, 1.107, 1.131, 1.152, 1.172, 1.196, 1.216, 1.241, + 1.216, 1.194, 1.174, 1.155, 1.133, 1.112, 1.094, 1.074, 1.059, 1.045, 1.032, 1.021, 1.012, 1.007, 1.003, 1.002, 1.002, 1.003, 1.008, 1.015, 1.025, 1.038, 1.049, 1.067, 1.084, 1.102, 1.126, 1.147, 1.169, 1.191, 1.214, 1.238, + 1.212, 1.188, 1.171, 1.149, 1.127, 1.105, 1.087, 1.069, 1.055, 1.039, 1.027, 1.016, 1.007, 1.003, 0.999, 0.997, 0.998, 1.001, 1.003, 1.011, 1.021, 1.032, 1.043, 1.059, 1.077, 1.101, 1.121, 1.142, 1.164, 1.187, 1.211, 1.236, + 1.208, 1.187, 1.169, 1.149, 1.124, 1.104, 1.085, 1.067, 1.051, 1.036, 1.024, 1.013, 1.005, 0.999, 0.996, 0.994, 0.994, 0.996, 1.001, 1.006, 1.017, 1.025, 1.038, 1.053, 1.072, 1.093, 1.116, 1.138, 1.159, 1.183, 1.207, 1.235, + 1.208, 1.181, 1.164, 1.144, 1.122, 1.098, 1.079, 1.062, 1.046, 1.033, 1.018, 1.009, 1.002, 0.996, 0.992, 0.989, 0.991, 0.994, 0.996, 1.002, 1.012, 1.021, 1.035, 1.051, 1.069, 1.091, 1.113, 1.137, 1.157, 1.182, 1.206, 1.233, + 1.206, 1.179, 1.163, 1.142, 1.119, 1.098, 1.079, 1.061, 1.045, 1.031, 1.017, 1.008, 1.001, 0.995, 0.991, 0.989, 0.989, 0.992, 0.996, 1.001, 1.011, 1.019, 1.034, 1.051, 1.069, 1.089, 1.112, 1.136, 1.157, 1.181, 1.205, 1.233, + 1.206, 1.179, 1.163, 1.139, 1.119, 1.098, 1.079, 1.061, 1.044, 1.031, 1.016, 1.007, 1.001, 0.995, 0.991, 0.989, 0.989, 0.991, 0.996, 1.002, 1.011, 1.019, 1.034, 1.049, 1.069, 1.088, 1.113, 1.136, 1.156, 1.179, 1.204, 1.233, + 1.207, 1.179, 1.163, 1.139, 1.119, 1.099, 1.079, 1.061, 1.044, 1.031, 1.017, 1.007, 1.001, 0.995, 0.991, 0.989, 0.989, 0.992, 0.997, 1.003, 1.011, 1.021, 1.034, 1.051, 1.071, 1.089, 1.112, 1.136, 1.157, 1.179, 1.204, 1.233, + 1.207, 1.179, 1.163, 1.143, 1.121, 1.101, 1.082, 1.063, 1.047, 1.032, 1.019, 1.009, 1.003, 0.998, 0.994, 0.991, 0.991, 0.995, 0.999, 1.004, 1.013, 1.024, 1.038, 1.052, 1.071, 1.091, 1.112, 1.136, 1.159, 1.181, 1.205, 1.233, + 1.207, 1.185, 1.166, 1.148, 1.124, 1.104, 1.087, 1.068, 1.052, 1.037, 1.025, 1.016, 1.006, 1.002, 0.998, 0.995, 0.995, 0.999, 1.003, 1.008, 1.017, 1.029, 1.043, 1.056, 1.076, 1.094, 1.116, 1.138, 1.159, 1.183, 1.205, 1.232, + 1.211, 1.186, 1.167, 1.151, 1.128, 1.108, 1.089, 1.072, 1.057, 1.042, 1.031, 1.021, 1.013, 1.006, 1.002, 0.999, 0.999, 1.003, 1.007, 1.013, 1.021, 1.031, 1.047, 1.062, 1.081, 1.098, 1.121, 1.141, 1.164, 1.185, 1.207, 1.232, + 1.211, 1.188, 1.169, 1.154, 1.134, 1.114, 1.094, 1.078, 1.063, 1.051, 1.039, 1.028, 1.019, 1.013, 1.007, 1.006, 1.006, 1.007, 1.013, 1.019, 1.027, 1.039, 1.051, 1.069, 1.087, 1.105, 1.124, 1.146, 1.165, 1.186, 1.209, 1.232, + 1.214, 1.191, 1.175, 1.159, 1.141, 1.123, 1.105, 1.087, 1.072, 1.058, 1.046, 1.036, 1.028, 1.019, 1.014, 1.013, 1.013, 1.015, 1.019, 1.027, 1.037, 1.048, 1.061, 1.076, 1.094, 1.109, 1.132, 1.149, 1.169, 1.189, 1.209, 1.233, + 1.219, 1.194, 1.179, 1.163, 1.146, 1.129, 1.113, 1.095, 1.081, 1.066, 1.055, 1.045, 1.036, 1.029, 1.023, 1.021, 1.021, 1.026, 1.031, 1.037, 1.048, 1.057, 1.069, 1.085, 1.101, 1.118, 1.137, 1.156, 1.174, 1.193, 1.213, 1.233, + 1.219, 1.199, 1.184, 1.172, 1.155, 1.138, 1.122, 1.104, 1.088, 1.075, 1.065, 1.055, 1.045, 1.038, 1.034, 1.031, 1.031, 1.035, 1.041, 1.048, 1.057, 1.066, 1.081, 1.096, 1.111, 1.125, 1.146, 1.164, 1.178, 1.196, 1.214, 1.233, + 1.222, 1.204, 1.189, 1.178, 1.162, 1.148, 1.132, 1.115, 1.101, 1.087, 1.075, 1.064, 1.055, 1.048, 1.043, 1.042, 1.042, 1.046, 1.049, 1.057, 1.066, 1.076, 1.089, 1.106, 1.121, 1.133, 1.149, 1.167, 1.183, 1.199, 1.215, 1.234, + 1.222, 1.205, 1.191, 1.184, 1.171, 1.155, 1.142, 1.124, 1.109, 1.097, 1.087, 1.077, 1.065, 1.059, 1.055, 1.053, 1.053, 1.057, 1.059, 1.067, 1.076, 1.088, 1.102, 1.116, 1.131, 1.143, 1.157, 1.175, 1.187, 1.202, 1.215, 1.231, + 1.223, 1.211, 1.198, 1.189, 1.178, 1.165, 1.151, 1.136, 1.122, 1.108, 1.097, 1.087, 1.079, 1.073, 1.067, 1.066, 1.066, 1.069, 1.074, 1.079, 1.088, 1.101, 1.114, 1.128, 1.141, 1.152, 1.166, 1.182, 1.194, 1.205, 1.215, 1.229, + 1.223, 1.212, 1.204, 1.197, 1.186, 1.173, 1.161, 1.149, 1.133, 1.121, 1.108, 1.101, 1.092, 1.085, 1.082, 1.082, 1.082, 1.085, 1.091, 1.096, 1.101, 1.113, 1.125, 1.138, 1.151, 1.164, 1.175, 1.188, 1.198, 1.207, 1.215, 1.222, + 1.217, 1.213, 1.211, 1.203, 1.194, 1.181, 1.169, 1.158, 1.145, 1.133, 1.123, 1.113, 1.106, 1.097, 1.096, 1.094, 1.094, 1.098, 1.104, 1.108, 1.114, 1.124, 1.137, 1.149, 1.161, 1.172, 1.182, 1.194, 1.203, 1.209, 1.211, 1.217, + 1.214, 1.211, 1.209, 1.206, 1.201, 1.188, 1.179, 1.168, 1.154, 1.144, 1.136, 1.126, 1.119, 1.112, 1.109, 1.108, 1.108, 1.108, 1.117, 1.119, 1.124, 1.133, 1.147, 1.158, 1.171, 1.178, 1.188, 1.198, 1.205, 1.208, 1.209, 1.211, + 1.207, 1.208, 1.209, 1.206, 1.202, 1.192, 1.182, 1.171, 1.159, 1.146, 1.142, 1.136, 1.126, 1.119, 1.116, 1.114, 1.115, 1.117, 1.119, 1.128, 1.129, 1.136, 1.155, 1.162, 1.176, 1.182, 1.188, 1.198, 1.205, 1.208, 1.207, 1.206 + ] + }, + { + "ct": 5000, + "table": + [ + 1.879, 1.878, 1.872, 1.862, 1.856, 1.842, 1.826, 1.815, 1.811, 1.799, 1.787, 1.777, 1.768, 1.761, 1.761, 1.761, 1.762, 1.763, 1.764, 1.769, 1.776, 1.789, 1.799, 1.807, 1.824, 1.841, 1.853, 1.861, 1.871, 1.874, 1.885, 1.889, + 1.879, 1.875, 1.859, 1.846, 1.835, 1.817, 1.806, 1.794, 1.777, 1.771, 1.755, 1.743, 1.733, 1.726, 1.721, 1.721, 1.721, 1.722, 1.729, 1.734, 1.747, 1.759, 1.771, 1.783, 1.801, 1.813, 1.831, 1.841, 1.849, 1.862, 1.876, 1.888, + 1.876, 1.861, 1.846, 1.835, 1.817, 1.806, 1.793, 1.777, 1.766, 1.752, 1.736, 1.727, 1.713, 1.702, 1.696, 1.695, 1.695, 1.697, 1.704, 1.715, 1.725, 1.739, 1.754, 1.771, 1.783, 1.801, 1.813, 1.831, 1.841, 1.851, 1.866, 1.888, + 1.878, 1.861, 1.843, 1.829, 1.811, 1.794, 1.779, 1.766, 1.751, 1.734, 1.721, 1.711, 1.695, 1.682, 1.679, 1.677, 1.677, 1.678, 1.687, 1.696, 1.713, 1.723, 1.737, 1.754, 1.774, 1.785, 1.811, 1.825, 1.833, 1.849, 1.866, 1.889, + 1.882, 1.859, 1.837, 1.821, 1.803, 1.784, 1.769, 1.752, 1.735, 1.717, 1.701, 1.689, 1.676, 1.664, 1.659, 1.658, 1.658, 1.659, 1.668, 1.679, 1.694, 1.711, 1.723, 1.739, 1.756, 1.777, 1.797, 1.813, 1.827, 1.844, 1.865, 1.889, + 1.869, 1.849, 1.832, 1.811, 1.792, 1.772, 1.755, 1.737, 1.717, 1.699, 1.688, 1.674, 1.661, 1.646, 1.642, 1.638, 1.638, 1.641, 1.651, 1.659, 1.676, 1.693, 1.708, 1.724, 1.744, 1.763, 1.783, 1.801, 1.819, 1.838, 1.864, 1.889, + 1.869, 1.841, 1.817, 1.801, 1.782, 1.758, 1.741, 1.721, 1.699, 1.688, 1.674, 1.658, 1.643, 1.632, 1.627, 1.621, 1.621, 1.622, 1.631, 1.643, 1.658, 1.676, 1.689, 1.708, 1.729, 1.748, 1.767, 1.791, 1.812, 1.836, 1.859, 1.891, + 1.861, 1.836, 1.814, 1.792, 1.772, 1.745, 1.728, 1.707, 1.688, 1.673, 1.658, 1.643, 1.629, 1.618, 1.612, 1.609, 1.609, 1.611, 1.615, 1.629, 1.642, 1.658, 1.676, 1.689, 1.711, 1.734, 1.758, 1.782, 1.804, 1.827, 1.859, 1.891, + 1.861, 1.829, 1.807, 1.784, 1.759, 1.735, 1.717, 1.692, 1.674, 1.659, 1.644, 1.629, 1.617, 1.605, 1.598, 1.595, 1.595, 1.598, 1.607, 1.615, 1.631, 1.642, 1.661, 1.681, 1.701, 1.724, 1.746, 1.771, 1.799, 1.825, 1.857, 1.891, + 1.861, 1.826, 1.804, 1.779, 1.749, 1.729, 1.707, 1.687, 1.665, 1.648, 1.629, 1.617, 1.604, 1.595, 1.589, 1.585, 1.585, 1.592, 1.597, 1.607, 1.623, 1.635, 1.652, 1.674, 1.693, 1.716, 1.739, 1.766, 1.794, 1.822, 1.855, 1.889, + 1.861, 1.824, 1.799, 1.777, 1.748, 1.723, 1.701, 1.678, 1.657, 1.639, 1.619, 1.605, 1.596, 1.586, 1.581, 1.579, 1.577, 1.579, 1.588, 1.597, 1.612, 1.625, 1.641, 1.661, 1.681, 1.702, 1.732, 1.757, 1.785, 1.813, 1.847, 1.882, + 1.856, 1.819, 1.796, 1.767, 1.739, 1.714, 1.693, 1.666, 1.651, 1.629, 1.613, 1.597, 1.586, 1.579, 1.576, 1.572, 1.572, 1.573, 1.579, 1.588, 1.602, 1.619, 1.633, 1.655, 1.674, 1.698, 1.729, 1.754, 1.782, 1.809, 1.842, 1.874, + 1.853, 1.815, 1.792, 1.761, 1.734, 1.707, 1.682, 1.659, 1.639, 1.622, 1.605, 1.591, 1.579, 1.574, 1.569, 1.565, 1.566, 1.569, 1.573, 1.584, 1.597, 1.609, 1.624, 1.645, 1.666, 1.695, 1.722, 1.746, 1.772, 1.799, 1.835, 1.873, + 1.847, 1.811, 1.789, 1.759, 1.732, 1.703, 1.681, 1.657, 1.637, 1.619, 1.603, 1.588, 1.575, 1.569, 1.563, 1.561, 1.561, 1.563, 1.569, 1.576, 1.589, 1.601, 1.616, 1.636, 1.659, 1.686, 1.712, 1.741, 1.767, 1.798, 1.832, 1.873, + 1.847, 1.803, 1.779, 1.756, 1.727, 1.699, 1.674, 1.652, 1.632, 1.616, 1.595, 1.583, 1.572, 1.564, 1.558, 1.556, 1.557, 1.559, 1.563, 1.569, 1.583, 1.593, 1.613, 1.633, 1.657, 1.684, 1.709, 1.741, 1.766, 1.796, 1.831, 1.871, + 1.845, 1.802, 1.779, 1.755, 1.725, 1.696, 1.673, 1.649, 1.629, 1.614, 1.595, 1.582, 1.572, 1.563, 1.557, 1.556, 1.556, 1.558, 1.562, 1.569, 1.581, 1.593, 1.612, 1.633, 1.656, 1.679, 1.709, 1.741, 1.764, 1.796, 1.828, 1.869, + 1.845, 1.801, 1.779, 1.749, 1.723, 1.697, 1.673, 1.649, 1.627, 1.613, 1.593, 1.581, 1.573, 1.563, 1.558, 1.555, 1.555, 1.556, 1.562, 1.573, 1.581, 1.594, 1.611, 1.633, 1.656, 1.679, 1.711, 1.739, 1.764, 1.794, 1.828, 1.869, + 1.844, 1.801, 1.781, 1.749, 1.723, 1.697, 1.673, 1.649, 1.627, 1.614, 1.595, 1.581, 1.574, 1.564, 1.559, 1.557, 1.556, 1.559, 1.564, 1.574, 1.582, 1.595, 1.611, 1.634, 1.659, 1.683, 1.709, 1.739, 1.765, 1.794, 1.829, 1.872, + 1.845, 1.802, 1.781, 1.754, 1.725, 1.701, 1.677, 1.652, 1.632, 1.616, 1.599, 1.586, 1.576, 1.569, 1.563, 1.559, 1.558, 1.562, 1.569, 1.576, 1.587, 1.599, 1.618, 1.635, 1.661, 1.685, 1.709, 1.739, 1.767, 1.796, 1.829, 1.868, + 1.845, 1.809, 1.785, 1.762, 1.731, 1.706, 1.685, 1.659, 1.641, 1.622, 1.606, 1.595, 1.581, 1.575, 1.569, 1.564, 1.564, 1.569, 1.574, 1.582, 1.594, 1.607, 1.625, 1.642, 1.668, 1.687, 1.716, 1.741, 1.769, 1.798, 1.829, 1.868, + 1.849, 1.811, 1.785, 1.765, 1.734, 1.709, 1.688, 1.666, 1.647, 1.628, 1.613, 1.601, 1.592, 1.581, 1.575, 1.572, 1.572, 1.575, 1.581, 1.589, 1.599, 1.611, 1.631, 1.649, 1.673, 1.694, 1.721, 1.747, 1.771, 1.798, 1.829, 1.868, + 1.849, 1.816, 1.787, 1.766, 1.739, 1.716, 1.692, 1.673, 1.657, 1.641, 1.625, 1.612, 1.599, 1.592, 1.584, 1.581, 1.581, 1.581, 1.589, 1.598, 1.608, 1.622, 1.639, 1.659, 1.679, 1.701, 1.724, 1.751, 1.774, 1.802, 1.832, 1.868, + 1.855, 1.816, 1.793, 1.773, 1.748, 1.727, 1.707, 1.686, 1.667, 1.649, 1.636, 1.623, 1.612, 1.599, 1.594, 1.592, 1.591, 1.591, 1.598, 1.608, 1.621, 1.634, 1.649, 1.669, 1.693, 1.705, 1.736, 1.757, 1.778, 1.804, 1.833, 1.867, + 1.858, 1.818, 1.796, 1.778, 1.754, 1.733, 1.716, 1.695, 1.676, 1.661, 1.648, 1.635, 1.624, 1.613, 1.604, 1.601, 1.601, 1.606, 1.613, 1.621, 1.634, 1.646, 1.661, 1.679, 1.699, 1.714, 1.742, 1.761, 1.782, 1.809, 1.835, 1.867, + 1.857, 1.822, 1.801, 1.789, 1.766, 1.744, 1.726, 1.706, 1.688, 1.671, 1.659, 1.647, 1.635, 1.624, 1.621, 1.617, 1.617, 1.621, 1.627, 1.634, 1.645, 1.656, 1.674, 1.694, 1.709, 1.723, 1.751, 1.771, 1.786, 1.811, 1.837, 1.867, + 1.858, 1.824, 1.807, 1.794, 1.774, 1.757, 1.739, 1.716, 1.702, 1.687, 1.671, 1.662, 1.648, 1.636, 1.629, 1.629, 1.629, 1.633, 1.635, 1.646, 1.656, 1.669, 1.684, 1.705, 1.719, 1.732, 1.753, 1.774, 1.793, 1.815, 1.837, 1.871, + 1.858, 1.827, 1.809, 1.798, 1.782, 1.761, 1.749, 1.727, 1.711, 1.698, 1.687, 1.675, 1.663, 1.649, 1.646, 1.643, 1.643, 1.646, 1.649, 1.658, 1.669, 1.683, 1.698, 1.716, 1.731, 1.746, 1.761, 1.783, 1.795, 1.817, 1.836, 1.862, + 1.862, 1.834, 1.816, 1.805, 1.789, 1.774, 1.759, 1.743, 1.725, 1.711, 1.697, 1.688, 1.678, 1.668, 1.661, 1.659, 1.658, 1.659, 1.668, 1.673, 1.684, 1.698, 1.713, 1.728, 1.742, 1.757, 1.771, 1.791, 1.804, 1.821, 1.836, 1.862, + 1.859, 1.835, 1.825, 1.813, 1.794, 1.782, 1.771, 1.757, 1.739, 1.725, 1.711, 1.701, 1.693, 1.683, 1.679, 1.679, 1.679, 1.683, 1.689, 1.693, 1.698, 1.714, 1.726, 1.741, 1.754, 1.769, 1.781, 1.797, 1.808, 1.821, 1.835, 1.856, + 1.848, 1.836, 1.832, 1.822, 1.806, 1.789, 1.778, 1.765, 1.751, 1.739, 1.726, 1.718, 1.709, 1.699, 1.696, 1.695, 1.695, 1.696, 1.704, 1.705, 1.714, 1.724, 1.739, 1.753, 1.765, 1.777, 1.789, 1.803, 1.816, 1.824, 1.829, 1.842, + 1.839, 1.835, 1.834, 1.829, 1.815, 1.801, 1.787, 1.776, 1.759, 1.751, 1.744, 1.736, 1.724, 1.714, 1.711, 1.708, 1.707, 1.707, 1.717, 1.719, 1.724, 1.734, 1.748, 1.762, 1.775, 1.783, 1.796, 1.808, 1.819, 1.825, 1.828, 1.833, + 1.836, 1.833, 1.834, 1.832, 1.821, 1.806, 1.792, 1.785, 1.772, 1.759, 1.751, 1.744, 1.736, 1.725, 1.719, 1.715, 1.715, 1.718, 1.721, 1.728, 1.734, 1.736, 1.757, 1.768, 1.779, 1.787, 1.799, 1.812, 1.821, 1.824, 1.825, 1.833 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.189, 2.127, 2.115, 2.106, 2.113, 2.119, 2.131, 2.144, 2.155, 2.168, 2.176, 2.179, 2.181, 2.181, 2.185, 2.187, 2.187, 2.183, 2.179, 2.176, 2.169, 2.167, 2.159, 2.152, 2.145, 2.141, 2.135, 2.128, 2.124, 2.124, 2.139, 2.177, + 2.176, 2.133, 2.116, 2.112, 2.116, 2.125, 2.137, 2.154, 2.168, 2.179, 2.187, 2.194, 2.201, 2.204, 2.208, 2.208, 2.205, 2.202, 2.198, 2.195, 2.183, 2.177, 2.166, 2.159, 2.149, 2.143, 2.138, 2.132, 2.124, 2.125, 2.136, 2.164, + 2.175, 2.133, 2.117, 2.115, 2.121, 2.136, 2.154, 2.165, 2.179, 2.192, 2.198, 2.211, 2.218, 2.219, 2.221, 2.221, 2.217, 2.216, 2.211, 2.202, 2.197, 2.188, 2.181, 2.171, 2.159, 2.151, 2.141, 2.136, 2.125, 2.125, 2.132, 2.155, + 2.172, 2.128, 2.116, 2.116, 2.124, 2.143, 2.161, 2.177, 2.192, 2.204, 2.213, 2.221, 2.227, 2.231, 2.237, 2.237, 2.229, 2.224, 2.221, 2.213, 2.207, 2.197, 2.191, 2.179, 2.169, 2.156, 2.148, 2.138, 2.126, 2.123, 2.124, 2.149, + 2.169, 2.124, 2.119, 2.119, 2.135, 2.152, 2.174, 2.187, 2.204, 2.211, 2.224, 2.233, 2.236, 2.241, 2.246, 2.246, 2.243, 2.237, 2.234, 2.226, 2.218, 2.211, 2.199, 2.191, 2.177, 2.166, 2.155, 2.139, 2.129, 2.121, 2.121, 2.143, + 2.157, 2.124, 2.121, 2.127, 2.145, 2.157, 2.181, 2.197, 2.208, 2.221, 2.238, 2.245, 2.249, 2.249, 2.254, 2.254, 2.249, 2.247, 2.243, 2.237, 2.228, 2.219, 2.209, 2.198, 2.186, 2.172, 2.161, 2.143, 2.129, 2.121, 2.121, 2.141, + 2.157, 2.124, 2.124, 2.131, 2.148, 2.161, 2.188, 2.202, 2.214, 2.238, 2.246, 2.251, 2.255, 2.257, 2.259, 2.259, 2.257, 2.252, 2.251, 2.247, 2.238, 2.231, 2.219, 2.204, 2.193, 2.173, 2.166, 2.152, 2.134, 2.119, 2.119, 2.131, + 2.155, 2.125, 2.125, 2.135, 2.151, 2.169, 2.191, 2.207, 2.219, 2.243, 2.253, 2.258, 2.261, 2.266, 2.266, 2.267, 2.265, 2.262, 2.261, 2.254, 2.244, 2.238, 2.228, 2.212, 2.197, 2.179, 2.167, 2.158, 2.137, 2.122, 2.121, 2.131, + 2.155, 2.127, 2.127, 2.137, 2.153, 2.173, 2.197, 2.213, 2.231, 2.248, 2.257, 2.266, 2.271, 2.272, 2.274, 2.275, 2.275, 2.273, 2.271, 2.266, 2.257, 2.251, 2.238, 2.227, 2.209, 2.195, 2.175, 2.159, 2.141, 2.128, 2.127, 2.131, + 2.155, 2.128, 2.128, 2.139, 2.159, 2.182, 2.206, 2.225, 2.243, 2.252, 2.265, 2.272, 2.277, 2.283, 2.286, 2.284, 2.283, 2.282, 2.274, 2.272, 2.266, 2.256, 2.244, 2.238, 2.221, 2.202, 2.186, 2.169, 2.149, 2.129, 2.129, 2.135, + 2.154, 2.131, 2.131, 2.149, 2.166, 2.189, 2.211, 2.234, 2.248, 2.262, 2.272, 2.277, 2.287, 2.291, 2.293, 2.292, 2.291, 2.285, 2.284, 2.279, 2.272, 2.263, 2.254, 2.243, 2.226, 2.206, 2.193, 2.174, 2.153, 2.133, 2.133, 2.135, + 2.153, 2.135, 2.135, 2.151, 2.172, 2.198, 2.221, 2.238, 2.255, 2.265, 2.274, 2.287, 2.291, 2.296, 2.298, 2.298, 2.301, 2.297, 2.289, 2.285, 2.277, 2.271, 2.261, 2.251, 2.236, 2.216, 2.199, 2.179, 2.158, 2.135, 2.134, 2.135, + 2.152, 2.136, 2.136, 2.154, 2.176, 2.199, 2.224, 2.239, 2.256, 2.267, 2.282, 2.289, 2.295, 2.299, 2.303, 2.303, 2.302, 2.299, 2.297, 2.288, 2.284, 2.274, 2.262, 2.253, 2.238, 2.219, 2.202, 2.181, 2.158, 2.137, 2.135, 2.135, + 2.143, 2.134, 2.134, 2.154, 2.177, 2.201, 2.224, 2.241, 2.256, 2.271, 2.282, 2.289, 2.297, 2.302, 2.306, 2.306, 2.304, 2.301, 2.298, 2.289, 2.287, 2.272, 2.265, 2.255, 2.241, 2.221, 2.203, 2.183, 2.164, 2.141, 2.136, 2.135, + 2.142, 2.133, 2.133, 2.155, 2.178, 2.202, 2.223, 2.243, 2.258, 2.273, 2.283, 2.288, 2.296, 2.299, 2.306, 2.306, 2.301, 2.299, 2.296, 2.289, 2.286, 2.271, 2.267, 2.256, 2.244, 2.219, 2.206, 2.188, 2.163, 2.141, 2.137, 2.134, + 2.141, 2.131, 2.131, 2.153, 2.179, 2.202, 2.224, 2.242, 2.254, 2.274, 2.283, 2.288, 2.295, 2.298, 2.301, 2.301, 2.301, 2.296, 2.295, 2.289, 2.285, 2.271, 2.267, 2.257, 2.246, 2.223, 2.204, 2.188, 2.165, 2.141, 2.136, 2.134, + 2.141, 2.133, 2.133, 2.151, 2.179, 2.201, 2.224, 2.241, 2.254, 2.275, 2.283, 2.288, 2.294, 2.296, 2.298, 2.297, 2.295, 2.295, 2.294, 2.291, 2.284, 2.272, 2.267, 2.256, 2.248, 2.225, 2.208, 2.192, 2.167, 2.141, 2.137, 2.134, + 2.141, 2.132, 2.132, 2.151, 2.177, 2.199, 2.221, 2.238, 2.252, 2.274, 2.281, 2.287, 2.293, 2.295, 2.296, 2.294, 2.295, 2.295, 2.294, 2.291, 2.284, 2.274, 2.266, 2.257, 2.248, 2.226, 2.206, 2.189, 2.167, 2.143, 2.141, 2.141, + 2.141, 2.133, 2.133, 2.153, 2.175, 2.201, 2.221, 2.238, 2.252, 2.271, 2.278, 2.284, 2.288, 2.291, 2.292, 2.291, 2.293, 2.293, 2.293, 2.287, 2.279, 2.275, 2.266, 2.256, 2.243, 2.224, 2.206, 2.189, 2.168, 2.146, 2.142, 2.134, + 2.137, 2.131, 2.131, 2.154, 2.173, 2.199, 2.221, 2.236, 2.251, 2.267, 2.272, 2.278, 2.284, 2.287, 2.288, 2.286, 2.288, 2.288, 2.288, 2.283, 2.277, 2.273, 2.265, 2.256, 2.241, 2.219, 2.205, 2.187, 2.167, 2.144, 2.137, 2.132, + 2.136, 2.131, 2.131, 2.152, 2.169, 2.197, 2.218, 2.233, 2.246, 2.257, 2.269, 2.274, 2.281, 2.284, 2.286, 2.285, 2.286, 2.286, 2.286, 2.279, 2.274, 2.269, 2.263, 2.254, 2.239, 2.217, 2.203, 2.181, 2.162, 2.143, 2.133, 2.131, + 2.136, 2.131, 2.131, 2.151, 2.167, 2.189, 2.205, 2.226, 2.242, 2.253, 2.261, 2.271, 2.275, 2.279, 2.283, 2.283, 2.284, 2.284, 2.281, 2.277, 2.271, 2.264, 2.257, 2.246, 2.232, 2.215, 2.195, 2.176, 2.158, 2.141, 2.131, 2.128, + 2.136, 2.129, 2.131, 2.147, 2.162, 2.181, 2.203, 2.219, 2.236, 2.246, 2.256, 2.263, 2.271, 2.274, 2.278, 2.278, 2.276, 2.277, 2.276, 2.273, 2.266, 2.258, 2.251, 2.241, 2.227, 2.198, 2.191, 2.169, 2.154, 2.136, 2.125, 2.122, + 2.132, 2.126, 2.126, 2.139, 2.153, 2.168, 2.194, 2.212, 2.224, 2.238, 2.251, 2.258, 2.263, 2.266, 2.269, 2.271, 2.269, 2.269, 2.269, 2.267, 2.259, 2.253, 2.245, 2.237, 2.219, 2.196, 2.179, 2.162, 2.149, 2.132, 2.122, 2.121, + 2.124, 2.119, 2.121, 2.137, 2.147, 2.164, 2.183, 2.199, 2.219, 2.231, 2.239, 2.251, 2.257, 2.261, 2.262, 2.262, 2.259, 2.259, 2.261, 2.258, 2.253, 2.245, 2.237, 2.224, 2.209, 2.187, 2.174, 2.157, 2.141, 2.122, 2.121, 2.121, + 2.123, 2.115, 2.115, 2.131, 2.138, 2.157, 2.174, 2.188, 2.207, 2.221, 2.233, 2.239, 2.243, 2.244, 2.244, 2.244, 2.246, 2.245, 2.246, 2.244, 2.241, 2.231, 2.224, 2.212, 2.195, 2.176, 2.159, 2.145, 2.128, 2.117, 2.117, 2.123, + 2.123, 2.113, 2.113, 2.123, 2.132, 2.141, 2.162, 2.177, 2.191, 2.208, 2.221, 2.231, 2.231, 2.232, 2.234, 2.235, 2.235, 2.235, 2.238, 2.237, 2.225, 2.214, 2.209, 2.199, 2.181, 2.164, 2.146, 2.135, 2.123, 2.116, 2.116, 2.115, + 2.129, 2.115, 2.115, 2.121, 2.128, 2.135, 2.149, 2.164, 2.178, 2.193, 2.207, 2.221, 2.222, 2.222, 2.223, 2.224, 2.224, 2.224, 2.224, 2.223, 2.214, 2.205, 2.196, 2.185, 2.171, 2.151, 2.141, 2.129, 2.119, 2.116, 2.116, 2.117, + 2.137, 2.119, 2.119, 2.119, 2.122, 2.129, 2.141, 2.159, 2.167, 2.182, 2.195, 2.206, 2.211, 2.216, 2.218, 2.219, 2.219, 2.219, 2.217, 2.212, 2.202, 2.194, 2.184, 2.174, 2.162, 2.145, 2.134, 2.124, 2.118, 2.117, 2.118, 2.121, + 2.138, 2.131, 2.121, 2.122, 2.125, 2.128, 2.137, 2.154, 2.162, 2.176, 2.187, 2.194, 2.196, 2.198, 2.205, 2.205, 2.202, 2.202, 2.203, 2.201, 2.191, 2.182, 2.174, 2.162, 2.149, 2.136, 2.126, 2.121, 2.119, 2.118, 2.127, 2.133, + 2.157, 2.148, 2.131, 2.129, 2.129, 2.136, 2.148, 2.157, 2.169, 2.177, 2.182, 2.187, 2.188, 2.191, 2.193, 2.193, 2.192, 2.199, 2.201, 2.199, 2.186, 2.178, 2.167, 2.152, 2.146, 2.137, 2.126, 2.124, 2.121, 2.126, 2.133, 2.151, + 2.161, 2.157, 2.148, 2.147, 2.147, 2.147, 2.154, 2.162, 2.174, 2.179, 2.181, 2.184, 2.186, 2.187, 2.189, 2.189, 2.187, 2.188, 2.199, 2.201, 2.187, 2.178, 2.163, 2.148, 2.145, 2.141, 2.131, 2.129, 2.128, 2.135, 2.151, 2.153 + ] + }, + { + "ct": 5000, + "table": + [ + 1.191, 1.165, 1.156, 1.155, 1.157, 1.161, 1.168, 1.176, 1.179, 1.185, 1.187, 1.189, 1.189, 1.189, 1.191, 1.191, 1.191, 1.189, 1.188, 1.188, 1.185, 1.184, 1.182, 1.178, 1.173, 1.171, 1.166, 1.163, 1.159, 1.159, 1.164, 1.187, + 1.188, 1.164, 1.157, 1.156, 1.158, 1.166, 1.173, 1.179, 1.185, 1.193, 1.195, 1.198, 1.199, 1.201, 1.201, 1.202, 1.201, 1.199, 1.199, 1.196, 1.194, 1.189, 1.185, 1.182, 1.177, 1.172, 1.168, 1.164, 1.161, 1.161, 1.162, 1.181, + 1.184, 1.164, 1.157, 1.157, 1.161, 1.171, 1.179, 1.185, 1.193, 1.197, 1.201, 1.206, 1.208, 1.209, 1.209, 1.208, 1.207, 1.207, 1.207, 1.202, 1.199, 1.195, 1.192, 1.189, 1.182, 1.176, 1.171, 1.166, 1.161, 1.159, 1.161, 1.177, + 1.183, 1.162, 1.158, 1.158, 1.163, 1.174, 1.182, 1.191, 1.197, 1.203, 1.208, 1.212, 1.214, 1.214, 1.218, 1.218, 1.214, 1.212, 1.211, 1.208, 1.206, 1.201, 1.197, 1.192, 1.189, 1.179, 1.174, 1.168, 1.162, 1.159, 1.159, 1.173, + 1.181, 1.159, 1.159, 1.159, 1.168, 1.178, 1.189, 1.196, 1.204, 1.208, 1.213, 1.217, 1.219, 1.221, 1.222, 1.222, 1.222, 1.221, 1.219, 1.215, 1.212, 1.208, 1.202, 1.197, 1.189, 1.183, 1.178, 1.169, 1.163, 1.158, 1.158, 1.169, + 1.174, 1.159, 1.159, 1.164, 1.172, 1.179, 1.192, 1.201, 1.208, 1.212, 1.219, 1.224, 1.225, 1.227, 1.228, 1.228, 1.226, 1.225, 1.224, 1.221, 1.217, 1.212, 1.208, 1.202, 1.194, 1.187, 1.181, 1.172, 1.164, 1.157, 1.157, 1.169, + 1.174, 1.159, 1.159, 1.165, 1.174, 1.184, 1.197, 1.205, 1.209, 1.219, 1.224, 1.228, 1.231, 1.231, 1.231, 1.231, 1.229, 1.229, 1.228, 1.226, 1.222, 1.218, 1.212, 1.205, 1.199, 1.188, 1.181, 1.175, 1.165, 1.157, 1.157, 1.163, + 1.173, 1.159, 1.159, 1.165, 1.176, 1.186, 1.198, 1.207, 1.213, 1.223, 1.229, 1.231, 1.235, 1.236, 1.236, 1.236, 1.236, 1.235, 1.234, 1.232, 1.226, 1.223, 1.218, 1.209, 1.201, 1.192, 1.183, 1.178, 1.165, 1.157, 1.157, 1.163, + 1.172, 1.159, 1.159, 1.166, 1.176, 1.188, 1.201, 1.209, 1.217, 1.227, 1.231, 1.236, 1.238, 1.239, 1.241, 1.242, 1.242, 1.241, 1.239, 1.235, 1.232, 1.227, 1.223, 1.215, 1.208, 1.199, 1.187, 1.179, 1.167, 1.159, 1.159, 1.163, + 1.172, 1.159, 1.159, 1.166, 1.177, 1.189, 1.203, 1.212, 1.223, 1.228, 1.236, 1.239, 1.242, 1.245, 1.246, 1.246, 1.247, 1.246, 1.242, 1.241, 1.237, 1.232, 1.226, 1.223, 1.213, 1.202, 1.191, 1.182, 1.172, 1.159, 1.159, 1.163, + 1.168, 1.158, 1.158, 1.167, 1.179, 1.192, 1.204, 1.218, 1.225, 1.233, 1.238, 1.242, 1.246, 1.248, 1.251, 1.251, 1.249, 1.248, 1.247, 1.244, 1.239, 1.237, 1.228, 1.223, 1.214, 1.203, 1.194, 1.183, 1.173, 1.161, 1.161, 1.162, + 1.166, 1.158, 1.158, 1.168, 1.183, 1.195, 1.207, 1.218, 1.226, 1.233, 1.239, 1.246, 1.248, 1.251, 1.254, 1.254, 1.254, 1.251, 1.249, 1.247, 1.242, 1.239, 1.232, 1.227, 1.219, 1.207, 1.195, 1.186, 1.175, 1.162, 1.161, 1.162, + 1.165, 1.158, 1.158, 1.168, 1.183, 1.196, 1.208, 1.219, 1.227, 1.234, 1.241, 1.247, 1.251, 1.254, 1.255, 1.256, 1.256, 1.254, 1.252, 1.249, 1.246, 1.241, 1.234, 1.228, 1.221, 1.211, 1.199, 1.187, 1.175, 1.163, 1.162, 1.162, + 1.161, 1.158, 1.158, 1.169, 1.183, 1.196, 1.208, 1.217, 1.227, 1.234, 1.241, 1.247, 1.253, 1.254, 1.256, 1.257, 1.256, 1.255, 1.253, 1.249, 1.247, 1.241, 1.236, 1.229, 1.221, 1.211, 1.199, 1.189, 1.176, 1.164, 1.163, 1.162, + 1.161, 1.156, 1.156, 1.169, 1.183, 1.196, 1.207, 1.218, 1.227, 1.235, 1.241, 1.246, 1.252, 1.254, 1.256, 1.257, 1.256, 1.254, 1.253, 1.249, 1.247, 1.241, 1.237, 1.231, 1.223, 1.211, 1.201, 1.191, 1.177, 1.164, 1.164, 1.161, + 1.161, 1.155, 1.155, 1.169, 1.182, 1.195, 1.208, 1.216, 1.225, 1.235, 1.241, 1.245, 1.249, 1.252, 1.254, 1.254, 1.254, 1.253, 1.252, 1.249, 1.246, 1.239, 1.237, 1.231, 1.224, 1.211, 1.201, 1.191, 1.178, 1.164, 1.162, 1.161, + 1.159, 1.155, 1.155, 1.168, 1.181, 1.195, 1.208, 1.217, 1.223, 1.235, 1.241, 1.244, 1.248, 1.251, 1.252, 1.252, 1.252, 1.252, 1.251, 1.248, 1.245, 1.241, 1.236, 1.231, 1.224, 1.212, 1.202, 1.191, 1.179, 1.164, 1.162, 1.161, + 1.158, 1.154, 1.154, 1.167, 1.181, 1.194, 1.206, 1.216, 1.222, 1.234, 1.237, 1.242, 1.245, 1.248, 1.251, 1.249, 1.249, 1.249, 1.249, 1.248, 1.244, 1.241, 1.235, 1.229, 1.223, 1.213, 1.202, 1.191, 1.179, 1.167, 1.163, 1.163, + 1.158, 1.154, 1.154, 1.168, 1.181, 1.194, 1.206, 1.215, 1.223, 1.231, 1.236, 1.239, 1.243, 1.245, 1.246, 1.246, 1.248, 1.248, 1.248, 1.245, 1.242, 1.239, 1.235, 1.229, 1.223, 1.213, 1.202, 1.191, 1.179, 1.167, 1.163, 1.162, + 1.157, 1.154, 1.154, 1.168, 1.179, 1.194, 1.205, 1.215, 1.222, 1.229, 1.233, 1.236, 1.239, 1.243, 1.244, 1.244, 1.245, 1.245, 1.244, 1.243, 1.239, 1.236, 1.234, 1.229, 1.222, 1.211, 1.202, 1.191, 1.179, 1.166, 1.163, 1.161, + 1.156, 1.155, 1.155, 1.168, 1.179, 1.193, 1.205, 1.213, 1.219, 1.225, 1.231, 1.234, 1.238, 1.239, 1.241, 1.243, 1.243, 1.243, 1.243, 1.239, 1.237, 1.235, 1.231, 1.228, 1.221, 1.209, 1.199, 1.189, 1.178, 1.166, 1.162, 1.159, + 1.156, 1.156, 1.157, 1.167, 1.178, 1.191, 1.199, 1.209, 1.217, 1.223, 1.226, 1.231, 1.233, 1.236, 1.239, 1.239, 1.241, 1.241, 1.239, 1.237, 1.235, 1.232, 1.229, 1.224, 1.217, 1.209, 1.196, 1.187, 1.176, 1.165, 1.159, 1.157, + 1.157, 1.157, 1.157, 1.166, 1.175, 1.187, 1.198, 1.205, 1.213, 1.219, 1.223, 1.227, 1.231, 1.233, 1.236, 1.236, 1.234, 1.235, 1.235, 1.235, 1.231, 1.229, 1.227, 1.222, 1.216, 1.201, 1.194, 1.184, 1.174, 1.163, 1.157, 1.156, + 1.158, 1.155, 1.155, 1.165, 1.172, 1.181, 1.194, 1.202, 1.208, 1.215, 1.221, 1.223, 1.227, 1.229, 1.231, 1.231, 1.231, 1.232, 1.233, 1.231, 1.228, 1.227, 1.223, 1.219, 1.213, 1.199, 1.189, 1.181, 1.171, 1.161, 1.157, 1.156, + 1.155, 1.154, 1.154, 1.164, 1.169, 1.179, 1.189, 1.196, 1.203, 1.208, 1.215, 1.221, 1.222, 1.224, 1.225, 1.225, 1.226, 1.228, 1.228, 1.227, 1.225, 1.222, 1.219, 1.213, 1.206, 1.196, 1.187, 1.177, 1.168, 1.159, 1.156, 1.156, + 1.155, 1.152, 1.152, 1.162, 1.167, 1.175, 1.185, 1.191, 1.198, 1.205, 1.209, 1.214, 1.216, 1.217, 1.217, 1.217, 1.219, 1.219, 1.219, 1.219, 1.217, 1.215, 1.213, 1.207, 1.199, 1.191, 1.179, 1.172, 1.165, 1.156, 1.155, 1.155, + 1.155, 1.152, 1.152, 1.161, 1.163, 1.169, 1.179, 1.186, 1.192, 1.198, 1.204, 1.208, 1.211, 1.211, 1.211, 1.212, 1.212, 1.213, 1.215, 1.215, 1.211, 1.208, 1.205, 1.199, 1.194, 1.185, 1.175, 1.167, 1.161, 1.156, 1.155, 1.153, + 1.157, 1.152, 1.152, 1.159, 1.162, 1.166, 1.174, 1.181, 1.187, 1.192, 1.197, 1.203, 1.204, 1.205, 1.204, 1.204, 1.204, 1.205, 1.206, 1.206, 1.204, 1.201, 1.198, 1.194, 1.187, 1.176, 1.171, 1.164, 1.159, 1.156, 1.155, 1.154, + 1.159, 1.154, 1.154, 1.158, 1.159, 1.163, 1.171, 1.176, 1.181, 1.187, 1.191, 1.195, 1.198, 1.199, 1.199, 1.201, 1.201, 1.202, 1.202, 1.199, 1.196, 1.193, 1.191, 1.188, 1.182, 1.174, 1.166, 1.162, 1.157, 1.156, 1.156, 1.156, + 1.162, 1.161, 1.158, 1.159, 1.159, 1.162, 1.167, 1.173, 1.178, 1.181, 1.186, 1.189, 1.189, 1.191, 1.193, 1.193, 1.193, 1.194, 1.194, 1.194, 1.189, 1.187, 1.186, 1.182, 1.176, 1.167, 1.163, 1.159, 1.158, 1.157, 1.158, 1.161, + 1.172, 1.165, 1.162, 1.162, 1.163, 1.166, 1.169, 1.173, 1.178, 1.181, 1.182, 1.185, 1.186, 1.186, 1.186, 1.187, 1.187, 1.189, 1.192, 1.191, 1.187, 1.185, 1.181, 1.177, 1.172, 1.167, 1.163, 1.159, 1.159, 1.161, 1.163, 1.166, + 1.173, 1.172, 1.166, 1.165, 1.166, 1.168, 1.171, 1.176, 1.179, 1.182, 1.181, 1.183, 1.185, 1.185, 1.185, 1.185, 1.185, 1.185, 1.191, 1.191, 1.185, 1.181, 1.179, 1.173, 1.169, 1.168, 1.163, 1.162, 1.161, 1.164, 1.166, 1.167 + ] + } + ], + "luminance_lut": + [ + 2.271, 2.218, 2.105, 2.004, 1.909, 1.829, 1.762, 1.705, 1.665, 1.629, 1.592, 1.559, 1.528, 1.516, 1.511, 1.511, 1.511, 1.514, 1.525, 1.553, 1.585, 1.617, 1.655, 1.697, 1.752, 1.816, 1.893, 1.982, 2.084, 2.195, 2.321, 2.342, + 2.218, 2.166, 2.057, 1.959, 1.871, 1.793, 1.726, 1.675, 1.633, 1.592, 1.559, 1.528, 1.503, 1.484, 1.474, 1.472, 1.472, 1.482, 1.499, 1.523, 1.553, 1.585, 1.619, 1.664, 1.715, 1.779, 1.855, 1.938, 2.037, 2.147, 2.259, 2.321, + 2.166, 2.101, 1.997, 1.901, 1.818, 1.743, 1.683, 1.634, 1.588, 1.546, 1.508, 1.476, 1.449, 1.429, 1.418, 1.415, 1.415, 1.425, 1.444, 1.469, 1.501, 1.538, 1.577, 1.622, 1.671, 1.728, 1.799, 1.881, 1.975, 2.078, 2.185, 2.259, + 2.101, 2.039, 1.938, 1.848, 1.768, 1.699, 1.641, 1.588, 1.541, 1.494, 1.455, 1.421, 1.394, 1.374, 1.361, 1.357, 1.357, 1.367, 1.388, 1.414, 1.448, 1.485, 1.528, 1.577, 1.626, 1.682, 1.748, 1.827, 1.917, 2.014, 2.119, 2.185, + 2.039, 1.979, 1.883, 1.795, 1.722, 1.658, 1.596, 1.541, 1.493, 1.443, 1.401, 1.364, 1.336, 1.316, 1.303, 1.301, 1.301, 1.311, 1.331, 1.359, 1.393, 1.432, 1.482, 1.528, 1.582, 1.641, 1.701, 1.775, 1.861, 1.956, 2.056, 2.119, + 1.979, 1.932, 1.836, 1.752, 1.685, 1.621, 1.557, 1.497, 1.443, 1.399, 1.351, 1.314, 1.286, 1.264, 1.253, 1.249, 1.249, 1.259, 1.281, 1.311, 1.344, 1.387, 1.432, 1.484, 1.541, 1.601, 1.662, 1.731, 1.816, 1.908, 2.003, 2.056, + 1.934, 1.888, 1.798, 1.719, 1.651, 1.584, 1.519, 1.457, 1.401, 1.351, 1.307, 1.268, 1.239, 1.217, 1.206, 1.203, 1.203, 1.212, 1.234, 1.263, 1.298, 1.344, 1.387, 1.442, 1.502, 1.565, 1.628, 1.693, 1.774, 1.864, 1.956, 2.003, + 1.901, 1.851, 1.763, 1.688, 1.618, 1.551, 1.483, 1.419, 1.359, 1.307, 1.268, 1.226, 1.195, 1.175, 1.164, 1.161, 1.161, 1.171, 1.192, 1.221, 1.262, 1.298, 1.346, 1.404, 1.466, 1.532, 1.595, 1.661, 1.738, 1.826, 1.917, 1.956, + 1.873, 1.821, 1.734, 1.659, 1.591, 1.519, 1.451, 1.386, 1.324, 1.269, 1.226, 1.192, 1.159, 1.141, 1.127, 1.125, 1.125, 1.135, 1.155, 1.187, 1.221, 1.262, 1.311, 1.368, 1.432, 1.499, 1.566, 1.634, 1.708, 1.793, 1.882, 1.917, + 1.847, 1.797, 1.713, 1.639, 1.565, 1.493, 1.422, 1.355, 1.291, 1.238, 1.192, 1.159, 1.128, 1.108, 1.097, 1.094, 1.094, 1.104, 1.125, 1.155, 1.187, 1.229, 1.279, 1.338, 1.403, 1.471, 1.541, 1.611, 1.684, 1.766, 1.853, 1.885, + 1.828, 1.772, 1.691, 1.614, 1.539, 1.466, 1.394, 1.325, 1.264, 1.209, 1.163, 1.128, 1.104, 1.081, 1.069, 1.067, 1.067, 1.078, 1.101, 1.125, 1.159, 1.201, 1.252, 1.312, 1.379, 1.447, 1.517, 1.591, 1.665, 1.743, 1.831, 1.862, + 1.812, 1.754, 1.677, 1.599, 1.519, 1.445, 1.371, 1.302, 1.239, 1.185, 1.139, 1.104, 1.081, 1.061, 1.048, 1.046, 1.046, 1.058, 1.078, 1.102, 1.136, 1.177, 1.229, 1.289, 1.356, 1.425, 1.497, 1.572, 1.647, 1.724, 1.811, 1.847, + 1.798, 1.741, 1.663, 1.585, 1.506, 1.429, 1.353, 1.284, 1.221, 1.167, 1.121, 1.086, 1.061, 1.046, 1.031, 1.029, 1.029, 1.044, 1.058, 1.083, 1.116, 1.159, 1.209, 1.271, 1.338, 1.407, 1.479, 1.557, 1.633, 1.709, 1.792, 1.832, + 1.792, 1.727, 1.651, 1.572, 1.494, 1.414, 1.339, 1.269, 1.206, 1.152, 1.106, 1.072, 1.046, 1.031, 1.018, 1.016, 1.016, 1.029, 1.044, 1.069, 1.102, 1.145, 1.196, 1.256, 1.324, 1.394, 1.471, 1.545, 1.624, 1.698, 1.782, 1.825, + 1.787, 1.724, 1.647, 1.566, 1.484, 1.407, 1.329, 1.258, 1.196, 1.141, 1.097, 1.062, 1.036, 1.018, 1.012, 1.007, 1.011, 1.016, 1.034, 1.059, 1.093, 1.135, 1.186, 1.246, 1.314, 1.386, 1.461, 1.538, 1.616, 1.691, 1.773, 1.818, + 1.786, 1.721, 1.642, 1.562, 1.481, 1.402, 1.325, 1.254, 1.191, 1.137, 1.092, 1.057, 1.031, 1.013, 1.004, 1.001, 1.004, 1.011, 1.028, 1.054, 1.088, 1.129, 1.181, 1.241, 1.308, 1.382, 1.458, 1.535, 1.613, 1.687, 1.769, 1.818, + 1.786, 1.721, 1.642, 1.562, 1.481, 1.401, 1.325, 1.253, 1.191, 1.136, 1.091, 1.057, 1.031, 1.013, 1.003, 1.001, 1.001, 1.011, 1.028, 1.054, 1.088, 1.129, 1.181, 1.241, 1.308, 1.382, 1.458, 1.535, 1.613, 1.687, 1.769, 1.818, + 1.787, 1.722, 1.643, 1.563, 1.482, 1.402, 1.326, 1.254, 1.192, 1.138, 1.092, 1.057, 1.032, 1.013, 1.006, 1.002, 1.006, 1.012, 1.031, 1.057, 1.092, 1.133, 1.185, 1.243, 1.311, 1.385, 1.461, 1.539, 1.618, 1.691, 1.774, 1.821, + 1.789, 1.729, 1.651, 1.571, 1.489, 1.411, 1.334, 1.263, 1.201, 1.147, 1.101, 1.065, 1.038, 1.021, 1.013, 1.009, 1.012, 1.021, 1.038, 1.064, 1.098, 1.141, 1.193, 1.254, 1.321, 1.395, 1.472, 1.549, 1.626, 1.701, 1.785, 1.825, + 1.799, 1.739, 1.661, 1.581, 1.502, 1.422, 1.347, 1.277, 1.214, 1.159, 1.111, 1.075, 1.049, 1.037, 1.021, 1.019, 1.021, 1.036, 1.049, 1.076, 1.111, 1.154, 1.207, 1.268, 1.334, 1.408, 1.485, 1.562, 1.639, 1.715, 1.799, 1.837, + 1.811, 1.755, 1.676, 1.597, 1.518, 1.439, 1.365, 1.295, 1.231, 1.176, 1.129, 1.093, 1.067, 1.049, 1.038, 1.036, 1.036, 1.049, 1.067, 1.094, 1.129, 1.173, 1.225, 1.286, 1.353, 1.425, 1.501, 1.577, 1.653, 1.729, 1.815, 1.851, + 1.829, 1.774, 1.693, 1.615, 1.537, 1.462, 1.387, 1.316, 1.253, 1.198, 1.153, 1.115, 1.091, 1.067, 1.059, 1.056, 1.056, 1.067, 1.092, 1.115, 1.151, 1.196, 1.249, 1.309, 1.375, 1.446, 1.522, 1.595, 1.672, 1.752, 1.839, 1.871, + 1.851, 1.801, 1.713, 1.636, 1.561, 1.485, 1.411, 1.342, 1.281, 1.226, 1.179, 1.145, 1.115, 1.091, 1.082, 1.081, 1.082, 1.092, 1.115, 1.143, 1.178, 1.223, 1.276, 1.337, 1.402, 1.472, 1.544, 1.618, 1.691, 1.774, 1.865, 1.896, + 1.876, 1.831, 1.739, 1.663, 1.588, 1.513, 1.439, 1.374, 1.312, 1.258, 1.212, 1.179, 1.145, 1.123, 1.113, 1.112, 1.112, 1.122, 1.143, 1.177, 1.211, 1.256, 1.308, 1.368, 1.431, 1.501, 1.572, 1.641, 1.716, 1.802, 1.896, 1.931, + 1.909, 1.867, 1.771, 1.691, 1.617, 1.545, 1.474, 1.411, 1.349, 1.296, 1.252, 1.212, 1.182, 1.159, 1.149, 1.148, 1.149, 1.158, 1.179, 1.211, 1.253, 1.293, 1.344, 1.403, 1.465, 1.533, 1.603, 1.669, 1.747, 1.836, 1.931, 1.974, + 1.952, 1.905, 1.806, 1.722, 1.651, 1.578, 1.511, 1.448, 1.388, 1.338, 1.296, 1.252, 1.223, 1.201, 1.189, 1.189, 1.189, 1.199, 1.224, 1.253, 1.293, 1.338, 1.384, 1.442, 1.504, 1.571, 1.638, 1.704, 1.782, 1.872, 1.974, 2.025, + 2.004, 1.951, 1.849, 1.759, 1.688, 1.619, 1.552, 1.491, 1.435, 1.388, 1.338, 1.301, 1.272, 1.249, 1.238, 1.236, 1.236, 1.248, 1.271, 1.301, 1.338, 1.384, 1.431, 1.484, 1.543, 1.609, 1.675, 1.742, 1.825, 1.919, 2.025, 2.081, + 2.062, 2.004, 1.898, 1.805, 1.729, 1.661, 1.597, 1.539, 1.486, 1.435, 1.391, 1.354, 1.326, 1.303, 1.291, 1.289, 1.289, 1.301, 1.323, 1.353, 1.389, 1.431, 1.483, 1.528, 1.585, 1.649, 1.713, 1.787, 1.875, 1.971, 2.081, 2.145, + 2.129, 2.062, 1.951, 1.854, 1.774, 1.705, 1.642, 1.586, 1.539, 1.486, 1.445, 1.411, 1.383, 1.361, 1.348, 1.347, 1.348, 1.359, 1.379, 1.409, 1.447, 1.484, 1.528, 1.578, 1.631, 1.691, 1.759, 1.836, 1.928, 2.031, 2.145, 2.217, + 2.201, 2.129, 2.013, 1.912, 1.827, 1.752, 1.689, 1.642, 1.586, 1.544, 1.501, 1.468, 1.442, 1.421, 1.409, 1.409, 1.411, 1.421, 1.439, 1.467, 1.504, 1.543, 1.578, 1.629, 1.679, 1.739, 1.815, 1.894, 1.985, 2.098, 2.217, 2.298, + 2.273, 2.201, 2.081, 1.974, 1.886, 1.807, 1.741, 1.689, 1.643, 1.603, 1.562, 1.527, 1.504, 1.485, 1.475, 1.474, 1.475, 1.487, 1.503, 1.531, 1.565, 1.601, 1.634, 1.678, 1.728, 1.795, 1.877, 1.961, 2.052, 2.169, 2.298, 2.365, + 2.317, 2.273, 2.146, 2.039, 1.946, 1.864, 1.792, 1.737, 1.688, 1.643, 1.603, 1.562, 1.533, 1.525, 1.523, 1.523, 1.523, 1.525, 1.534, 1.565, 1.601, 1.634, 1.677, 1.722, 1.772, 1.848, 1.935, 2.023, 2.108, 2.232, 2.365, 2.403 + ], + "sigma": 0.00285, + "sigma_Cb": 0.00166 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2873, + "ccm": + [ + 1.88195, -0.26249, -0.61946, + -0.63842, 2.11535, -0.47693, + -0.13531, -0.99739, 2.13271 + ] + }, + { + "ct": 2965, + "ccm": + [ + 2.15048, -0.51859, -0.63189, + -0.53572, 1.92585, -0.39013, + 0.01831, -1.48576, 2.46744 + ] + }, + { + "ct": 3606, + "ccm": + [ + 1.97522, -0.43847, -0.53675, + -0.56151, 1.99765, -0.43614, + -0.12438, -0.77056, 1.89493 + ] + }, + { + "ct": 4700, + "ccm": + [ + 2.00971, -0.51461, -0.49511, + -0.52109, 2.01003, -0.48894, + -0.09527, -0.67318, 1.76845 + ] + }, + { + "ct": 5890, + "ccm": + [ + 2.13616, -0.65283, -0.48333, + -0.48364, 1.93115, -0.44751, + -0.13465, -0.54831, 1.68295 + ] + }, + { + "ct": 7600, + "ccm": + [ + 2.06599, -0.39161, -0.67439, + -0.50883, 2.27467, -0.76583, + -0.13961, -0.66121, 1.80081 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/ov5647_noir.json b/src/ipa/rpi/pisp/data/ov5647_noir.json new file mode 100644 index 00000000..3e04f21b --- /dev/null +++ b/src/ipa/rpi/pisp/data/ov5647_noir.json @@ -0,0 +1,1121 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 1024 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 29381, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 870, + "reference_Y": 12388 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 4.371 + } + }, + { + "rpi.geq": + { + "offset": 280, + "slope": 0.02153 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 1.0, 2.0 ] + }, + "short": + { + "shutter": [ 100, 15000, 30000 ], + "gain": [ 1.0, 2.0, 2.0 ] + }, + "long": + { + "shutter": [ 100, 15000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.02, + 1000, 0.02 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.01, + 1000, 0.01 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.9, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.005, + 1000, 0.005 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.19, + 1000, 0.19, + 10000, 0.19 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.238, 1.238, 1.238, 1.234, 1.227, 1.216, 1.207, 1.198, 1.191, 1.179, 1.169, 1.162, 1.155, 1.153, 1.152, 1.152, 1.152, 1.153, 1.154, 1.157, 1.166, 1.176, 1.183, 1.191, 1.204, 1.216, 1.226, 1.232, 1.239, 1.241, 1.241, 1.242, + 1.235, 1.234, 1.227, 1.222, 1.214, 1.203, 1.193, 1.184, 1.169, 1.161, 1.149, 1.139, 1.131, 1.126, 1.122, 1.121, 1.121, 1.123, 1.129, 1.136, 1.145, 1.157, 1.163, 1.175, 1.189, 1.199, 1.212, 1.221, 1.225, 1.231, 1.241, 1.242, + 1.234, 1.227, 1.222, 1.214, 1.203, 1.193, 1.183, 1.169, 1.158, 1.145, 1.133, 1.123, 1.113, 1.106, 1.101, 1.101, 1.101, 1.105, 1.111, 1.116, 1.128, 1.137, 1.149, 1.163, 1.174, 1.189, 1.199, 1.212, 1.221, 1.226, 1.234, 1.241, + 1.234, 1.226, 1.217, 1.209, 1.195, 1.183, 1.171, 1.158, 1.145, 1.131, 1.119, 1.108, 1.097, 1.088, 1.088, 1.085, 1.085, 1.087, 1.095, 1.102, 1.114, 1.124, 1.137, 1.149, 1.165, 1.176, 1.194, 1.207, 1.214, 1.224, 1.235, 1.247, + 1.238, 1.224, 1.213, 1.202, 1.187, 1.175, 1.161, 1.146, 1.132, 1.117, 1.105, 1.094, 1.082, 1.074, 1.071, 1.071, 1.071, 1.073, 1.079, 1.089, 1.099, 1.112, 1.124, 1.137, 1.152, 1.167, 1.183, 1.198, 1.211, 1.222, 1.235, 1.249, + 1.232, 1.221, 1.209, 1.195, 1.178, 1.163, 1.149, 1.134, 1.118, 1.104, 1.093, 1.079, 1.069, 1.061, 1.057, 1.056, 1.056, 1.059, 1.066, 1.073, 1.086, 1.098, 1.111, 1.124, 1.141, 1.157, 1.173, 1.188, 1.203, 1.219, 1.234, 1.251, + 1.231, 1.213, 1.197, 1.186, 1.169, 1.151, 1.137, 1.121, 1.104, 1.093, 1.079, 1.068, 1.056, 1.048, 1.045, 1.042, 1.042, 1.045, 1.051, 1.061, 1.071, 1.085, 1.098, 1.111, 1.129, 1.145, 1.161, 1.179, 1.197, 1.215, 1.231, 1.249, + 1.224, 1.211, 1.194, 1.178, 1.161, 1.141, 1.127, 1.109, 1.094, 1.081, 1.068, 1.055, 1.047, 1.038, 1.034, 1.032, 1.032, 1.035, 1.039, 1.048, 1.059, 1.071, 1.086, 1.098, 1.116, 1.134, 1.154, 1.172, 1.191, 1.209, 1.228, 1.249, + 1.223, 1.206, 1.187, 1.171, 1.152, 1.132, 1.117, 1.098, 1.082, 1.069, 1.056, 1.045, 1.037, 1.028, 1.024, 1.022, 1.022, 1.025, 1.031, 1.039, 1.048, 1.059, 1.074, 1.091, 1.106, 1.126, 1.144, 1.163, 1.186, 1.205, 1.227, 1.247, + 1.222, 1.199, 1.183, 1.164, 1.143, 1.126, 1.108, 1.091, 1.075, 1.059, 1.045, 1.037, 1.028, 1.019, 1.015, 1.014, 1.014, 1.018, 1.023, 1.031, 1.042, 1.051, 1.065, 1.081, 1.098, 1.118, 1.137, 1.158, 1.181, 1.201, 1.224, 1.245, + 1.221, 1.198, 1.179, 1.163, 1.141, 1.119, 1.101, 1.083, 1.066, 1.051, 1.038, 1.028, 1.019, 1.012, 1.009, 1.008, 1.007, 1.008, 1.015, 1.023, 1.033, 1.044, 1.058, 1.072, 1.089, 1.107, 1.131, 1.152, 1.172, 1.196, 1.216, 1.241, + 1.216, 1.194, 1.174, 1.155, 1.133, 1.112, 1.094, 1.074, 1.059, 1.045, 1.032, 1.021, 1.012, 1.007, 1.003, 1.002, 1.002, 1.003, 1.008, 1.015, 1.025, 1.038, 1.049, 1.067, 1.084, 1.102, 1.126, 1.147, 1.169, 1.191, 1.214, 1.238, + 1.212, 1.188, 1.171, 1.149, 1.127, 1.105, 1.087, 1.069, 1.055, 1.039, 1.027, 1.016, 1.007, 1.003, 0.999, 0.997, 0.998, 1.001, 1.003, 1.011, 1.021, 1.032, 1.043, 1.059, 1.077, 1.101, 1.121, 1.142, 1.164, 1.187, 1.211, 1.236, + 1.208, 1.187, 1.169, 1.149, 1.124, 1.104, 1.085, 1.067, 1.051, 1.036, 1.024, 1.013, 1.005, 0.999, 0.996, 0.994, 0.994, 0.996, 1.001, 1.006, 1.017, 1.025, 1.038, 1.053, 1.072, 1.093, 1.116, 1.138, 1.159, 1.183, 1.207, 1.235, + 1.208, 1.181, 1.164, 1.144, 1.122, 1.098, 1.079, 1.062, 1.046, 1.033, 1.018, 1.009, 1.002, 0.996, 0.992, 0.989, 0.991, 0.994, 0.996, 1.002, 1.012, 1.021, 1.035, 1.051, 1.069, 1.091, 1.113, 1.137, 1.157, 1.182, 1.206, 1.233, + 1.206, 1.179, 1.163, 1.142, 1.119, 1.098, 1.079, 1.061, 1.045, 1.031, 1.017, 1.008, 1.001, 0.995, 0.991, 0.989, 0.989, 0.992, 0.996, 1.001, 1.011, 1.019, 1.034, 1.051, 1.069, 1.089, 1.112, 1.136, 1.157, 1.181, 1.205, 1.233, + 1.206, 1.179, 1.163, 1.139, 1.119, 1.098, 1.079, 1.061, 1.044, 1.031, 1.016, 1.007, 1.001, 0.995, 0.991, 0.989, 0.989, 0.991, 0.996, 1.002, 1.011, 1.019, 1.034, 1.049, 1.069, 1.088, 1.113, 1.136, 1.156, 1.179, 1.204, 1.233, + 1.207, 1.179, 1.163, 1.139, 1.119, 1.099, 1.079, 1.061, 1.044, 1.031, 1.017, 1.007, 1.001, 0.995, 0.991, 0.989, 0.989, 0.992, 0.997, 1.003, 1.011, 1.021, 1.034, 1.051, 1.071, 1.089, 1.112, 1.136, 1.157, 1.179, 1.204, 1.233, + 1.207, 1.179, 1.163, 1.143, 1.121, 1.101, 1.082, 1.063, 1.047, 1.032, 1.019, 1.009, 1.003, 0.998, 0.994, 0.991, 0.991, 0.995, 0.999, 1.004, 1.013, 1.024, 1.038, 1.052, 1.071, 1.091, 1.112, 1.136, 1.159, 1.181, 1.205, 1.233, + 1.207, 1.185, 1.166, 1.148, 1.124, 1.104, 1.087, 1.068, 1.052, 1.037, 1.025, 1.016, 1.006, 1.002, 0.998, 0.995, 0.995, 0.999, 1.003, 1.008, 1.017, 1.029, 1.043, 1.056, 1.076, 1.094, 1.116, 1.138, 1.159, 1.183, 1.205, 1.232, + 1.211, 1.186, 1.167, 1.151, 1.128, 1.108, 1.089, 1.072, 1.057, 1.042, 1.031, 1.021, 1.013, 1.006, 1.002, 0.999, 0.999, 1.003, 1.007, 1.013, 1.021, 1.031, 1.047, 1.062, 1.081, 1.098, 1.121, 1.141, 1.164, 1.185, 1.207, 1.232, + 1.211, 1.188, 1.169, 1.154, 1.134, 1.114, 1.094, 1.078, 1.063, 1.051, 1.039, 1.028, 1.019, 1.013, 1.007, 1.006, 1.006, 1.007, 1.013, 1.019, 1.027, 1.039, 1.051, 1.069, 1.087, 1.105, 1.124, 1.146, 1.165, 1.186, 1.209, 1.232, + 1.214, 1.191, 1.175, 1.159, 1.141, 1.123, 1.105, 1.087, 1.072, 1.058, 1.046, 1.036, 1.028, 1.019, 1.014, 1.013, 1.013, 1.015, 1.019, 1.027, 1.037, 1.048, 1.061, 1.076, 1.094, 1.109, 1.132, 1.149, 1.169, 1.189, 1.209, 1.233, + 1.219, 1.194, 1.179, 1.163, 1.146, 1.129, 1.113, 1.095, 1.081, 1.066, 1.055, 1.045, 1.036, 1.029, 1.023, 1.021, 1.021, 1.026, 1.031, 1.037, 1.048, 1.057, 1.069, 1.085, 1.101, 1.118, 1.137, 1.156, 1.174, 1.193, 1.213, 1.233, + 1.219, 1.199, 1.184, 1.172, 1.155, 1.138, 1.122, 1.104, 1.088, 1.075, 1.065, 1.055, 1.045, 1.038, 1.034, 1.031, 1.031, 1.035, 1.041, 1.048, 1.057, 1.066, 1.081, 1.096, 1.111, 1.125, 1.146, 1.164, 1.178, 1.196, 1.214, 1.233, + 1.222, 1.204, 1.189, 1.178, 1.162, 1.148, 1.132, 1.115, 1.101, 1.087, 1.075, 1.064, 1.055, 1.048, 1.043, 1.042, 1.042, 1.046, 1.049, 1.057, 1.066, 1.076, 1.089, 1.106, 1.121, 1.133, 1.149, 1.167, 1.183, 1.199, 1.215, 1.234, + 1.222, 1.205, 1.191, 1.184, 1.171, 1.155, 1.142, 1.124, 1.109, 1.097, 1.087, 1.077, 1.065, 1.059, 1.055, 1.053, 1.053, 1.057, 1.059, 1.067, 1.076, 1.088, 1.102, 1.116, 1.131, 1.143, 1.157, 1.175, 1.187, 1.202, 1.215, 1.231, + 1.223, 1.211, 1.198, 1.189, 1.178, 1.165, 1.151, 1.136, 1.122, 1.108, 1.097, 1.087, 1.079, 1.073, 1.067, 1.066, 1.066, 1.069, 1.074, 1.079, 1.088, 1.101, 1.114, 1.128, 1.141, 1.152, 1.166, 1.182, 1.194, 1.205, 1.215, 1.229, + 1.223, 1.212, 1.204, 1.197, 1.186, 1.173, 1.161, 1.149, 1.133, 1.121, 1.108, 1.101, 1.092, 1.085, 1.082, 1.082, 1.082, 1.085, 1.091, 1.096, 1.101, 1.113, 1.125, 1.138, 1.151, 1.164, 1.175, 1.188, 1.198, 1.207, 1.215, 1.222, + 1.217, 1.213, 1.211, 1.203, 1.194, 1.181, 1.169, 1.158, 1.145, 1.133, 1.123, 1.113, 1.106, 1.097, 1.096, 1.094, 1.094, 1.098, 1.104, 1.108, 1.114, 1.124, 1.137, 1.149, 1.161, 1.172, 1.182, 1.194, 1.203, 1.209, 1.211, 1.217, + 1.214, 1.211, 1.209, 1.206, 1.201, 1.188, 1.179, 1.168, 1.154, 1.144, 1.136, 1.126, 1.119, 1.112, 1.109, 1.108, 1.108, 1.108, 1.117, 1.119, 1.124, 1.133, 1.147, 1.158, 1.171, 1.178, 1.188, 1.198, 1.205, 1.208, 1.209, 1.211, + 1.207, 1.208, 1.209, 1.206, 1.202, 1.192, 1.182, 1.171, 1.159, 1.146, 1.142, 1.136, 1.126, 1.119, 1.116, 1.114, 1.115, 1.117, 1.119, 1.128, 1.129, 1.136, 1.155, 1.162, 1.176, 1.182, 1.188, 1.198, 1.205, 1.208, 1.207, 1.206 + ] + }, + { + "ct": 5000, + "table": + [ + 1.879, 1.878, 1.872, 1.862, 1.856, 1.842, 1.826, 1.815, 1.811, 1.799, 1.787, 1.777, 1.768, 1.761, 1.761, 1.761, 1.762, 1.763, 1.764, 1.769, 1.776, 1.789, 1.799, 1.807, 1.824, 1.841, 1.853, 1.861, 1.871, 1.874, 1.885, 1.889, + 1.879, 1.875, 1.859, 1.846, 1.835, 1.817, 1.806, 1.794, 1.777, 1.771, 1.755, 1.743, 1.733, 1.726, 1.721, 1.721, 1.721, 1.722, 1.729, 1.734, 1.747, 1.759, 1.771, 1.783, 1.801, 1.813, 1.831, 1.841, 1.849, 1.862, 1.876, 1.888, + 1.876, 1.861, 1.846, 1.835, 1.817, 1.806, 1.793, 1.777, 1.766, 1.752, 1.736, 1.727, 1.713, 1.702, 1.696, 1.695, 1.695, 1.697, 1.704, 1.715, 1.725, 1.739, 1.754, 1.771, 1.783, 1.801, 1.813, 1.831, 1.841, 1.851, 1.866, 1.888, + 1.878, 1.861, 1.843, 1.829, 1.811, 1.794, 1.779, 1.766, 1.751, 1.734, 1.721, 1.711, 1.695, 1.682, 1.679, 1.677, 1.677, 1.678, 1.687, 1.696, 1.713, 1.723, 1.737, 1.754, 1.774, 1.785, 1.811, 1.825, 1.833, 1.849, 1.866, 1.889, + 1.882, 1.859, 1.837, 1.821, 1.803, 1.784, 1.769, 1.752, 1.735, 1.717, 1.701, 1.689, 1.676, 1.664, 1.659, 1.658, 1.658, 1.659, 1.668, 1.679, 1.694, 1.711, 1.723, 1.739, 1.756, 1.777, 1.797, 1.813, 1.827, 1.844, 1.865, 1.889, + 1.869, 1.849, 1.832, 1.811, 1.792, 1.772, 1.755, 1.737, 1.717, 1.699, 1.688, 1.674, 1.661, 1.646, 1.642, 1.638, 1.638, 1.641, 1.651, 1.659, 1.676, 1.693, 1.708, 1.724, 1.744, 1.763, 1.783, 1.801, 1.819, 1.838, 1.864, 1.889, + 1.869, 1.841, 1.817, 1.801, 1.782, 1.758, 1.741, 1.721, 1.699, 1.688, 1.674, 1.658, 1.643, 1.632, 1.627, 1.621, 1.621, 1.622, 1.631, 1.643, 1.658, 1.676, 1.689, 1.708, 1.729, 1.748, 1.767, 1.791, 1.812, 1.836, 1.859, 1.891, + 1.861, 1.836, 1.814, 1.792, 1.772, 1.745, 1.728, 1.707, 1.688, 1.673, 1.658, 1.643, 1.629, 1.618, 1.612, 1.609, 1.609, 1.611, 1.615, 1.629, 1.642, 1.658, 1.676, 1.689, 1.711, 1.734, 1.758, 1.782, 1.804, 1.827, 1.859, 1.891, + 1.861, 1.829, 1.807, 1.784, 1.759, 1.735, 1.717, 1.692, 1.674, 1.659, 1.644, 1.629, 1.617, 1.605, 1.598, 1.595, 1.595, 1.598, 1.607, 1.615, 1.631, 1.642, 1.661, 1.681, 1.701, 1.724, 1.746, 1.771, 1.799, 1.825, 1.857, 1.891, + 1.861, 1.826, 1.804, 1.779, 1.749, 1.729, 1.707, 1.687, 1.665, 1.648, 1.629, 1.617, 1.604, 1.595, 1.589, 1.585, 1.585, 1.592, 1.597, 1.607, 1.623, 1.635, 1.652, 1.674, 1.693, 1.716, 1.739, 1.766, 1.794, 1.822, 1.855, 1.889, + 1.861, 1.824, 1.799, 1.777, 1.748, 1.723, 1.701, 1.678, 1.657, 1.639, 1.619, 1.605, 1.596, 1.586, 1.581, 1.579, 1.577, 1.579, 1.588, 1.597, 1.612, 1.625, 1.641, 1.661, 1.681, 1.702, 1.732, 1.757, 1.785, 1.813, 1.847, 1.882, + 1.856, 1.819, 1.796, 1.767, 1.739, 1.714, 1.693, 1.666, 1.651, 1.629, 1.613, 1.597, 1.586, 1.579, 1.576, 1.572, 1.572, 1.573, 1.579, 1.588, 1.602, 1.619, 1.633, 1.655, 1.674, 1.698, 1.729, 1.754, 1.782, 1.809, 1.842, 1.874, + 1.853, 1.815, 1.792, 1.761, 1.734, 1.707, 1.682, 1.659, 1.639, 1.622, 1.605, 1.591, 1.579, 1.574, 1.569, 1.565, 1.566, 1.569, 1.573, 1.584, 1.597, 1.609, 1.624, 1.645, 1.666, 1.695, 1.722, 1.746, 1.772, 1.799, 1.835, 1.873, + 1.847, 1.811, 1.789, 1.759, 1.732, 1.703, 1.681, 1.657, 1.637, 1.619, 1.603, 1.588, 1.575, 1.569, 1.563, 1.561, 1.561, 1.563, 1.569, 1.576, 1.589, 1.601, 1.616, 1.636, 1.659, 1.686, 1.712, 1.741, 1.767, 1.798, 1.832, 1.873, + 1.847, 1.803, 1.779, 1.756, 1.727, 1.699, 1.674, 1.652, 1.632, 1.616, 1.595, 1.583, 1.572, 1.564, 1.558, 1.556, 1.557, 1.559, 1.563, 1.569, 1.583, 1.593, 1.613, 1.633, 1.657, 1.684, 1.709, 1.741, 1.766, 1.796, 1.831, 1.871, + 1.845, 1.802, 1.779, 1.755, 1.725, 1.696, 1.673, 1.649, 1.629, 1.614, 1.595, 1.582, 1.572, 1.563, 1.557, 1.556, 1.556, 1.558, 1.562, 1.569, 1.581, 1.593, 1.612, 1.633, 1.656, 1.679, 1.709, 1.741, 1.764, 1.796, 1.828, 1.869, + 1.845, 1.801, 1.779, 1.749, 1.723, 1.697, 1.673, 1.649, 1.627, 1.613, 1.593, 1.581, 1.573, 1.563, 1.558, 1.555, 1.555, 1.556, 1.562, 1.573, 1.581, 1.594, 1.611, 1.633, 1.656, 1.679, 1.711, 1.739, 1.764, 1.794, 1.828, 1.869, + 1.844, 1.801, 1.781, 1.749, 1.723, 1.697, 1.673, 1.649, 1.627, 1.614, 1.595, 1.581, 1.574, 1.564, 1.559, 1.557, 1.556, 1.559, 1.564, 1.574, 1.582, 1.595, 1.611, 1.634, 1.659, 1.683, 1.709, 1.739, 1.765, 1.794, 1.829, 1.872, + 1.845, 1.802, 1.781, 1.754, 1.725, 1.701, 1.677, 1.652, 1.632, 1.616, 1.599, 1.586, 1.576, 1.569, 1.563, 1.559, 1.558, 1.562, 1.569, 1.576, 1.587, 1.599, 1.618, 1.635, 1.661, 1.685, 1.709, 1.739, 1.767, 1.796, 1.829, 1.868, + 1.845, 1.809, 1.785, 1.762, 1.731, 1.706, 1.685, 1.659, 1.641, 1.622, 1.606, 1.595, 1.581, 1.575, 1.569, 1.564, 1.564, 1.569, 1.574, 1.582, 1.594, 1.607, 1.625, 1.642, 1.668, 1.687, 1.716, 1.741, 1.769, 1.798, 1.829, 1.868, + 1.849, 1.811, 1.785, 1.765, 1.734, 1.709, 1.688, 1.666, 1.647, 1.628, 1.613, 1.601, 1.592, 1.581, 1.575, 1.572, 1.572, 1.575, 1.581, 1.589, 1.599, 1.611, 1.631, 1.649, 1.673, 1.694, 1.721, 1.747, 1.771, 1.798, 1.829, 1.868, + 1.849, 1.816, 1.787, 1.766, 1.739, 1.716, 1.692, 1.673, 1.657, 1.641, 1.625, 1.612, 1.599, 1.592, 1.584, 1.581, 1.581, 1.581, 1.589, 1.598, 1.608, 1.622, 1.639, 1.659, 1.679, 1.701, 1.724, 1.751, 1.774, 1.802, 1.832, 1.868, + 1.855, 1.816, 1.793, 1.773, 1.748, 1.727, 1.707, 1.686, 1.667, 1.649, 1.636, 1.623, 1.612, 1.599, 1.594, 1.592, 1.591, 1.591, 1.598, 1.608, 1.621, 1.634, 1.649, 1.669, 1.693, 1.705, 1.736, 1.757, 1.778, 1.804, 1.833, 1.867, + 1.858, 1.818, 1.796, 1.778, 1.754, 1.733, 1.716, 1.695, 1.676, 1.661, 1.648, 1.635, 1.624, 1.613, 1.604, 1.601, 1.601, 1.606, 1.613, 1.621, 1.634, 1.646, 1.661, 1.679, 1.699, 1.714, 1.742, 1.761, 1.782, 1.809, 1.835, 1.867, + 1.857, 1.822, 1.801, 1.789, 1.766, 1.744, 1.726, 1.706, 1.688, 1.671, 1.659, 1.647, 1.635, 1.624, 1.621, 1.617, 1.617, 1.621, 1.627, 1.634, 1.645, 1.656, 1.674, 1.694, 1.709, 1.723, 1.751, 1.771, 1.786, 1.811, 1.837, 1.867, + 1.858, 1.824, 1.807, 1.794, 1.774, 1.757, 1.739, 1.716, 1.702, 1.687, 1.671, 1.662, 1.648, 1.636, 1.629, 1.629, 1.629, 1.633, 1.635, 1.646, 1.656, 1.669, 1.684, 1.705, 1.719, 1.732, 1.753, 1.774, 1.793, 1.815, 1.837, 1.871, + 1.858, 1.827, 1.809, 1.798, 1.782, 1.761, 1.749, 1.727, 1.711, 1.698, 1.687, 1.675, 1.663, 1.649, 1.646, 1.643, 1.643, 1.646, 1.649, 1.658, 1.669, 1.683, 1.698, 1.716, 1.731, 1.746, 1.761, 1.783, 1.795, 1.817, 1.836, 1.862, + 1.862, 1.834, 1.816, 1.805, 1.789, 1.774, 1.759, 1.743, 1.725, 1.711, 1.697, 1.688, 1.678, 1.668, 1.661, 1.659, 1.658, 1.659, 1.668, 1.673, 1.684, 1.698, 1.713, 1.728, 1.742, 1.757, 1.771, 1.791, 1.804, 1.821, 1.836, 1.862, + 1.859, 1.835, 1.825, 1.813, 1.794, 1.782, 1.771, 1.757, 1.739, 1.725, 1.711, 1.701, 1.693, 1.683, 1.679, 1.679, 1.679, 1.683, 1.689, 1.693, 1.698, 1.714, 1.726, 1.741, 1.754, 1.769, 1.781, 1.797, 1.808, 1.821, 1.835, 1.856, + 1.848, 1.836, 1.832, 1.822, 1.806, 1.789, 1.778, 1.765, 1.751, 1.739, 1.726, 1.718, 1.709, 1.699, 1.696, 1.695, 1.695, 1.696, 1.704, 1.705, 1.714, 1.724, 1.739, 1.753, 1.765, 1.777, 1.789, 1.803, 1.816, 1.824, 1.829, 1.842, + 1.839, 1.835, 1.834, 1.829, 1.815, 1.801, 1.787, 1.776, 1.759, 1.751, 1.744, 1.736, 1.724, 1.714, 1.711, 1.708, 1.707, 1.707, 1.717, 1.719, 1.724, 1.734, 1.748, 1.762, 1.775, 1.783, 1.796, 1.808, 1.819, 1.825, 1.828, 1.833, + 1.836, 1.833, 1.834, 1.832, 1.821, 1.806, 1.792, 1.785, 1.772, 1.759, 1.751, 1.744, 1.736, 1.725, 1.719, 1.715, 1.715, 1.718, 1.721, 1.728, 1.734, 1.736, 1.757, 1.768, 1.779, 1.787, 1.799, 1.812, 1.821, 1.824, 1.825, 1.833 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 2.189, 2.127, 2.115, 2.106, 2.113, 2.119, 2.131, 2.144, 2.155, 2.168, 2.176, 2.179, 2.181, 2.181, 2.185, 2.187, 2.187, 2.183, 2.179, 2.176, 2.169, 2.167, 2.159, 2.152, 2.145, 2.141, 2.135, 2.128, 2.124, 2.124, 2.139, 2.177, + 2.176, 2.133, 2.116, 2.112, 2.116, 2.125, 2.137, 2.154, 2.168, 2.179, 2.187, 2.194, 2.201, 2.204, 2.208, 2.208, 2.205, 2.202, 2.198, 2.195, 2.183, 2.177, 2.166, 2.159, 2.149, 2.143, 2.138, 2.132, 2.124, 2.125, 2.136, 2.164, + 2.175, 2.133, 2.117, 2.115, 2.121, 2.136, 2.154, 2.165, 2.179, 2.192, 2.198, 2.211, 2.218, 2.219, 2.221, 2.221, 2.217, 2.216, 2.211, 2.202, 2.197, 2.188, 2.181, 2.171, 2.159, 2.151, 2.141, 2.136, 2.125, 2.125, 2.132, 2.155, + 2.172, 2.128, 2.116, 2.116, 2.124, 2.143, 2.161, 2.177, 2.192, 2.204, 2.213, 2.221, 2.227, 2.231, 2.237, 2.237, 2.229, 2.224, 2.221, 2.213, 2.207, 2.197, 2.191, 2.179, 2.169, 2.156, 2.148, 2.138, 2.126, 2.123, 2.124, 2.149, + 2.169, 2.124, 2.119, 2.119, 2.135, 2.152, 2.174, 2.187, 2.204, 2.211, 2.224, 2.233, 2.236, 2.241, 2.246, 2.246, 2.243, 2.237, 2.234, 2.226, 2.218, 2.211, 2.199, 2.191, 2.177, 2.166, 2.155, 2.139, 2.129, 2.121, 2.121, 2.143, + 2.157, 2.124, 2.121, 2.127, 2.145, 2.157, 2.181, 2.197, 2.208, 2.221, 2.238, 2.245, 2.249, 2.249, 2.254, 2.254, 2.249, 2.247, 2.243, 2.237, 2.228, 2.219, 2.209, 2.198, 2.186, 2.172, 2.161, 2.143, 2.129, 2.121, 2.121, 2.141, + 2.157, 2.124, 2.124, 2.131, 2.148, 2.161, 2.188, 2.202, 2.214, 2.238, 2.246, 2.251, 2.255, 2.257, 2.259, 2.259, 2.257, 2.252, 2.251, 2.247, 2.238, 2.231, 2.219, 2.204, 2.193, 2.173, 2.166, 2.152, 2.134, 2.119, 2.119, 2.131, + 2.155, 2.125, 2.125, 2.135, 2.151, 2.169, 2.191, 2.207, 2.219, 2.243, 2.253, 2.258, 2.261, 2.266, 2.266, 2.267, 2.265, 2.262, 2.261, 2.254, 2.244, 2.238, 2.228, 2.212, 2.197, 2.179, 2.167, 2.158, 2.137, 2.122, 2.121, 2.131, + 2.155, 2.127, 2.127, 2.137, 2.153, 2.173, 2.197, 2.213, 2.231, 2.248, 2.257, 2.266, 2.271, 2.272, 2.274, 2.275, 2.275, 2.273, 2.271, 2.266, 2.257, 2.251, 2.238, 2.227, 2.209, 2.195, 2.175, 2.159, 2.141, 2.128, 2.127, 2.131, + 2.155, 2.128, 2.128, 2.139, 2.159, 2.182, 2.206, 2.225, 2.243, 2.252, 2.265, 2.272, 2.277, 2.283, 2.286, 2.284, 2.283, 2.282, 2.274, 2.272, 2.266, 2.256, 2.244, 2.238, 2.221, 2.202, 2.186, 2.169, 2.149, 2.129, 2.129, 2.135, + 2.154, 2.131, 2.131, 2.149, 2.166, 2.189, 2.211, 2.234, 2.248, 2.262, 2.272, 2.277, 2.287, 2.291, 2.293, 2.292, 2.291, 2.285, 2.284, 2.279, 2.272, 2.263, 2.254, 2.243, 2.226, 2.206, 2.193, 2.174, 2.153, 2.133, 2.133, 2.135, + 2.153, 2.135, 2.135, 2.151, 2.172, 2.198, 2.221, 2.238, 2.255, 2.265, 2.274, 2.287, 2.291, 2.296, 2.298, 2.298, 2.301, 2.297, 2.289, 2.285, 2.277, 2.271, 2.261, 2.251, 2.236, 2.216, 2.199, 2.179, 2.158, 2.135, 2.134, 2.135, + 2.152, 2.136, 2.136, 2.154, 2.176, 2.199, 2.224, 2.239, 2.256, 2.267, 2.282, 2.289, 2.295, 2.299, 2.303, 2.303, 2.302, 2.299, 2.297, 2.288, 2.284, 2.274, 2.262, 2.253, 2.238, 2.219, 2.202, 2.181, 2.158, 2.137, 2.135, 2.135, + 2.143, 2.134, 2.134, 2.154, 2.177, 2.201, 2.224, 2.241, 2.256, 2.271, 2.282, 2.289, 2.297, 2.302, 2.306, 2.306, 2.304, 2.301, 2.298, 2.289, 2.287, 2.272, 2.265, 2.255, 2.241, 2.221, 2.203, 2.183, 2.164, 2.141, 2.136, 2.135, + 2.142, 2.133, 2.133, 2.155, 2.178, 2.202, 2.223, 2.243, 2.258, 2.273, 2.283, 2.288, 2.296, 2.299, 2.306, 2.306, 2.301, 2.299, 2.296, 2.289, 2.286, 2.271, 2.267, 2.256, 2.244, 2.219, 2.206, 2.188, 2.163, 2.141, 2.137, 2.134, + 2.141, 2.131, 2.131, 2.153, 2.179, 2.202, 2.224, 2.242, 2.254, 2.274, 2.283, 2.288, 2.295, 2.298, 2.301, 2.301, 2.301, 2.296, 2.295, 2.289, 2.285, 2.271, 2.267, 2.257, 2.246, 2.223, 2.204, 2.188, 2.165, 2.141, 2.136, 2.134, + 2.141, 2.133, 2.133, 2.151, 2.179, 2.201, 2.224, 2.241, 2.254, 2.275, 2.283, 2.288, 2.294, 2.296, 2.298, 2.297, 2.295, 2.295, 2.294, 2.291, 2.284, 2.272, 2.267, 2.256, 2.248, 2.225, 2.208, 2.192, 2.167, 2.141, 2.137, 2.134, + 2.141, 2.132, 2.132, 2.151, 2.177, 2.199, 2.221, 2.238, 2.252, 2.274, 2.281, 2.287, 2.293, 2.295, 2.296, 2.294, 2.295, 2.295, 2.294, 2.291, 2.284, 2.274, 2.266, 2.257, 2.248, 2.226, 2.206, 2.189, 2.167, 2.143, 2.141, 2.141, + 2.141, 2.133, 2.133, 2.153, 2.175, 2.201, 2.221, 2.238, 2.252, 2.271, 2.278, 2.284, 2.288, 2.291, 2.292, 2.291, 2.293, 2.293, 2.293, 2.287, 2.279, 2.275, 2.266, 2.256, 2.243, 2.224, 2.206, 2.189, 2.168, 2.146, 2.142, 2.134, + 2.137, 2.131, 2.131, 2.154, 2.173, 2.199, 2.221, 2.236, 2.251, 2.267, 2.272, 2.278, 2.284, 2.287, 2.288, 2.286, 2.288, 2.288, 2.288, 2.283, 2.277, 2.273, 2.265, 2.256, 2.241, 2.219, 2.205, 2.187, 2.167, 2.144, 2.137, 2.132, + 2.136, 2.131, 2.131, 2.152, 2.169, 2.197, 2.218, 2.233, 2.246, 2.257, 2.269, 2.274, 2.281, 2.284, 2.286, 2.285, 2.286, 2.286, 2.286, 2.279, 2.274, 2.269, 2.263, 2.254, 2.239, 2.217, 2.203, 2.181, 2.162, 2.143, 2.133, 2.131, + 2.136, 2.131, 2.131, 2.151, 2.167, 2.189, 2.205, 2.226, 2.242, 2.253, 2.261, 2.271, 2.275, 2.279, 2.283, 2.283, 2.284, 2.284, 2.281, 2.277, 2.271, 2.264, 2.257, 2.246, 2.232, 2.215, 2.195, 2.176, 2.158, 2.141, 2.131, 2.128, + 2.136, 2.129, 2.131, 2.147, 2.162, 2.181, 2.203, 2.219, 2.236, 2.246, 2.256, 2.263, 2.271, 2.274, 2.278, 2.278, 2.276, 2.277, 2.276, 2.273, 2.266, 2.258, 2.251, 2.241, 2.227, 2.198, 2.191, 2.169, 2.154, 2.136, 2.125, 2.122, + 2.132, 2.126, 2.126, 2.139, 2.153, 2.168, 2.194, 2.212, 2.224, 2.238, 2.251, 2.258, 2.263, 2.266, 2.269, 2.271, 2.269, 2.269, 2.269, 2.267, 2.259, 2.253, 2.245, 2.237, 2.219, 2.196, 2.179, 2.162, 2.149, 2.132, 2.122, 2.121, + 2.124, 2.119, 2.121, 2.137, 2.147, 2.164, 2.183, 2.199, 2.219, 2.231, 2.239, 2.251, 2.257, 2.261, 2.262, 2.262, 2.259, 2.259, 2.261, 2.258, 2.253, 2.245, 2.237, 2.224, 2.209, 2.187, 2.174, 2.157, 2.141, 2.122, 2.121, 2.121, + 2.123, 2.115, 2.115, 2.131, 2.138, 2.157, 2.174, 2.188, 2.207, 2.221, 2.233, 2.239, 2.243, 2.244, 2.244, 2.244, 2.246, 2.245, 2.246, 2.244, 2.241, 2.231, 2.224, 2.212, 2.195, 2.176, 2.159, 2.145, 2.128, 2.117, 2.117, 2.123, + 2.123, 2.113, 2.113, 2.123, 2.132, 2.141, 2.162, 2.177, 2.191, 2.208, 2.221, 2.231, 2.231, 2.232, 2.234, 2.235, 2.235, 2.235, 2.238, 2.237, 2.225, 2.214, 2.209, 2.199, 2.181, 2.164, 2.146, 2.135, 2.123, 2.116, 2.116, 2.115, + 2.129, 2.115, 2.115, 2.121, 2.128, 2.135, 2.149, 2.164, 2.178, 2.193, 2.207, 2.221, 2.222, 2.222, 2.223, 2.224, 2.224, 2.224, 2.224, 2.223, 2.214, 2.205, 2.196, 2.185, 2.171, 2.151, 2.141, 2.129, 2.119, 2.116, 2.116, 2.117, + 2.137, 2.119, 2.119, 2.119, 2.122, 2.129, 2.141, 2.159, 2.167, 2.182, 2.195, 2.206, 2.211, 2.216, 2.218, 2.219, 2.219, 2.219, 2.217, 2.212, 2.202, 2.194, 2.184, 2.174, 2.162, 2.145, 2.134, 2.124, 2.118, 2.117, 2.118, 2.121, + 2.138, 2.131, 2.121, 2.122, 2.125, 2.128, 2.137, 2.154, 2.162, 2.176, 2.187, 2.194, 2.196, 2.198, 2.205, 2.205, 2.202, 2.202, 2.203, 2.201, 2.191, 2.182, 2.174, 2.162, 2.149, 2.136, 2.126, 2.121, 2.119, 2.118, 2.127, 2.133, + 2.157, 2.148, 2.131, 2.129, 2.129, 2.136, 2.148, 2.157, 2.169, 2.177, 2.182, 2.187, 2.188, 2.191, 2.193, 2.193, 2.192, 2.199, 2.201, 2.199, 2.186, 2.178, 2.167, 2.152, 2.146, 2.137, 2.126, 2.124, 2.121, 2.126, 2.133, 2.151, + 2.161, 2.157, 2.148, 2.147, 2.147, 2.147, 2.154, 2.162, 2.174, 2.179, 2.181, 2.184, 2.186, 2.187, 2.189, 2.189, 2.187, 2.188, 2.199, 2.201, 2.187, 2.178, 2.163, 2.148, 2.145, 2.141, 2.131, 2.129, 2.128, 2.135, 2.151, 2.153 + ] + }, + { + "ct": 5000, + "table": + [ + 1.191, 1.165, 1.156, 1.155, 1.157, 1.161, 1.168, 1.176, 1.179, 1.185, 1.187, 1.189, 1.189, 1.189, 1.191, 1.191, 1.191, 1.189, 1.188, 1.188, 1.185, 1.184, 1.182, 1.178, 1.173, 1.171, 1.166, 1.163, 1.159, 1.159, 1.164, 1.187, + 1.188, 1.164, 1.157, 1.156, 1.158, 1.166, 1.173, 1.179, 1.185, 1.193, 1.195, 1.198, 1.199, 1.201, 1.201, 1.202, 1.201, 1.199, 1.199, 1.196, 1.194, 1.189, 1.185, 1.182, 1.177, 1.172, 1.168, 1.164, 1.161, 1.161, 1.162, 1.181, + 1.184, 1.164, 1.157, 1.157, 1.161, 1.171, 1.179, 1.185, 1.193, 1.197, 1.201, 1.206, 1.208, 1.209, 1.209, 1.208, 1.207, 1.207, 1.207, 1.202, 1.199, 1.195, 1.192, 1.189, 1.182, 1.176, 1.171, 1.166, 1.161, 1.159, 1.161, 1.177, + 1.183, 1.162, 1.158, 1.158, 1.163, 1.174, 1.182, 1.191, 1.197, 1.203, 1.208, 1.212, 1.214, 1.214, 1.218, 1.218, 1.214, 1.212, 1.211, 1.208, 1.206, 1.201, 1.197, 1.192, 1.189, 1.179, 1.174, 1.168, 1.162, 1.159, 1.159, 1.173, + 1.181, 1.159, 1.159, 1.159, 1.168, 1.178, 1.189, 1.196, 1.204, 1.208, 1.213, 1.217, 1.219, 1.221, 1.222, 1.222, 1.222, 1.221, 1.219, 1.215, 1.212, 1.208, 1.202, 1.197, 1.189, 1.183, 1.178, 1.169, 1.163, 1.158, 1.158, 1.169, + 1.174, 1.159, 1.159, 1.164, 1.172, 1.179, 1.192, 1.201, 1.208, 1.212, 1.219, 1.224, 1.225, 1.227, 1.228, 1.228, 1.226, 1.225, 1.224, 1.221, 1.217, 1.212, 1.208, 1.202, 1.194, 1.187, 1.181, 1.172, 1.164, 1.157, 1.157, 1.169, + 1.174, 1.159, 1.159, 1.165, 1.174, 1.184, 1.197, 1.205, 1.209, 1.219, 1.224, 1.228, 1.231, 1.231, 1.231, 1.231, 1.229, 1.229, 1.228, 1.226, 1.222, 1.218, 1.212, 1.205, 1.199, 1.188, 1.181, 1.175, 1.165, 1.157, 1.157, 1.163, + 1.173, 1.159, 1.159, 1.165, 1.176, 1.186, 1.198, 1.207, 1.213, 1.223, 1.229, 1.231, 1.235, 1.236, 1.236, 1.236, 1.236, 1.235, 1.234, 1.232, 1.226, 1.223, 1.218, 1.209, 1.201, 1.192, 1.183, 1.178, 1.165, 1.157, 1.157, 1.163, + 1.172, 1.159, 1.159, 1.166, 1.176, 1.188, 1.201, 1.209, 1.217, 1.227, 1.231, 1.236, 1.238, 1.239, 1.241, 1.242, 1.242, 1.241, 1.239, 1.235, 1.232, 1.227, 1.223, 1.215, 1.208, 1.199, 1.187, 1.179, 1.167, 1.159, 1.159, 1.163, + 1.172, 1.159, 1.159, 1.166, 1.177, 1.189, 1.203, 1.212, 1.223, 1.228, 1.236, 1.239, 1.242, 1.245, 1.246, 1.246, 1.247, 1.246, 1.242, 1.241, 1.237, 1.232, 1.226, 1.223, 1.213, 1.202, 1.191, 1.182, 1.172, 1.159, 1.159, 1.163, + 1.168, 1.158, 1.158, 1.167, 1.179, 1.192, 1.204, 1.218, 1.225, 1.233, 1.238, 1.242, 1.246, 1.248, 1.251, 1.251, 1.249, 1.248, 1.247, 1.244, 1.239, 1.237, 1.228, 1.223, 1.214, 1.203, 1.194, 1.183, 1.173, 1.161, 1.161, 1.162, + 1.166, 1.158, 1.158, 1.168, 1.183, 1.195, 1.207, 1.218, 1.226, 1.233, 1.239, 1.246, 1.248, 1.251, 1.254, 1.254, 1.254, 1.251, 1.249, 1.247, 1.242, 1.239, 1.232, 1.227, 1.219, 1.207, 1.195, 1.186, 1.175, 1.162, 1.161, 1.162, + 1.165, 1.158, 1.158, 1.168, 1.183, 1.196, 1.208, 1.219, 1.227, 1.234, 1.241, 1.247, 1.251, 1.254, 1.255, 1.256, 1.256, 1.254, 1.252, 1.249, 1.246, 1.241, 1.234, 1.228, 1.221, 1.211, 1.199, 1.187, 1.175, 1.163, 1.162, 1.162, + 1.161, 1.158, 1.158, 1.169, 1.183, 1.196, 1.208, 1.217, 1.227, 1.234, 1.241, 1.247, 1.253, 1.254, 1.256, 1.257, 1.256, 1.255, 1.253, 1.249, 1.247, 1.241, 1.236, 1.229, 1.221, 1.211, 1.199, 1.189, 1.176, 1.164, 1.163, 1.162, + 1.161, 1.156, 1.156, 1.169, 1.183, 1.196, 1.207, 1.218, 1.227, 1.235, 1.241, 1.246, 1.252, 1.254, 1.256, 1.257, 1.256, 1.254, 1.253, 1.249, 1.247, 1.241, 1.237, 1.231, 1.223, 1.211, 1.201, 1.191, 1.177, 1.164, 1.164, 1.161, + 1.161, 1.155, 1.155, 1.169, 1.182, 1.195, 1.208, 1.216, 1.225, 1.235, 1.241, 1.245, 1.249, 1.252, 1.254, 1.254, 1.254, 1.253, 1.252, 1.249, 1.246, 1.239, 1.237, 1.231, 1.224, 1.211, 1.201, 1.191, 1.178, 1.164, 1.162, 1.161, + 1.159, 1.155, 1.155, 1.168, 1.181, 1.195, 1.208, 1.217, 1.223, 1.235, 1.241, 1.244, 1.248, 1.251, 1.252, 1.252, 1.252, 1.252, 1.251, 1.248, 1.245, 1.241, 1.236, 1.231, 1.224, 1.212, 1.202, 1.191, 1.179, 1.164, 1.162, 1.161, + 1.158, 1.154, 1.154, 1.167, 1.181, 1.194, 1.206, 1.216, 1.222, 1.234, 1.237, 1.242, 1.245, 1.248, 1.251, 1.249, 1.249, 1.249, 1.249, 1.248, 1.244, 1.241, 1.235, 1.229, 1.223, 1.213, 1.202, 1.191, 1.179, 1.167, 1.163, 1.163, + 1.158, 1.154, 1.154, 1.168, 1.181, 1.194, 1.206, 1.215, 1.223, 1.231, 1.236, 1.239, 1.243, 1.245, 1.246, 1.246, 1.248, 1.248, 1.248, 1.245, 1.242, 1.239, 1.235, 1.229, 1.223, 1.213, 1.202, 1.191, 1.179, 1.167, 1.163, 1.162, + 1.157, 1.154, 1.154, 1.168, 1.179, 1.194, 1.205, 1.215, 1.222, 1.229, 1.233, 1.236, 1.239, 1.243, 1.244, 1.244, 1.245, 1.245, 1.244, 1.243, 1.239, 1.236, 1.234, 1.229, 1.222, 1.211, 1.202, 1.191, 1.179, 1.166, 1.163, 1.161, + 1.156, 1.155, 1.155, 1.168, 1.179, 1.193, 1.205, 1.213, 1.219, 1.225, 1.231, 1.234, 1.238, 1.239, 1.241, 1.243, 1.243, 1.243, 1.243, 1.239, 1.237, 1.235, 1.231, 1.228, 1.221, 1.209, 1.199, 1.189, 1.178, 1.166, 1.162, 1.159, + 1.156, 1.156, 1.157, 1.167, 1.178, 1.191, 1.199, 1.209, 1.217, 1.223, 1.226, 1.231, 1.233, 1.236, 1.239, 1.239, 1.241, 1.241, 1.239, 1.237, 1.235, 1.232, 1.229, 1.224, 1.217, 1.209, 1.196, 1.187, 1.176, 1.165, 1.159, 1.157, + 1.157, 1.157, 1.157, 1.166, 1.175, 1.187, 1.198, 1.205, 1.213, 1.219, 1.223, 1.227, 1.231, 1.233, 1.236, 1.236, 1.234, 1.235, 1.235, 1.235, 1.231, 1.229, 1.227, 1.222, 1.216, 1.201, 1.194, 1.184, 1.174, 1.163, 1.157, 1.156, + 1.158, 1.155, 1.155, 1.165, 1.172, 1.181, 1.194, 1.202, 1.208, 1.215, 1.221, 1.223, 1.227, 1.229, 1.231, 1.231, 1.231, 1.232, 1.233, 1.231, 1.228, 1.227, 1.223, 1.219, 1.213, 1.199, 1.189, 1.181, 1.171, 1.161, 1.157, 1.156, + 1.155, 1.154, 1.154, 1.164, 1.169, 1.179, 1.189, 1.196, 1.203, 1.208, 1.215, 1.221, 1.222, 1.224, 1.225, 1.225, 1.226, 1.228, 1.228, 1.227, 1.225, 1.222, 1.219, 1.213, 1.206, 1.196, 1.187, 1.177, 1.168, 1.159, 1.156, 1.156, + 1.155, 1.152, 1.152, 1.162, 1.167, 1.175, 1.185, 1.191, 1.198, 1.205, 1.209, 1.214, 1.216, 1.217, 1.217, 1.217, 1.219, 1.219, 1.219, 1.219, 1.217, 1.215, 1.213, 1.207, 1.199, 1.191, 1.179, 1.172, 1.165, 1.156, 1.155, 1.155, + 1.155, 1.152, 1.152, 1.161, 1.163, 1.169, 1.179, 1.186, 1.192, 1.198, 1.204, 1.208, 1.211, 1.211, 1.211, 1.212, 1.212, 1.213, 1.215, 1.215, 1.211, 1.208, 1.205, 1.199, 1.194, 1.185, 1.175, 1.167, 1.161, 1.156, 1.155, 1.153, + 1.157, 1.152, 1.152, 1.159, 1.162, 1.166, 1.174, 1.181, 1.187, 1.192, 1.197, 1.203, 1.204, 1.205, 1.204, 1.204, 1.204, 1.205, 1.206, 1.206, 1.204, 1.201, 1.198, 1.194, 1.187, 1.176, 1.171, 1.164, 1.159, 1.156, 1.155, 1.154, + 1.159, 1.154, 1.154, 1.158, 1.159, 1.163, 1.171, 1.176, 1.181, 1.187, 1.191, 1.195, 1.198, 1.199, 1.199, 1.201, 1.201, 1.202, 1.202, 1.199, 1.196, 1.193, 1.191, 1.188, 1.182, 1.174, 1.166, 1.162, 1.157, 1.156, 1.156, 1.156, + 1.162, 1.161, 1.158, 1.159, 1.159, 1.162, 1.167, 1.173, 1.178, 1.181, 1.186, 1.189, 1.189, 1.191, 1.193, 1.193, 1.193, 1.194, 1.194, 1.194, 1.189, 1.187, 1.186, 1.182, 1.176, 1.167, 1.163, 1.159, 1.158, 1.157, 1.158, 1.161, + 1.172, 1.165, 1.162, 1.162, 1.163, 1.166, 1.169, 1.173, 1.178, 1.181, 1.182, 1.185, 1.186, 1.186, 1.186, 1.187, 1.187, 1.189, 1.192, 1.191, 1.187, 1.185, 1.181, 1.177, 1.172, 1.167, 1.163, 1.159, 1.159, 1.161, 1.163, 1.166, + 1.173, 1.172, 1.166, 1.165, 1.166, 1.168, 1.171, 1.176, 1.179, 1.182, 1.181, 1.183, 1.185, 1.185, 1.185, 1.185, 1.185, 1.185, 1.191, 1.191, 1.185, 1.181, 1.179, 1.173, 1.169, 1.168, 1.163, 1.162, 1.161, 1.164, 1.166, 1.167 + ] + } + ], + "luminance_lut": + [ + 2.271, 2.218, 2.105, 2.004, 1.909, 1.829, 1.762, 1.705, 1.665, 1.629, 1.592, 1.559, 1.528, 1.516, 1.511, 1.511, 1.511, 1.514, 1.525, 1.553, 1.585, 1.617, 1.655, 1.697, 1.752, 1.816, 1.893, 1.982, 2.084, 2.195, 2.321, 2.342, + 2.218, 2.166, 2.057, 1.959, 1.871, 1.793, 1.726, 1.675, 1.633, 1.592, 1.559, 1.528, 1.503, 1.484, 1.474, 1.472, 1.472, 1.482, 1.499, 1.523, 1.553, 1.585, 1.619, 1.664, 1.715, 1.779, 1.855, 1.938, 2.037, 2.147, 2.259, 2.321, + 2.166, 2.101, 1.997, 1.901, 1.818, 1.743, 1.683, 1.634, 1.588, 1.546, 1.508, 1.476, 1.449, 1.429, 1.418, 1.415, 1.415, 1.425, 1.444, 1.469, 1.501, 1.538, 1.577, 1.622, 1.671, 1.728, 1.799, 1.881, 1.975, 2.078, 2.185, 2.259, + 2.101, 2.039, 1.938, 1.848, 1.768, 1.699, 1.641, 1.588, 1.541, 1.494, 1.455, 1.421, 1.394, 1.374, 1.361, 1.357, 1.357, 1.367, 1.388, 1.414, 1.448, 1.485, 1.528, 1.577, 1.626, 1.682, 1.748, 1.827, 1.917, 2.014, 2.119, 2.185, + 2.039, 1.979, 1.883, 1.795, 1.722, 1.658, 1.596, 1.541, 1.493, 1.443, 1.401, 1.364, 1.336, 1.316, 1.303, 1.301, 1.301, 1.311, 1.331, 1.359, 1.393, 1.432, 1.482, 1.528, 1.582, 1.641, 1.701, 1.775, 1.861, 1.956, 2.056, 2.119, + 1.979, 1.932, 1.836, 1.752, 1.685, 1.621, 1.557, 1.497, 1.443, 1.399, 1.351, 1.314, 1.286, 1.264, 1.253, 1.249, 1.249, 1.259, 1.281, 1.311, 1.344, 1.387, 1.432, 1.484, 1.541, 1.601, 1.662, 1.731, 1.816, 1.908, 2.003, 2.056, + 1.934, 1.888, 1.798, 1.719, 1.651, 1.584, 1.519, 1.457, 1.401, 1.351, 1.307, 1.268, 1.239, 1.217, 1.206, 1.203, 1.203, 1.212, 1.234, 1.263, 1.298, 1.344, 1.387, 1.442, 1.502, 1.565, 1.628, 1.693, 1.774, 1.864, 1.956, 2.003, + 1.901, 1.851, 1.763, 1.688, 1.618, 1.551, 1.483, 1.419, 1.359, 1.307, 1.268, 1.226, 1.195, 1.175, 1.164, 1.161, 1.161, 1.171, 1.192, 1.221, 1.262, 1.298, 1.346, 1.404, 1.466, 1.532, 1.595, 1.661, 1.738, 1.826, 1.917, 1.956, + 1.873, 1.821, 1.734, 1.659, 1.591, 1.519, 1.451, 1.386, 1.324, 1.269, 1.226, 1.192, 1.159, 1.141, 1.127, 1.125, 1.125, 1.135, 1.155, 1.187, 1.221, 1.262, 1.311, 1.368, 1.432, 1.499, 1.566, 1.634, 1.708, 1.793, 1.882, 1.917, + 1.847, 1.797, 1.713, 1.639, 1.565, 1.493, 1.422, 1.355, 1.291, 1.238, 1.192, 1.159, 1.128, 1.108, 1.097, 1.094, 1.094, 1.104, 1.125, 1.155, 1.187, 1.229, 1.279, 1.338, 1.403, 1.471, 1.541, 1.611, 1.684, 1.766, 1.853, 1.885, + 1.828, 1.772, 1.691, 1.614, 1.539, 1.466, 1.394, 1.325, 1.264, 1.209, 1.163, 1.128, 1.104, 1.081, 1.069, 1.067, 1.067, 1.078, 1.101, 1.125, 1.159, 1.201, 1.252, 1.312, 1.379, 1.447, 1.517, 1.591, 1.665, 1.743, 1.831, 1.862, + 1.812, 1.754, 1.677, 1.599, 1.519, 1.445, 1.371, 1.302, 1.239, 1.185, 1.139, 1.104, 1.081, 1.061, 1.048, 1.046, 1.046, 1.058, 1.078, 1.102, 1.136, 1.177, 1.229, 1.289, 1.356, 1.425, 1.497, 1.572, 1.647, 1.724, 1.811, 1.847, + 1.798, 1.741, 1.663, 1.585, 1.506, 1.429, 1.353, 1.284, 1.221, 1.167, 1.121, 1.086, 1.061, 1.046, 1.031, 1.029, 1.029, 1.044, 1.058, 1.083, 1.116, 1.159, 1.209, 1.271, 1.338, 1.407, 1.479, 1.557, 1.633, 1.709, 1.792, 1.832, + 1.792, 1.727, 1.651, 1.572, 1.494, 1.414, 1.339, 1.269, 1.206, 1.152, 1.106, 1.072, 1.046, 1.031, 1.018, 1.016, 1.016, 1.029, 1.044, 1.069, 1.102, 1.145, 1.196, 1.256, 1.324, 1.394, 1.471, 1.545, 1.624, 1.698, 1.782, 1.825, + 1.787, 1.724, 1.647, 1.566, 1.484, 1.407, 1.329, 1.258, 1.196, 1.141, 1.097, 1.062, 1.036, 1.018, 1.012, 1.007, 1.011, 1.016, 1.034, 1.059, 1.093, 1.135, 1.186, 1.246, 1.314, 1.386, 1.461, 1.538, 1.616, 1.691, 1.773, 1.818, + 1.786, 1.721, 1.642, 1.562, 1.481, 1.402, 1.325, 1.254, 1.191, 1.137, 1.092, 1.057, 1.031, 1.013, 1.004, 1.001, 1.004, 1.011, 1.028, 1.054, 1.088, 1.129, 1.181, 1.241, 1.308, 1.382, 1.458, 1.535, 1.613, 1.687, 1.769, 1.818, + 1.786, 1.721, 1.642, 1.562, 1.481, 1.401, 1.325, 1.253, 1.191, 1.136, 1.091, 1.057, 1.031, 1.013, 1.003, 1.001, 1.001, 1.011, 1.028, 1.054, 1.088, 1.129, 1.181, 1.241, 1.308, 1.382, 1.458, 1.535, 1.613, 1.687, 1.769, 1.818, + 1.787, 1.722, 1.643, 1.563, 1.482, 1.402, 1.326, 1.254, 1.192, 1.138, 1.092, 1.057, 1.032, 1.013, 1.006, 1.002, 1.006, 1.012, 1.031, 1.057, 1.092, 1.133, 1.185, 1.243, 1.311, 1.385, 1.461, 1.539, 1.618, 1.691, 1.774, 1.821, + 1.789, 1.729, 1.651, 1.571, 1.489, 1.411, 1.334, 1.263, 1.201, 1.147, 1.101, 1.065, 1.038, 1.021, 1.013, 1.009, 1.012, 1.021, 1.038, 1.064, 1.098, 1.141, 1.193, 1.254, 1.321, 1.395, 1.472, 1.549, 1.626, 1.701, 1.785, 1.825, + 1.799, 1.739, 1.661, 1.581, 1.502, 1.422, 1.347, 1.277, 1.214, 1.159, 1.111, 1.075, 1.049, 1.037, 1.021, 1.019, 1.021, 1.036, 1.049, 1.076, 1.111, 1.154, 1.207, 1.268, 1.334, 1.408, 1.485, 1.562, 1.639, 1.715, 1.799, 1.837, + 1.811, 1.755, 1.676, 1.597, 1.518, 1.439, 1.365, 1.295, 1.231, 1.176, 1.129, 1.093, 1.067, 1.049, 1.038, 1.036, 1.036, 1.049, 1.067, 1.094, 1.129, 1.173, 1.225, 1.286, 1.353, 1.425, 1.501, 1.577, 1.653, 1.729, 1.815, 1.851, + 1.829, 1.774, 1.693, 1.615, 1.537, 1.462, 1.387, 1.316, 1.253, 1.198, 1.153, 1.115, 1.091, 1.067, 1.059, 1.056, 1.056, 1.067, 1.092, 1.115, 1.151, 1.196, 1.249, 1.309, 1.375, 1.446, 1.522, 1.595, 1.672, 1.752, 1.839, 1.871, + 1.851, 1.801, 1.713, 1.636, 1.561, 1.485, 1.411, 1.342, 1.281, 1.226, 1.179, 1.145, 1.115, 1.091, 1.082, 1.081, 1.082, 1.092, 1.115, 1.143, 1.178, 1.223, 1.276, 1.337, 1.402, 1.472, 1.544, 1.618, 1.691, 1.774, 1.865, 1.896, + 1.876, 1.831, 1.739, 1.663, 1.588, 1.513, 1.439, 1.374, 1.312, 1.258, 1.212, 1.179, 1.145, 1.123, 1.113, 1.112, 1.112, 1.122, 1.143, 1.177, 1.211, 1.256, 1.308, 1.368, 1.431, 1.501, 1.572, 1.641, 1.716, 1.802, 1.896, 1.931, + 1.909, 1.867, 1.771, 1.691, 1.617, 1.545, 1.474, 1.411, 1.349, 1.296, 1.252, 1.212, 1.182, 1.159, 1.149, 1.148, 1.149, 1.158, 1.179, 1.211, 1.253, 1.293, 1.344, 1.403, 1.465, 1.533, 1.603, 1.669, 1.747, 1.836, 1.931, 1.974, + 1.952, 1.905, 1.806, 1.722, 1.651, 1.578, 1.511, 1.448, 1.388, 1.338, 1.296, 1.252, 1.223, 1.201, 1.189, 1.189, 1.189, 1.199, 1.224, 1.253, 1.293, 1.338, 1.384, 1.442, 1.504, 1.571, 1.638, 1.704, 1.782, 1.872, 1.974, 2.025, + 2.004, 1.951, 1.849, 1.759, 1.688, 1.619, 1.552, 1.491, 1.435, 1.388, 1.338, 1.301, 1.272, 1.249, 1.238, 1.236, 1.236, 1.248, 1.271, 1.301, 1.338, 1.384, 1.431, 1.484, 1.543, 1.609, 1.675, 1.742, 1.825, 1.919, 2.025, 2.081, + 2.062, 2.004, 1.898, 1.805, 1.729, 1.661, 1.597, 1.539, 1.486, 1.435, 1.391, 1.354, 1.326, 1.303, 1.291, 1.289, 1.289, 1.301, 1.323, 1.353, 1.389, 1.431, 1.483, 1.528, 1.585, 1.649, 1.713, 1.787, 1.875, 1.971, 2.081, 2.145, + 2.129, 2.062, 1.951, 1.854, 1.774, 1.705, 1.642, 1.586, 1.539, 1.486, 1.445, 1.411, 1.383, 1.361, 1.348, 1.347, 1.348, 1.359, 1.379, 1.409, 1.447, 1.484, 1.528, 1.578, 1.631, 1.691, 1.759, 1.836, 1.928, 2.031, 2.145, 2.217, + 2.201, 2.129, 2.013, 1.912, 1.827, 1.752, 1.689, 1.642, 1.586, 1.544, 1.501, 1.468, 1.442, 1.421, 1.409, 1.409, 1.411, 1.421, 1.439, 1.467, 1.504, 1.543, 1.578, 1.629, 1.679, 1.739, 1.815, 1.894, 1.985, 2.098, 2.217, 2.298, + 2.273, 2.201, 2.081, 1.974, 1.886, 1.807, 1.741, 1.689, 1.643, 1.603, 1.562, 1.527, 1.504, 1.485, 1.475, 1.474, 1.475, 1.487, 1.503, 1.531, 1.565, 1.601, 1.634, 1.678, 1.728, 1.795, 1.877, 1.961, 2.052, 2.169, 2.298, 2.365, + 2.317, 2.273, 2.146, 2.039, 1.946, 1.864, 1.792, 1.737, 1.688, 1.643, 1.603, 1.562, 1.533, 1.525, 1.523, 1.523, 1.523, 1.525, 1.534, 1.565, 1.601, 1.634, 1.677, 1.722, 1.772, 1.848, 1.935, 2.023, 2.108, 2.232, 2.365, 2.403 + ], + "sigma": 0.00285, + "sigma_Cb": 0.00166 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.70741, -0.05307, -0.65433, + -0.62822, 1.68836, -0.06014, + -0.04452, -1.87628, 2.92079 + ] + }, + { + "ct": 2803, + "ccm": + [ + 1.74383, -0.18731, -0.55652, + -0.56491, 1.67772, -0.11281, + -0.01522, -1.60635, 2.62157 + ] + }, + { + "ct": 2912, + "ccm": + [ + 1.75215, -0.22221, -0.52995, + -0.54568, 1.63522, -0.08954, + 0.02633, -1.56997, 2.54364 + ] + }, + { + "ct": 2914, + "ccm": + [ + 1.72423, -0.28939, -0.43484, + -0.55188, 1.62925, -0.07737, + 0.01959, -1.28661, 2.26702 + ] + }, + { + "ct": 3605, + "ccm": + [ + 1.80381, -0.43646, -0.36735, + -0.46505, 1.56814, -0.10309, + 0.00929, -1.00424, 1.99495 + ] + }, + { + "ct": 4540, + "ccm": + [ + 1.85263, -0.46545, -0.38719, + -0.44136, 1.68443, -0.24307, + 0.04108, -0.85599, 1.81491 + ] + }, + { + "ct": 5699, + "ccm": + [ + 1.98595, -0.63542, -0.35054, + -0.34623, 1.54146, -0.19522, + 0.00411, -0.70936, 1.70525 + ] + }, + { + "ct": 8625, + "ccm": + [ + 2.21637, -0.56663, -0.64974, + -0.41133, 1.96625, -0.55492, + -0.02307, -0.83529, 1.85837 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "short": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/ov64a40.json b/src/ipa/rpi/pisp/data/ov64a40.json new file mode 100755 index 00000000..d9e263eb --- /dev/null +++ b/src/ipa/rpi/pisp/data/ov64a40.json @@ -0,0 +1,1133 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 17861, + "reference_gain": 2.0, + "reference_aperture": 1.0, + "reference_lux": 1073, + "reference_Y": 9022 + } + }, + { + "rpi.dpc": + { + "strength": 1 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.984 + } + }, + { + "rpi.geq": + { + "offset": 215, + "slope": 0.01121 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 7700 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8000 + } + }, + "bayes": 1, + "ct_curve": + [ + 2300.0, 1.0576, 0.4098, + 2700.0, 0.7924, 0.4334, + 3000.0, 0.7635, 0.4428, + 4000.0, 0.6003, 0.5412, + 4150.0, 0.5627, 0.5789, + 6500.0, 0.4409, 0.7596 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.05597, + "transverse_neg": 0.04295 + } + }, + { + "rpi.agc": + { + "channels": [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ ], + "shadows": [ ] + }, + "channel_constraints": [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 6500, + "table": + [ + 2.447, 2.441, 2.423, 2.414, 2.401, 2.391, 2.379, 2.376, 2.375, 2.369, 2.364, 2.362, 2.359, 2.356, 2.351, 2.349, 2.349, 2.341, 2.336, 2.334, 2.332, 2.331, 2.331, 2.331, 2.333, 2.338, 2.342, 2.347, 2.359, 2.368, 2.378, 2.391, + 2.447, 2.441, 2.422, 2.408, 2.393, 2.388, 2.382, 2.376, 2.369, 2.365, 2.362, 2.359, 2.353, 2.351, 2.345, 2.345, 2.336, 2.336, 2.334, 2.327, 2.325, 2.326, 2.326, 2.329, 2.329, 2.334, 2.338, 2.347, 2.359, 2.368, 2.378, 2.389, + 2.446, 2.433, 2.414, 2.403, 2.393, 2.387, 2.382, 2.371, 2.369, 2.362, 2.356, 2.351, 2.335, 2.335, 2.329, 2.326, 2.318, 2.314, 2.313, 2.312, 2.312, 2.312, 2.318, 2.321, 2.326, 2.329, 2.334, 2.344, 2.357, 2.368, 2.378, 2.389, + 2.443, 2.431, 2.409, 2.402, 2.393, 2.383, 2.374, 2.369, 2.363, 2.356, 2.343, 2.335, 2.322, 2.317, 2.311, 2.309, 2.302, 2.299, 2.298, 2.295, 2.295, 2.296, 2.309, 2.313, 2.321, 2.326, 2.329, 2.341, 2.352, 2.364, 2.374, 2.385, + 2.442, 2.427, 2.409, 2.399, 2.393, 2.383, 2.371, 2.364, 2.356, 2.343, 2.333, 2.319, 2.313, 2.302, 2.297, 2.295, 2.293, 2.288, 2.285, 2.281, 2.281, 2.285, 2.293, 2.306, 2.313, 2.322, 2.327, 2.337, 2.351, 2.364, 2.374, 2.385, + 2.442, 2.427, 2.411, 2.398, 2.389, 2.381, 2.369, 2.359, 2.351, 2.335, 2.319, 2.312, 2.301, 2.296, 2.289, 2.283, 2.279, 2.277, 2.273, 2.269, 2.267, 2.268, 2.279, 2.293, 2.306, 2.316, 2.325, 2.335, 2.348, 2.361, 2.372, 2.385, + 2.441, 2.428, 2.411, 2.399, 2.389, 2.381, 2.369, 2.356, 2.343, 2.331, 2.312, 2.301, 2.292, 2.286, 2.279, 2.278, 2.273, 2.271, 2.267, 2.259, 2.259, 2.262, 2.268, 2.279, 2.297, 2.311, 2.323, 2.335, 2.347, 2.357, 2.371, 2.388, + 2.441, 2.428, 2.412, 2.397, 2.389, 2.381, 2.369, 2.355, 2.337, 2.318, 2.308, 2.292, 2.286, 2.278, 2.276, 2.268, 2.264, 2.263, 2.257, 2.252, 2.252, 2.255, 2.262, 2.273, 2.284, 2.303, 2.319, 2.336, 2.343, 2.356, 2.365, 2.388, + 2.441, 2.428, 2.412, 2.401, 2.389, 2.381, 2.363, 2.344, 2.323, 2.311, 2.299, 2.287, 2.278, 2.275, 2.267, 2.264, 2.259, 2.256, 2.249, 2.246, 2.246, 2.249, 2.255, 2.262, 2.279, 2.296, 2.313, 2.336, 2.347, 2.355, 2.366, 2.384, + 2.441, 2.427, 2.412, 2.401, 2.391, 2.381, 2.355, 2.339, 2.323, 2.308, 2.297, 2.284, 2.275, 2.267, 2.259, 2.253, 2.251, 2.247, 2.244, 2.239, 2.239, 2.244, 2.249, 2.259, 2.276, 2.288, 2.311, 2.332, 2.348, 2.356, 2.366, 2.384, + 2.442, 2.428, 2.415, 2.402, 2.391, 2.379, 2.354, 2.331, 2.323, 2.302, 2.291, 2.278, 2.267, 2.259, 2.253, 2.246, 2.245, 2.241, 2.236, 2.233, 2.235, 2.239, 2.245, 2.255, 2.271, 2.287, 2.307, 2.331, 2.349, 2.356, 2.367, 2.384, + 2.441, 2.429, 2.419, 2.404, 2.394, 2.375, 2.352, 2.331, 2.318, 2.301, 2.288, 2.273, 2.263, 2.254, 2.246, 2.242, 2.237, 2.232, 2.229, 2.229, 2.232, 2.235, 2.245, 2.255, 2.271, 2.286, 2.307, 2.331, 2.351, 2.359, 2.371, 2.385, + 2.441, 2.432, 2.419, 2.406, 2.395, 2.375, 2.349, 2.332, 2.317, 2.301, 2.288, 2.273, 2.262, 2.249, 2.242, 2.235, 2.226, 2.225, 2.225, 2.227, 2.232, 2.235, 2.246, 2.255, 2.271, 2.286, 2.307, 2.331, 2.351, 2.366, 2.376, 2.388, + 2.441, 2.437, 2.424, 2.406, 2.395, 2.375, 2.352, 2.333, 2.317, 2.304, 2.289, 2.274, 2.261, 2.245, 2.235, 2.226, 2.217, 2.216, 2.219, 2.225, 2.229, 2.237, 2.246, 2.255, 2.272, 2.289, 2.307, 2.334, 2.352, 2.369, 2.376, 2.392, + 2.447, 2.438, 2.425, 2.412, 2.399, 2.378, 2.352, 2.335, 2.317, 2.304, 2.289, 2.275, 2.259, 2.243, 2.227, 2.217, 2.214, 2.209, 2.214, 2.219, 2.229, 2.238, 2.246, 2.258, 2.272, 2.289, 2.309, 2.334, 2.355, 2.369, 2.385, 2.392, + 2.449, 2.441, 2.425, 2.416, 2.399, 2.381, 2.357, 2.336, 2.321, 2.305, 2.289, 2.275, 2.261, 2.242, 2.225, 2.214, 2.207, 2.207, 2.209, 2.218, 2.229, 2.238, 2.249, 2.261, 2.275, 2.291, 2.309, 2.336, 2.354, 2.371, 2.386, 2.396, + 2.451, 2.442, 2.426, 2.419, 2.403, 2.383, 2.361, 2.341, 2.321, 2.305, 2.289, 2.276, 2.259, 2.243, 2.225, 2.213, 2.207, 2.207, 2.209, 2.218, 2.227, 2.241, 2.249, 2.261, 2.277, 2.295, 2.316, 2.338, 2.355, 2.374, 2.387, 2.398, + 2.452, 2.442, 2.427, 2.419, 2.405, 2.384, 2.361, 2.341, 2.321, 2.305, 2.293, 2.277, 2.259, 2.244, 2.226, 2.213, 2.211, 2.208, 2.211, 2.218, 2.229, 2.241, 2.249, 2.263, 2.279, 2.298, 2.319, 2.339, 2.359, 2.374, 2.387, 2.398, + 2.452, 2.442, 2.428, 2.419, 2.405, 2.384, 2.361, 2.341, 2.324, 2.305, 2.293, 2.277, 2.259, 2.245, 2.232, 2.222, 2.213, 2.213, 2.218, 2.223, 2.229, 2.241, 2.249, 2.265, 2.279, 2.298, 2.319, 2.339, 2.363, 2.374, 2.387, 2.406, + 2.452, 2.442, 2.428, 2.419, 2.405, 2.384, 2.361, 2.343, 2.324, 2.305, 2.293, 2.279, 2.265, 2.251, 2.241, 2.232, 2.222, 2.222, 2.223, 2.229, 2.233, 2.241, 2.251, 2.265, 2.279, 2.298, 2.321, 2.341, 2.364, 2.374, 2.387, 2.405, + 2.454, 2.442, 2.429, 2.419, 2.404, 2.386, 2.365, 2.346, 2.327, 2.309, 2.293, 2.281, 2.269, 2.258, 2.251, 2.241, 2.237, 2.231, 2.231, 2.233, 2.238, 2.245, 2.256, 2.267, 2.283, 2.301, 2.323, 2.344, 2.366, 2.379, 2.388, 2.405, + 2.454, 2.439, 2.431, 2.421, 2.404, 2.392, 2.369, 2.348, 2.332, 2.314, 2.299, 2.288, 2.274, 2.268, 2.258, 2.252, 2.249, 2.244, 2.241, 2.241, 2.245, 2.251, 2.259, 2.269, 2.287, 2.301, 2.324, 2.352, 2.366, 2.379, 2.388, 2.405, + 2.453, 2.438, 2.431, 2.418, 2.407, 2.393, 2.374, 2.352, 2.337, 2.321, 2.303, 2.293, 2.284, 2.274, 2.268, 2.261, 2.259, 2.257, 2.251, 2.251, 2.251, 2.258, 2.266, 2.276, 2.297, 2.314, 2.333, 2.354, 2.366, 2.381, 2.391, 2.407, + 2.453, 2.438, 2.431, 2.417, 2.408, 2.396, 2.379, 2.359, 2.344, 2.329, 2.314, 2.301, 2.293, 2.284, 2.277, 2.276, 2.271, 2.266, 2.266, 2.261, 2.261, 2.266, 2.276, 2.285, 2.301, 2.318, 2.339, 2.356, 2.371, 2.383, 2.393, 2.408, + 2.453, 2.441, 2.428, 2.417, 2.409, 2.398, 2.386, 2.371, 2.351, 2.339, 2.324, 2.314, 2.301, 2.296, 2.289, 2.288, 2.281, 2.278, 2.275, 2.271, 2.271, 2.276, 2.285, 2.294, 2.311, 2.327, 2.346, 2.356, 2.373, 2.386, 2.393, 2.409, + 2.457, 2.441, 2.428, 2.417, 2.412, 2.403, 2.389, 2.381, 2.365, 2.349, 2.337, 2.324, 2.314, 2.309, 2.306, 2.303, 2.295, 2.294, 2.291, 2.287, 2.287, 2.289, 2.294, 2.309, 2.319, 2.332, 2.351, 2.359, 2.377, 2.386, 2.398, 2.411, + 2.457, 2.443, 2.427, 2.417, 2.413, 2.405, 2.399, 2.388, 2.373, 2.365, 2.349, 2.337, 2.327, 2.323, 2.318, 2.314, 2.312, 2.309, 2.304, 2.303, 2.302, 2.303, 2.309, 2.319, 2.332, 2.344, 2.355, 2.365, 2.379, 2.392, 2.403, 2.412, + 2.459, 2.449, 2.427, 2.417, 2.413, 2.407, 2.405, 2.399, 2.388, 2.373, 2.366, 2.353, 2.346, 2.338, 2.334, 2.331, 2.328, 2.321, 2.321, 2.317, 2.317, 2.321, 2.321, 2.337, 2.344, 2.355, 2.363, 2.369, 2.383, 2.392, 2.407, 2.418, + 2.465, 2.453, 2.439, 2.427, 2.417, 2.413, 2.407, 2.405, 2.399, 2.391, 2.376, 2.368, 2.359, 2.358, 2.349, 2.347, 2.346, 2.341, 2.341, 2.335, 2.334, 2.334, 2.341, 2.347, 2.355, 2.363, 2.368, 2.377, 2.388, 2.399, 2.418, 2.421, + 2.467, 2.453, 2.441, 2.431, 2.423, 2.417, 2.416, 2.408, 2.405, 2.399, 2.394, 2.386, 2.381, 2.378, 2.371, 2.369, 2.365, 2.361, 2.359, 2.356, 2.356, 2.356, 2.356, 2.361, 2.367, 2.368, 2.374, 2.387, 2.393, 2.407, 2.418, 2.427, + 2.473, 2.456, 2.447, 2.434, 2.431, 2.423, 2.419, 2.419, 2.413, 2.406, 2.404, 2.403, 2.397, 2.395, 2.394, 2.391, 2.386, 2.381, 2.378, 2.371, 2.371, 2.365, 2.365, 2.372, 2.374, 2.376, 2.379, 2.392, 2.401, 2.413, 2.422, 2.431, + 2.473, 2.469, 2.449, 2.441, 2.433, 2.431, 2.423, 2.419, 2.419, 2.413, 2.412, 2.409, 2.403, 2.402, 2.402, 2.399, 2.399, 2.391, 2.387, 2.385, 2.381, 2.379, 2.379, 2.379, 2.379, 2.379, 2.388, 2.395, 2.409, 2.413, 2.426, 2.431 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 6500, + "table": + [ + 1.308, 1.307, 1.301, 1.301, 1.297, 1.295, 1.295, 1.295, 1.295, 1.295, 1.295, 1.295, 1.295, 1.295, 1.295, 1.296, 1.298, 1.299, 1.298, 1.298, 1.298, 1.299, 1.299, 1.299, 1.299, 1.307, 1.308, 1.311, 1.313, 1.317, 1.322, 1.322, + 1.307, 1.304, 1.298, 1.293, 1.292, 1.291, 1.288, 1.288, 1.288, 1.288, 1.289, 1.291, 1.292, 1.292, 1.294, 1.294, 1.294, 1.293, 1.293, 1.292, 1.293, 1.295, 1.296, 1.298, 1.299, 1.299, 1.302, 1.307, 1.309, 1.313, 1.318, 1.322, + 1.303, 1.298, 1.293, 1.291, 1.289, 1.287, 1.286, 1.286, 1.287, 1.287, 1.288, 1.289, 1.291, 1.292, 1.292, 1.294, 1.293, 1.293, 1.293, 1.292, 1.291, 1.292, 1.295, 1.296, 1.297, 1.298, 1.299, 1.302, 1.306, 1.308, 1.313, 1.317, + 1.299, 1.293, 1.291, 1.289, 1.287, 1.286, 1.286, 1.286, 1.287, 1.287, 1.288, 1.289, 1.291, 1.292, 1.293, 1.293, 1.293, 1.293, 1.293, 1.291, 1.291, 1.291, 1.293, 1.295, 1.296, 1.297, 1.298, 1.301, 1.304, 1.306, 1.308, 1.315, + 1.297, 1.291, 1.289, 1.286, 1.286, 1.286, 1.286, 1.287, 1.287, 1.287, 1.288, 1.289, 1.289, 1.292, 1.294, 1.294, 1.293, 1.293, 1.293, 1.291, 1.289, 1.289, 1.292, 1.294, 1.295, 1.296, 1.297, 1.298, 1.301, 1.305, 1.308, 1.313, + 1.295, 1.289, 1.287, 1.285, 1.285, 1.285, 1.285, 1.286, 1.287, 1.287, 1.287, 1.288, 1.289, 1.293, 1.294, 1.295, 1.295, 1.294, 1.294, 1.292, 1.289, 1.289, 1.289, 1.294, 1.294, 1.295, 1.295, 1.296, 1.299, 1.301, 1.307, 1.308, + 1.292, 1.287, 1.285, 1.284, 1.283, 1.283, 1.283, 1.284, 1.284, 1.286, 1.287, 1.288, 1.289, 1.293, 1.294, 1.295, 1.295, 1.295, 1.294, 1.292, 1.291, 1.289, 1.289, 1.291, 1.293, 1.294, 1.294, 1.295, 1.296, 1.299, 1.305, 1.307, + 1.292, 1.285, 1.282, 1.282, 1.282, 1.282, 1.282, 1.283, 1.283, 1.284, 1.286, 1.287, 1.289, 1.293, 1.295, 1.296, 1.296, 1.295, 1.295, 1.292, 1.291, 1.289, 1.288, 1.288, 1.291, 1.292, 1.293, 1.294, 1.296, 1.297, 1.301, 1.306, + 1.291, 1.283, 1.282, 1.281, 1.281, 1.281, 1.281, 1.283, 1.282, 1.283, 1.283, 1.286, 1.287, 1.289, 1.293, 1.295, 1.296, 1.296, 1.294, 1.291, 1.291, 1.289, 1.288, 1.288, 1.289, 1.291, 1.292, 1.293, 1.296, 1.297, 1.299, 1.301, + 1.288, 1.283, 1.281, 1.279, 1.279, 1.279, 1.281, 1.281, 1.281, 1.282, 1.282, 1.283, 1.286, 1.289, 1.292, 1.295, 1.296, 1.296, 1.293, 1.291, 1.289, 1.288, 1.287, 1.287, 1.289, 1.291, 1.292, 1.292, 1.294, 1.296, 1.297, 1.299, + 1.287, 1.282, 1.279, 1.278, 1.279, 1.279, 1.279, 1.279, 1.281, 1.281, 1.282, 1.283, 1.283, 1.288, 1.291, 1.295, 1.295, 1.294, 1.292, 1.289, 1.288, 1.287, 1.287, 1.287, 1.288, 1.289, 1.291, 1.292, 1.293, 1.294, 1.297, 1.298, + 1.284, 1.279, 1.278, 1.278, 1.279, 1.279, 1.279, 1.279, 1.279, 1.281, 1.282, 1.283, 1.283, 1.288, 1.291, 1.294, 1.294, 1.294, 1.292, 1.288, 1.288, 1.288, 1.289, 1.288, 1.288, 1.288, 1.291, 1.292, 1.293, 1.294, 1.296, 1.298, + 1.284, 1.279, 1.277, 1.277, 1.277, 1.278, 1.279, 1.279, 1.279, 1.281, 1.282, 1.283, 1.285, 1.288, 1.291, 1.294, 1.294, 1.294, 1.292, 1.291, 1.289, 1.289, 1.289, 1.289, 1.288, 1.288, 1.289, 1.293, 1.293, 1.295, 1.296, 1.298, + 1.283, 1.279, 1.276, 1.276, 1.276, 1.277, 1.278, 1.279, 1.281, 1.281, 1.282, 1.283, 1.285, 1.289, 1.291, 1.292, 1.293, 1.293, 1.293, 1.292, 1.289, 1.289, 1.291, 1.291, 1.289, 1.288, 1.289, 1.292, 1.293, 1.294, 1.296, 1.299, + 1.283, 1.279, 1.275, 1.276, 1.276, 1.277, 1.278, 1.279, 1.281, 1.282, 1.282, 1.283, 1.285, 1.289, 1.291, 1.291, 1.292, 1.293, 1.293, 1.293, 1.291, 1.291, 1.291, 1.291, 1.289, 1.288, 1.288, 1.289, 1.292, 1.294, 1.295, 1.299, + 1.283, 1.277, 1.275, 1.275, 1.276, 1.276, 1.279, 1.279, 1.281, 1.282, 1.282, 1.283, 1.285, 1.289, 1.291, 1.291, 1.291, 1.292, 1.293, 1.293, 1.292, 1.291, 1.291, 1.291, 1.291, 1.289, 1.288, 1.289, 1.291, 1.293, 1.295, 1.298, + 1.282, 1.277, 1.275, 1.274, 1.275, 1.276, 1.278, 1.279, 1.281, 1.282, 1.282, 1.282, 1.284, 1.287, 1.289, 1.289, 1.289, 1.289, 1.292, 1.292, 1.292, 1.291, 1.291, 1.291, 1.291, 1.289, 1.288, 1.289, 1.291, 1.292, 1.296, 1.298, + 1.282, 1.276, 1.274, 1.274, 1.275, 1.276, 1.277, 1.279, 1.279, 1.281, 1.281, 1.281, 1.282, 1.286, 1.287, 1.289, 1.289, 1.289, 1.289, 1.291, 1.291, 1.291, 1.291, 1.291, 1.291, 1.289, 1.289, 1.289, 1.291, 1.292, 1.297, 1.298, + 1.282, 1.275, 1.274, 1.274, 1.274, 1.275, 1.275, 1.277, 1.279, 1.279, 1.279, 1.279, 1.281, 1.282, 1.286, 1.287, 1.287, 1.288, 1.288, 1.289, 1.289, 1.289, 1.291, 1.291, 1.291, 1.289, 1.289, 1.289, 1.291, 1.292, 1.297, 1.298, + 1.279, 1.274, 1.273, 1.273, 1.274, 1.275, 1.275, 1.276, 1.277, 1.278, 1.278, 1.278, 1.278, 1.281, 1.283, 1.285, 1.286, 1.286, 1.286, 1.286, 1.286, 1.288, 1.289, 1.289, 1.289, 1.289, 1.289, 1.289, 1.291, 1.292, 1.297, 1.298, + 1.279, 1.274, 1.272, 1.272, 1.273, 1.274, 1.275, 1.274, 1.276, 1.276, 1.276, 1.276, 1.277, 1.278, 1.282, 1.283, 1.283, 1.283, 1.283, 1.283, 1.284, 1.286, 1.288, 1.288, 1.288, 1.288, 1.289, 1.291, 1.291, 1.292, 1.296, 1.298, + 1.279, 1.273, 1.272, 1.272, 1.273, 1.273, 1.274, 1.274, 1.275, 1.275, 1.275, 1.275, 1.276, 1.277, 1.279, 1.281, 1.283, 1.283, 1.282, 1.282, 1.283, 1.284, 1.287, 1.288, 1.288, 1.288, 1.289, 1.291, 1.292, 1.292, 1.296, 1.299, + 1.282, 1.272, 1.271, 1.271, 1.272, 1.272, 1.273, 1.274, 1.274, 1.274, 1.274, 1.274, 1.276, 1.277, 1.279, 1.281, 1.282, 1.282, 1.282, 1.281, 1.282, 1.283, 1.286, 1.287, 1.288, 1.288, 1.288, 1.291, 1.292, 1.292, 1.296, 1.297, + 1.281, 1.273, 1.272, 1.271, 1.271, 1.271, 1.272, 1.273, 1.274, 1.274, 1.274, 1.275, 1.276, 1.278, 1.279, 1.281, 1.282, 1.282, 1.282, 1.282, 1.282, 1.284, 1.285, 1.287, 1.288, 1.288, 1.288, 1.289, 1.292, 1.293, 1.296, 1.297, + 1.281, 1.275, 1.272, 1.271, 1.271, 1.271, 1.272, 1.273, 1.273, 1.273, 1.273, 1.275, 1.277, 1.279, 1.282, 1.282, 1.283, 1.283, 1.283, 1.282, 1.282, 1.284, 1.285, 1.286, 1.288, 1.288, 1.288, 1.289, 1.293, 1.296, 1.297, 1.297, + 1.281, 1.276, 1.272, 1.271, 1.269, 1.269, 1.272, 1.273, 1.274, 1.274, 1.275, 1.276, 1.279, 1.282, 1.283, 1.283, 1.284, 1.284, 1.285, 1.285, 1.284, 1.285, 1.285, 1.286, 1.289, 1.289, 1.289, 1.289, 1.293, 1.295, 1.297, 1.301, + 1.286, 1.276, 1.272, 1.271, 1.271, 1.269, 1.272, 1.273, 1.274, 1.276, 1.276, 1.276, 1.279, 1.282, 1.284, 1.285, 1.285, 1.285, 1.286, 1.286, 1.285, 1.285, 1.286, 1.286, 1.289, 1.289, 1.289, 1.291, 1.292, 1.295, 1.301, 1.302, + 1.286, 1.277, 1.272, 1.272, 1.271, 1.271, 1.272, 1.273, 1.276, 1.276, 1.276, 1.277, 1.279, 1.282, 1.284, 1.285, 1.285, 1.285, 1.286, 1.286, 1.286, 1.286, 1.286, 1.286, 1.288, 1.289, 1.289, 1.291, 1.292, 1.294, 1.301, 1.304, + 1.285, 1.276, 1.274, 1.272, 1.271, 1.271, 1.271, 1.273, 1.274, 1.276, 1.276, 1.278, 1.279, 1.282, 1.283, 1.284, 1.285, 1.285, 1.286, 1.286, 1.286, 1.286, 1.286, 1.287, 1.287, 1.288, 1.291, 1.292, 1.292, 1.295, 1.301, 1.307, + 1.285, 1.278, 1.275, 1.273, 1.272, 1.272, 1.271, 1.272, 1.274, 1.275, 1.276, 1.279, 1.279, 1.281, 1.284, 1.284, 1.285, 1.285, 1.285, 1.285, 1.285, 1.285, 1.286, 1.287, 1.287, 1.287, 1.291, 1.292, 1.293, 1.297, 1.301, 1.307, + 1.283, 1.277, 1.275, 1.273, 1.272, 1.271, 1.269, 1.271, 1.272, 1.274, 1.275, 1.276, 1.279, 1.279, 1.279, 1.284, 1.284, 1.284, 1.284, 1.284, 1.284, 1.284, 1.285, 1.285, 1.287, 1.287, 1.291, 1.292, 1.293, 1.298, 1.301, 1.301, + 1.283, 1.277, 1.275, 1.272, 1.271, 1.268, 1.268, 1.269, 1.271, 1.272, 1.273, 1.272, 1.277, 1.279, 1.279, 1.281, 1.281, 1.282, 1.282, 1.282, 1.281, 1.283, 1.285, 1.285, 1.287, 1.287, 1.288, 1.291, 1.293, 1.298, 1.299, 1.299 + ] + } + ], + "luminance_lut": + [ + 4.903, 4.653, 4.193, 3.818, 3.501, 3.239, 3.015, 2.824, 2.666, 2.529, 2.414, 2.316, 2.241, 2.205, 2.184, 2.178, 2.178, 2.178, 2.188, 2.216, 2.269, 2.359, 2.465, 2.597, 2.738, 2.915, 3.125, 3.374, 3.662, 4.011, 4.418, 4.656, + 4.653, 4.392, 3.985, 3.621, 3.323, 3.071, 2.855, 2.678, 2.534, 2.414, 2.316, 2.234, 2.168, 2.116, 2.079, 2.058, 2.058, 2.065, 2.091, 2.135, 2.196, 2.269, 2.359, 2.465, 2.599, 2.761, 2.962, 3.195, 3.472, 3.801, 4.188, 4.418, + 4.392, 4.149, 3.766, 3.423, 3.139, 2.901, 2.697, 2.534, 2.409, 2.286, 2.191, 2.106, 2.034, 1.977, 1.938, 1.914, 1.914, 1.923, 1.952, 1.998, 2.063, 2.139, 2.231, 2.341, 2.465, 2.612, 2.796, 3.019, 3.284, 3.596, 3.963, 4.188, + 4.149, 3.936, 3.566, 3.252, 2.982, 2.749, 2.561, 2.409, 2.286, 2.169, 2.066, 1.976, 1.901, 1.842, 1.801, 1.777, 1.777, 1.785, 1.814, 1.861, 1.929, 2.012, 2.107, 2.227, 2.341, 2.483, 2.651, 2.863, 3.119, 3.415, 3.764, 3.979, + 3.951, 3.743, 3.392, 3.091, 2.831, 2.615, 2.438, 2.297, 2.169, 2.061, 1.942, 1.847, 1.768, 1.711, 1.669, 1.646, 1.646, 1.653, 1.681, 1.729, 1.796, 1.882, 1.987, 2.107, 2.227, 2.367, 2.528, 2.727, 2.968, 3.254, 3.583, 3.805, + 3.785, 3.572, 3.242, 2.952, 2.703, 2.501, 2.336, 2.195, 2.061, 1.942, 1.825, 1.726, 1.651, 1.591, 1.549, 1.527, 1.527, 1.533, 1.562, 1.611, 1.676, 1.763, 1.881, 1.987, 2.119, 2.263, 2.422, 2.608, 2.833, 3.109, 3.428, 3.652, + 3.644, 3.424, 3.107, 2.828, 2.592, 2.401, 2.244, 2.097, 1.954, 1.825, 1.724, 1.614, 1.538, 1.479, 1.441, 1.419, 1.419, 1.424, 1.452, 1.498, 1.564, 1.656, 1.763, 1.881, 2.018, 2.169, 2.325, 2.504, 2.718, 2.979, 3.291, 3.519, + 3.525, 3.297, 2.986, 2.718, 2.495, 2.318, 2.156, 2.004, 1.856, 1.724, 1.614, 1.524, 1.439, 1.384, 1.348, 1.328, 1.328, 1.332, 1.358, 1.402, 1.466, 1.564, 1.656, 1.781, 1.923, 2.076, 2.241, 2.414, 2.616, 2.864, 3.166, 3.411, + 3.413, 3.191, 2.889, 2.631, 2.421, 2.245, 2.082, 1.924, 1.772, 1.637, 1.524, 1.439, 1.359, 1.307, 1.272, 1.252, 1.252, 1.258, 1.283, 1.324, 1.391, 1.466, 1.569, 1.694, 1.838, 1.999, 2.166, 2.341, 2.537, 2.774, 3.069, 3.306, + 3.333, 3.086, 2.793, 2.547, 2.349, 2.174, 2.006, 1.843, 1.689, 1.556, 1.444, 1.359, 1.299, 1.239, 1.207, 1.189, 1.189, 1.194, 1.219, 1.261, 1.324, 1.391, 1.491, 1.614, 1.757, 1.921, 2.092, 2.269, 2.463, 2.687, 2.967, 3.225, + 3.256, 3.016, 2.727, 2.491, 2.295, 2.118, 1.947, 1.779, 1.625, 1.494, 1.383, 1.299, 1.239, 1.189, 1.158, 1.141, 1.141, 1.147, 1.171, 1.217, 1.261, 1.334, 1.429, 1.551, 1.695, 1.857, 2.035, 2.217, 2.409, 2.628, 2.895, 3.153, + 3.211, 2.946, 2.666, 2.433, 2.246, 2.068, 1.891, 1.721, 1.569, 1.437, 1.332, 1.249, 1.189, 1.156, 1.113, 1.096, 1.096, 1.102, 1.133, 1.171, 1.217, 1.286, 1.378, 1.496, 1.638, 1.802, 1.981, 2.166, 2.358, 2.573, 2.832, 3.105, + 3.165, 2.901, 2.625, 2.401, 2.211, 2.031, 1.848, 1.678, 1.528, 1.398, 1.294, 1.216, 1.156, 1.113, 1.085, 1.061, 1.061, 1.067, 1.102, 1.133, 1.185, 1.253, 1.341, 1.456, 1.597, 1.761, 1.942, 2.131, 2.325, 2.534, 2.793, 3.069, + 3.132, 2.862, 2.593, 2.371, 2.182, 1.997, 1.816, 1.646, 1.496, 1.367, 1.267, 1.189, 1.131, 1.085, 1.061, 1.032, 1.032, 1.043, 1.067, 1.108, 1.161, 1.228, 1.313, 1.426, 1.566, 1.728, 1.911, 2.102, 2.297, 2.508, 2.761, 3.039, + 3.118, 2.833, 2.566, 2.344, 2.157, 1.972, 1.786, 1.618, 1.469, 1.343, 1.244, 1.169, 1.111, 1.063, 1.032, 1.018, 1.009, 1.027, 1.043, 1.086, 1.141, 1.207, 1.292, 1.403, 1.541, 1.703, 1.884, 2.077, 2.274, 2.484, 2.735, 3.031, + 3.111, 2.815, 2.553, 2.334, 2.145, 1.959, 1.774, 1.605, 1.455, 1.331, 1.234, 1.159, 1.101, 1.053, 1.018, 1.005, 1.004, 1.006, 1.033, 1.077, 1.132, 1.199, 1.283, 1.393, 1.531, 1.692, 1.873, 2.067, 2.265, 2.477, 2.726, 3.028, + 3.111, 2.815, 2.552, 2.333, 2.145, 1.959, 1.774, 1.605, 1.455, 1.331, 1.234, 1.159, 1.101, 1.053, 1.018, 1.001, 1.001, 1.006, 1.033, 1.077, 1.132, 1.199, 1.283, 1.393, 1.531, 1.692, 1.873, 2.067, 2.265, 2.475, 2.726, 3.028, + 3.111, 2.822, 2.552, 2.333, 2.146, 1.961, 1.775, 1.607, 1.457, 1.333, 1.236, 1.161, 1.103, 1.056, 1.021, 1.015, 1.004, 1.017, 1.037, 1.079, 1.135, 1.201, 1.287, 1.395, 1.533, 1.695, 1.876, 2.067, 2.266, 2.475, 2.727, 3.028, + 3.123, 2.844, 2.579, 2.357, 2.166, 1.983, 1.797, 1.627, 1.477, 1.351, 1.253, 1.177, 1.119, 1.073, 1.046, 1.021, 1.021, 1.037, 1.055, 1.097, 1.151, 1.218, 1.305, 1.416, 1.555, 1.717, 1.898, 2.089, 2.287, 2.499, 2.753, 3.041, + 3.149, 2.881, 2.609, 2.383, 2.191, 2.009, 1.827, 1.658, 1.506, 1.378, 1.277, 1.199, 1.142, 1.099, 1.073, 1.046, 1.046, 1.055, 1.089, 1.121, 1.173, 1.242, 1.331, 1.444, 1.584, 1.747, 1.928, 2.118, 2.315, 2.528, 2.787, 3.071, + 3.187, 2.933, 2.654, 2.422, 2.228, 2.049, 1.871, 1.699, 1.547, 1.417, 1.313, 1.232, 1.173, 1.136, 1.099, 1.081, 1.081, 1.089, 1.121, 1.152, 1.205, 1.275, 1.368, 1.484, 1.626, 1.789, 1.971, 2.159, 2.354, 2.571, 2.834, 3.107, + 3.247, 2.989, 2.703, 2.464, 2.269, 2.091, 1.915, 1.748, 1.595, 1.464, 1.356, 1.272, 1.216, 1.173, 1.136, 1.121, 1.121, 1.128, 1.152, 1.199, 1.242, 1.316, 1.411, 1.532, 1.675, 1.839, 2.019, 2.202, 2.398, 2.618, 2.891, 3.164, + 3.306, 3.068, 2.776, 2.524, 2.324, 2.148, 1.979, 1.814, 1.661, 1.526, 1.416, 1.328, 1.272, 1.216, 1.185, 1.169, 1.169, 1.177, 1.199, 1.242, 1.297, 1.371, 1.473, 1.596, 1.741, 1.904, 2.081, 2.263, 2.459, 2.687, 2.971, 3.221, + 3.394, 3.161, 2.855, 2.598, 2.387, 2.211, 2.047, 1.885, 1.734, 1.599, 1.485, 1.399, 1.328, 1.273, 1.242, 1.224, 1.224, 1.231, 1.256, 1.297, 1.369, 1.438, 1.542, 1.669, 1.813, 1.976, 2.149, 2.331, 2.527, 2.764, 3.057, 3.304, + 3.496, 3.263, 2.949, 2.684, 2.463, 2.279, 2.118, 1.965, 1.817, 1.681, 1.568, 1.485, 1.399, 1.345, 1.309, 1.291, 1.291, 1.299, 1.325, 1.369, 1.438, 1.528, 1.623, 1.751, 1.897, 2.056, 2.225, 2.405, 2.608, 2.858, 3.162, 3.402, + 3.611, 3.397, 3.072, 2.793, 2.558, 2.368, 2.205, 2.057, 1.914, 1.779, 1.673, 1.568, 1.492, 1.435, 1.398, 1.377, 1.377, 1.385, 1.414, 1.461, 1.528, 1.623, 1.722, 1.851, 1.996, 2.152, 2.318, 2.499, 2.712, 2.977, 3.294, 3.519, + 3.764, 3.537, 3.204, 2.908, 2.661, 2.459, 2.294, 2.147, 2.012, 1.885, 1.779, 1.673, 1.595, 1.535, 1.496, 1.476, 1.476, 1.484, 1.515, 1.564, 1.631, 1.722, 1.837, 1.954, 2.096, 2.247, 2.411, 2.599, 2.825, 3.108, 3.431, 3.662, + 3.919, 3.704, 3.353, 3.046, 2.787, 2.571, 2.393, 2.247, 2.117, 2.007, 1.885, 1.789, 1.709, 1.651, 1.613, 1.591, 1.591, 1.599, 1.631, 1.679, 1.749, 1.837, 1.954, 2.069, 2.204, 2.352, 2.518, 2.719, 2.962, 3.257, 3.591, 3.815, + 4.126, 3.894, 3.521, 3.203, 2.931, 2.702, 2.512, 2.358, 2.238, 2.117, 2.007, 1.915, 1.839, 1.779, 1.739, 1.719, 1.719, 1.726, 1.759, 1.808, 1.877, 1.965, 2.069, 2.202, 2.319, 2.467, 2.645, 2.859, 3.122, 3.427, 3.784, 4.019, + 4.391, 4.126, 3.721, 3.389, 3.103, 2.857, 2.655, 2.493, 2.358, 2.238, 2.138, 2.049, 1.976, 1.921, 1.882, 1.859, 1.859, 1.868, 1.899, 1.949, 2.015, 2.102, 2.202, 2.319, 2.456, 2.605, 2.794, 3.026, 3.305, 3.629, 4.019, 4.275, + 4.671, 4.391, 3.966, 3.603, 3.297, 3.041, 2.821, 2.643, 2.493, 2.374, 2.277, 2.191, 2.122, 2.069, 2.031, 2.011, 2.011, 2.021, 2.049, 2.097, 2.163, 2.245, 2.343, 2.456, 2.601, 2.762, 2.972, 3.225, 3.514, 3.867, 4.275, 4.547, + 4.899, 4.671, 4.209, 3.824, 3.501, 3.229, 2.999, 2.805, 2.643, 2.493, 2.374, 2.277, 2.208, 2.172, 2.153, 2.148, 2.148, 2.148, 2.164, 2.192, 2.245, 2.343, 2.456, 2.592, 2.749, 2.935, 3.161, 3.423, 3.736, 4.121, 4.547, 4.751 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2300, + "ccm": + [ + 1.77591, -0.16036, -0.61554, + -0.26235, 1.66133, -0.39898, + -0.22474, -1.94117, 3.16591 + ] + }, + { + "ct": 2700, + "ccm": + [ + 1.54016, 0.02018, -0.56034, + -0.27333, 1.78261, -0.50928, + -0.13821, -1.22069, 2.35891 + ] + }, + { + "ct": 3000, + "ccm": + [ + 1.73266, -0.19227, -0.54039, + -0.44685, 2.04704, -0.60018, + -0.13631, -0.94323, 2.07953 + ] + }, + { + "ct": 4000, + "ccm": + [ + 1.70137, -0.23462, -0.46675, + -0.34126, 1.80328, -0.46202, + -0.14242, -0.75105, 1.89347 + ] + }, + { + "ct": 4150, + "ccm": + [ + 2.09386, -0.69875, -0.39511, + -0.38239, 1.78872, -0.40633, + -0.11896, -0.74324, 1.86219 + ] + }, + { + "ct": 6500, + "ccm": + [ + 1.69679, -0.27504, -0.42174, + -0.23619, 1.87692, -0.64073, + -0.07905, -0.61889, 1.69795 + ] + } + ] + } + }, + { + "rpi.cac": { } + }, + { + "rpi.sharpen": + { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + } + }, + { + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + }, + "SingleExposure": + { + "cadence": [ 1 ], + "channel_map": + { + "short": 1 + }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": + { + "night": 3 + }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 3.0, + "max": 15.0, + "default": 4.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 1.0, + "step_fine": 0.25, + "contrast_ratio": 0.75, + "pdaf_gain": -0.02, + "pdaf_squelch": 0.125, + "max_slew": 2.0, + "pdaf_frames": 0, + "dropout_frames": 0, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 16, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 0, 15.0, 1023 ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/ov9281_mono.json b/src/ipa/rpi/pisp/data/ov9281_mono.json new file mode 100644 index 00000000..54229b83 --- /dev/null +++ b/src/ipa/rpi/pisp/data/ov9281_mono.json @@ -0,0 +1,215 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 2000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 800, + "reference_Y": 20000 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.5 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 3.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 30000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.4, + 1000, 0.4 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "n_iter": 0, + "luminance_strength": 1.0, + "corner_strength": 1.5 + } + }, + { + "rpi.contrast": + { + "ce_enable": 0, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/se327m12.json b/src/ipa/rpi/pisp/data/se327m12.json new file mode 100644 index 00000000..46f2378c --- /dev/null +++ b/src/ipa/rpi/pisp/data/se327m12.json @@ -0,0 +1,639 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 6873, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 800, + "reference_Y": 12293 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 1.986 + } + }, + { + "rpi.geq": + { + "offset": 207, + "slope": 0.00539 + } + }, + { + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 8000 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8600 + } + }, + "bayes": 1, + "ct_curve": + [ + 2900.0, 0.9217, 0.3657, + 3600.0, 0.7876, 0.4651, + 4600.0, 0.6807, 0.5684, + 5800.0, 0.5937, 0.6724, + 8100.0, 0.5447, 0.7403 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.0162, + "transverse_neg": 0.0204 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.5, + "calibrations_Cr": [ + { + "ct": 4000, + "table": + [ + 1.481, 1.476, 1.471, 1.461, 1.45, 1.441, 1.431, 1.424, 1.418, 1.412, 1.406, 1.401, 1.396, 1.393, 1.39, 1.389, 1.389, 1.389, 1.389, 1.39, 1.391, 1.393, 1.395, 1.398, 1.401, 1.405, 1.411, 1.417, 1.423, 1.429, 1.433, 1.437, + 1.478, 1.472, 1.466, 1.456, 1.446, 1.436, 1.427, 1.42, 1.414, 1.408, 1.402, 1.398, 1.394, 1.391, 1.388, 1.387, 1.387, 1.387, 1.387, 1.387, 1.389, 1.391, 1.392, 1.395, 1.399, 1.403, 1.408, 1.414, 1.42, 1.427, 1.43, 1.434, + 1.475, 1.468, 1.461, 1.451, 1.441, 1.432, 1.423, 1.416, 1.41, 1.404, 1.399, 1.395, 1.392, 1.389, 1.387, 1.385, 1.384, 1.384, 1.384, 1.385, 1.387, 1.388, 1.39, 1.392, 1.396, 1.401, 1.405, 1.411, 1.418, 1.424, 1.428, 1.431, + 1.472, 1.464, 1.456, 1.446, 1.437, 1.428, 1.419, 1.412, 1.406, 1.401, 1.395, 1.392, 1.39, 1.387, 1.385, 1.383, 1.382, 1.382, 1.382, 1.382, 1.384, 1.386, 1.387, 1.389, 1.394, 1.398, 1.403, 1.407, 1.415, 1.422, 1.425, 1.429, + 1.469, 1.46, 1.451, 1.442, 1.433, 1.425, 1.417, 1.41, 1.403, 1.398, 1.393, 1.39, 1.388, 1.385, 1.382, 1.381, 1.38, 1.38, 1.38, 1.381, 1.382, 1.384, 1.385, 1.387, 1.391, 1.395, 1.399, 1.404, 1.411, 1.418, 1.422, 1.426, + 1.467, 1.457, 1.447, 1.438, 1.429, 1.422, 1.414, 1.407, 1.401, 1.396, 1.392, 1.388, 1.385, 1.383, 1.38, 1.378, 1.378, 1.378, 1.378, 1.379, 1.38, 1.381, 1.383, 1.386, 1.388, 1.391, 1.396, 1.401, 1.407, 1.414, 1.419, 1.424, + 1.465, 1.454, 1.443, 1.435, 1.427, 1.419, 1.412, 1.405, 1.399, 1.394, 1.39, 1.387, 1.384, 1.381, 1.378, 1.377, 1.377, 1.377, 1.377, 1.377, 1.378, 1.379, 1.382, 1.384, 1.386, 1.388, 1.393, 1.398, 1.405, 1.411, 1.416, 1.421, + 1.464, 1.453, 1.443, 1.434, 1.426, 1.418, 1.411, 1.405, 1.398, 1.393, 1.389, 1.385, 1.382, 1.38, 1.378, 1.376, 1.376, 1.376, 1.376, 1.376, 1.377, 1.378, 1.38, 1.382, 1.384, 1.387, 1.392, 1.397, 1.403, 1.409, 1.415, 1.42, + 1.462, 1.452, 1.442, 1.433, 1.425, 1.418, 1.411, 1.404, 1.397, 1.392, 1.387, 1.384, 1.381, 1.379, 1.377, 1.376, 1.375, 1.374, 1.374, 1.375, 1.375, 1.376, 1.378, 1.38, 1.383, 1.386, 1.39, 1.395, 1.402, 1.408, 1.413, 1.419, + 1.462, 1.452, 1.441, 1.432, 1.424, 1.417, 1.41, 1.403, 1.397, 1.391, 1.387, 1.383, 1.38, 1.378, 1.377, 1.375, 1.374, 1.374, 1.374, 1.374, 1.374, 1.375, 1.377, 1.379, 1.381, 1.384, 1.389, 1.394, 1.4, 1.407, 1.412, 1.417, + 1.461, 1.451, 1.441, 1.432, 1.423, 1.416, 1.409, 1.403, 1.396, 1.391, 1.387, 1.383, 1.381, 1.379, 1.377, 1.375, 1.374, 1.373, 1.373, 1.374, 1.374, 1.374, 1.376, 1.378, 1.38, 1.383, 1.388, 1.392, 1.399, 1.405, 1.411, 1.416, + 1.461, 1.45, 1.44, 1.431, 1.422, 1.415, 1.409, 1.402, 1.396, 1.391, 1.386, 1.384, 1.382, 1.379, 1.377, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.374, 1.375, 1.377, 1.379, 1.382, 1.386, 1.39, 1.397, 1.404, 1.41, 1.415, + 1.461, 1.45, 1.44, 1.431, 1.422, 1.415, 1.408, 1.401, 1.395, 1.39, 1.386, 1.383, 1.381, 1.379, 1.377, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.374, 1.375, 1.376, 1.379, 1.381, 1.385, 1.39, 1.396, 1.403, 1.409, 1.414, + 1.461, 1.45, 1.44, 1.43, 1.421, 1.414, 1.407, 1.4, 1.394, 1.39, 1.386, 1.383, 1.381, 1.379, 1.377, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.374, 1.375, 1.376, 1.378, 1.381, 1.385, 1.39, 1.396, 1.402, 1.408, 1.414, + 1.461, 1.45, 1.44, 1.43, 1.42, 1.413, 1.406, 1.399, 1.394, 1.389, 1.385, 1.382, 1.38, 1.378, 1.377, 1.375, 1.374, 1.373, 1.372, 1.372, 1.373, 1.374, 1.375, 1.376, 1.378, 1.38, 1.385, 1.39, 1.396, 1.401, 1.407, 1.413, + 1.461, 1.45, 1.439, 1.43, 1.42, 1.412, 1.405, 1.399, 1.393, 1.388, 1.385, 1.382, 1.379, 1.378, 1.376, 1.375, 1.374, 1.373, 1.372, 1.372, 1.373, 1.374, 1.374, 1.376, 1.378, 1.381, 1.385, 1.389, 1.395, 1.401, 1.407, 1.413, + 1.461, 1.45, 1.439, 1.43, 1.42, 1.412, 1.404, 1.398, 1.392, 1.388, 1.384, 1.381, 1.379, 1.377, 1.376, 1.375, 1.374, 1.373, 1.372, 1.372, 1.372, 1.373, 1.374, 1.376, 1.378, 1.381, 1.385, 1.389, 1.395, 1.401, 1.408, 1.414, + 1.461, 1.45, 1.439, 1.429, 1.42, 1.412, 1.404, 1.397, 1.391, 1.387, 1.384, 1.381, 1.378, 1.376, 1.375, 1.374, 1.374, 1.373, 1.372, 1.372, 1.372, 1.373, 1.374, 1.376, 1.379, 1.382, 1.385, 1.389, 1.395, 1.401, 1.408, 1.414, + 1.461, 1.45, 1.439, 1.429, 1.42, 1.412, 1.404, 1.398, 1.391, 1.387, 1.383, 1.381, 1.378, 1.376, 1.375, 1.374, 1.373, 1.373, 1.373, 1.372, 1.373, 1.373, 1.374, 1.376, 1.379, 1.382, 1.385, 1.389, 1.395, 1.401, 1.408, 1.414, + 1.462, 1.45, 1.439, 1.429, 1.42, 1.412, 1.404, 1.398, 1.392, 1.387, 1.383, 1.38, 1.378, 1.376, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.373, 1.374, 1.375, 1.376, 1.379, 1.382, 1.385, 1.39, 1.395, 1.401, 1.408, 1.414, + 1.462, 1.451, 1.439, 1.43, 1.421, 1.413, 1.405, 1.399, 1.393, 1.388, 1.383, 1.38, 1.378, 1.376, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.374, 1.374, 1.375, 1.377, 1.379, 1.382, 1.386, 1.39, 1.396, 1.402, 1.408, 1.414, + 1.462, 1.451, 1.44, 1.431, 1.422, 1.414, 1.406, 1.399, 1.393, 1.388, 1.383, 1.38, 1.378, 1.376, 1.375, 1.374, 1.373, 1.373, 1.373, 1.373, 1.373, 1.374, 1.375, 1.377, 1.379, 1.382, 1.386, 1.391, 1.396, 1.402, 1.408, 1.414, + 1.462, 1.452, 1.441, 1.432, 1.423, 1.415, 1.406, 1.4, 1.393, 1.389, 1.384, 1.381, 1.378, 1.376, 1.375, 1.374, 1.373, 1.373, 1.372, 1.372, 1.373, 1.374, 1.375, 1.377, 1.38, 1.383, 1.387, 1.391, 1.397, 1.402, 1.408, 1.414, + 1.462, 1.452, 1.442, 1.433, 1.424, 1.416, 1.407, 1.4, 1.394, 1.389, 1.384, 1.381, 1.378, 1.376, 1.375, 1.373, 1.373, 1.372, 1.372, 1.372, 1.372, 1.373, 1.375, 1.377, 1.38, 1.383, 1.387, 1.391, 1.397, 1.402, 1.408, 1.414, + 1.464, 1.453, 1.443, 1.434, 1.425, 1.416, 1.408, 1.401, 1.394, 1.389, 1.384, 1.381, 1.378, 1.376, 1.374, 1.373, 1.372, 1.371, 1.371, 1.371, 1.372, 1.373, 1.374, 1.376, 1.379, 1.382, 1.386, 1.391, 1.397, 1.402, 1.409, 1.416, + 1.465, 1.454, 1.444, 1.435, 1.425, 1.417, 1.408, 1.401, 1.395, 1.389, 1.384, 1.381, 1.379, 1.376, 1.374, 1.372, 1.37, 1.369, 1.369, 1.37, 1.371, 1.373, 1.374, 1.376, 1.379, 1.382, 1.386, 1.39, 1.396, 1.402, 1.41, 1.417, + 1.466, 1.456, 1.446, 1.436, 1.426, 1.418, 1.41, 1.403, 1.396, 1.39, 1.384, 1.381, 1.379, 1.377, 1.375, 1.372, 1.37, 1.369, 1.369, 1.37, 1.371, 1.373, 1.374, 1.376, 1.379, 1.383, 1.387, 1.391, 1.397, 1.404, 1.411, 1.418, + 1.467, 1.457, 1.448, 1.437, 1.427, 1.419, 1.412, 1.404, 1.397, 1.391, 1.385, 1.382, 1.38, 1.378, 1.375, 1.373, 1.371, 1.37, 1.37, 1.371, 1.372, 1.373, 1.375, 1.377, 1.381, 1.384, 1.388, 1.392, 1.399, 1.405, 1.413, 1.42, + 1.469, 1.459, 1.449, 1.439, 1.428, 1.421, 1.414, 1.406, 1.398, 1.392, 1.386, 1.383, 1.381, 1.379, 1.376, 1.374, 1.372, 1.371, 1.371, 1.371, 1.372, 1.374, 1.375, 1.378, 1.382, 1.386, 1.389, 1.394, 1.4, 1.407, 1.414, 1.422, + 1.47, 1.461, 1.452, 1.441, 1.431, 1.423, 1.416, 1.409, 1.401, 1.395, 1.388, 1.385, 1.382, 1.38, 1.377, 1.375, 1.374, 1.373, 1.373, 1.373, 1.374, 1.375, 1.377, 1.379, 1.383, 1.388, 1.392, 1.397, 1.405, 1.412, 1.417, 1.423, + 1.472, 1.463, 1.454, 1.444, 1.434, 1.426, 1.418, 1.412, 1.405, 1.398, 1.391, 1.387, 1.383, 1.381, 1.379, 1.377, 1.376, 1.375, 1.375, 1.375, 1.376, 1.377, 1.378, 1.381, 1.385, 1.39, 1.395, 1.401, 1.409, 1.417, 1.421, 1.425, + 1.474, 1.465, 1.457, 1.447, 1.437, 1.429, 1.421, 1.414, 1.409, 1.401, 1.394, 1.388, 1.385, 1.382, 1.38, 1.378, 1.378, 1.377, 1.377, 1.377, 1.378, 1.378, 1.38, 1.382, 1.387, 1.392, 1.399, 1.405, 1.414, 1.422, 1.424, 1.426 + ] + }, + { + "ct": 5000, + "table": + [ + 1.742, 1.732, 1.722, 1.707, 1.691, 1.677, 1.664, 1.652, 1.642, 1.633, 1.626, 1.62, 1.615, 1.612, 1.61, 1.608, 1.608, 1.607, 1.606, 1.607, 1.608, 1.61, 1.614, 1.618, 1.623, 1.627, 1.635, 1.643, 1.654, 1.666, 1.673, 1.681, + 1.737, 1.726, 1.715, 1.7, 1.685, 1.671, 1.658, 1.648, 1.639, 1.63, 1.622, 1.616, 1.611, 1.608, 1.606, 1.605, 1.604, 1.603, 1.603, 1.603, 1.605, 1.607, 1.611, 1.615, 1.62, 1.625, 1.631, 1.639, 1.65, 1.661, 1.669, 1.677, + 1.732, 1.721, 1.709, 1.694, 1.679, 1.665, 1.652, 1.643, 1.635, 1.627, 1.619, 1.613, 1.607, 1.604, 1.603, 1.601, 1.6, 1.599, 1.599, 1.6, 1.602, 1.604, 1.608, 1.612, 1.617, 1.622, 1.628, 1.635, 1.646, 1.657, 1.665, 1.674, + 1.727, 1.715, 1.703, 1.688, 1.673, 1.66, 1.647, 1.639, 1.632, 1.624, 1.616, 1.61, 1.604, 1.601, 1.599, 1.598, 1.596, 1.596, 1.596, 1.597, 1.599, 1.602, 1.605, 1.609, 1.614, 1.619, 1.625, 1.632, 1.642, 1.653, 1.661, 1.67, + 1.722, 1.71, 1.699, 1.684, 1.668, 1.656, 1.643, 1.635, 1.628, 1.62, 1.613, 1.607, 1.601, 1.598, 1.596, 1.595, 1.593, 1.593, 1.593, 1.594, 1.596, 1.598, 1.602, 1.606, 1.61, 1.615, 1.622, 1.629, 1.639, 1.649, 1.657, 1.666, + 1.716, 1.705, 1.694, 1.679, 1.663, 1.651, 1.64, 1.631, 1.623, 1.616, 1.61, 1.604, 1.599, 1.595, 1.594, 1.592, 1.591, 1.59, 1.59, 1.591, 1.592, 1.595, 1.599, 1.604, 1.607, 1.612, 1.619, 1.627, 1.636, 1.644, 1.653, 1.661, + 1.712, 1.701, 1.69, 1.675, 1.659, 1.647, 1.636, 1.628, 1.619, 1.613, 1.607, 1.602, 1.597, 1.593, 1.591, 1.589, 1.588, 1.587, 1.587, 1.588, 1.59, 1.592, 1.596, 1.601, 1.604, 1.609, 1.616, 1.624, 1.632, 1.641, 1.649, 1.658, + 1.71, 1.699, 1.687, 1.672, 1.657, 1.645, 1.633, 1.625, 1.618, 1.611, 1.605, 1.6, 1.595, 1.592, 1.589, 1.587, 1.586, 1.586, 1.586, 1.587, 1.588, 1.59, 1.594, 1.597, 1.601, 1.606, 1.613, 1.621, 1.629, 1.638, 1.647, 1.657, + 1.708, 1.696, 1.683, 1.669, 1.654, 1.642, 1.631, 1.623, 1.616, 1.609, 1.602, 1.597, 1.593, 1.59, 1.587, 1.585, 1.584, 1.584, 1.584, 1.585, 1.587, 1.588, 1.591, 1.594, 1.598, 1.604, 1.61, 1.618, 1.626, 1.635, 1.645, 1.655, + 1.705, 1.693, 1.68, 1.666, 1.652, 1.64, 1.628, 1.62, 1.614, 1.607, 1.6, 1.595, 1.592, 1.588, 1.586, 1.584, 1.583, 1.582, 1.583, 1.584, 1.585, 1.587, 1.589, 1.592, 1.596, 1.602, 1.608, 1.615, 1.624, 1.633, 1.644, 1.654, + 1.703, 1.69, 1.677, 1.663, 1.649, 1.638, 1.626, 1.618, 1.611, 1.604, 1.598, 1.593, 1.59, 1.587, 1.584, 1.582, 1.581, 1.581, 1.582, 1.583, 1.584, 1.585, 1.587, 1.59, 1.595, 1.6, 1.607, 1.614, 1.623, 1.633, 1.643, 1.653, + 1.7, 1.687, 1.674, 1.66, 1.646, 1.635, 1.625, 1.616, 1.609, 1.602, 1.596, 1.591, 1.588, 1.585, 1.583, 1.581, 1.58, 1.58, 1.581, 1.582, 1.583, 1.584, 1.586, 1.589, 1.594, 1.599, 1.606, 1.613, 1.622, 1.632, 1.642, 1.652, + 1.698, 1.685, 1.671, 1.658, 1.644, 1.633, 1.623, 1.615, 1.607, 1.6, 1.594, 1.59, 1.587, 1.584, 1.582, 1.58, 1.579, 1.579, 1.58, 1.581, 1.582, 1.583, 1.585, 1.588, 1.593, 1.598, 1.605, 1.611, 1.621, 1.631, 1.641, 1.652, + 1.698, 1.683, 1.669, 1.655, 1.642, 1.631, 1.621, 1.613, 1.605, 1.599, 1.593, 1.589, 1.586, 1.583, 1.581, 1.579, 1.578, 1.578, 1.579, 1.58, 1.581, 1.582, 1.584, 1.587, 1.593, 1.598, 1.604, 1.61, 1.62, 1.629, 1.641, 1.652, + 1.697, 1.682, 1.666, 1.653, 1.639, 1.629, 1.619, 1.611, 1.603, 1.597, 1.591, 1.587, 1.585, 1.583, 1.58, 1.579, 1.578, 1.577, 1.578, 1.579, 1.58, 1.582, 1.584, 1.587, 1.592, 1.598, 1.603, 1.608, 1.618, 1.628, 1.64, 1.652, + 1.697, 1.681, 1.665, 1.651, 1.638, 1.628, 1.618, 1.61, 1.602, 1.597, 1.591, 1.588, 1.585, 1.582, 1.58, 1.578, 1.577, 1.577, 1.577, 1.578, 1.579, 1.581, 1.584, 1.587, 1.592, 1.598, 1.603, 1.608, 1.618, 1.628, 1.64, 1.652, + 1.697, 1.681, 1.664, 1.65, 1.637, 1.626, 1.616, 1.609, 1.602, 1.596, 1.592, 1.588, 1.585, 1.582, 1.579, 1.578, 1.577, 1.576, 1.577, 1.577, 1.579, 1.581, 1.584, 1.587, 1.593, 1.598, 1.603, 1.608, 1.618, 1.628, 1.641, 1.653, + 1.697, 1.68, 1.663, 1.649, 1.636, 1.625, 1.615, 1.608, 1.601, 1.596, 1.592, 1.588, 1.585, 1.582, 1.579, 1.577, 1.577, 1.576, 1.576, 1.577, 1.578, 1.58, 1.584, 1.588, 1.593, 1.598, 1.603, 1.608, 1.619, 1.629, 1.641, 1.653, + 1.697, 1.68, 1.663, 1.649, 1.635, 1.625, 1.615, 1.607, 1.6, 1.596, 1.592, 1.588, 1.584, 1.581, 1.579, 1.577, 1.577, 1.576, 1.576, 1.577, 1.579, 1.581, 1.585, 1.588, 1.593, 1.598, 1.604, 1.61, 1.621, 1.631, 1.643, 1.654, + 1.697, 1.68, 1.663, 1.649, 1.635, 1.625, 1.615, 1.607, 1.6, 1.595, 1.591, 1.587, 1.584, 1.581, 1.579, 1.577, 1.577, 1.576, 1.577, 1.578, 1.58, 1.582, 1.586, 1.589, 1.594, 1.599, 1.605, 1.611, 1.623, 1.634, 1.644, 1.654, + 1.697, 1.68, 1.664, 1.649, 1.635, 1.625, 1.615, 1.608, 1.6, 1.595, 1.591, 1.587, 1.583, 1.581, 1.579, 1.578, 1.577, 1.576, 1.577, 1.578, 1.581, 1.583, 1.587, 1.59, 1.595, 1.6, 1.606, 1.613, 1.625, 1.636, 1.646, 1.655, + 1.699, 1.682, 1.665, 1.651, 1.636, 1.626, 1.616, 1.609, 1.602, 1.596, 1.591, 1.587, 1.584, 1.581, 1.579, 1.578, 1.577, 1.577, 1.578, 1.58, 1.581, 1.584, 1.587, 1.591, 1.596, 1.601, 1.608, 1.615, 1.626, 1.637, 1.647, 1.657, + 1.7, 1.683, 1.666, 1.652, 1.637, 1.627, 1.617, 1.61, 1.603, 1.597, 1.591, 1.587, 1.584, 1.581, 1.579, 1.578, 1.577, 1.578, 1.579, 1.581, 1.582, 1.584, 1.588, 1.592, 1.597, 1.602, 1.609, 1.617, 1.628, 1.639, 1.649, 1.658, + 1.702, 1.685, 1.668, 1.653, 1.639, 1.628, 1.618, 1.611, 1.604, 1.598, 1.591, 1.587, 1.584, 1.582, 1.58, 1.578, 1.578, 1.578, 1.58, 1.581, 1.583, 1.585, 1.589, 1.593, 1.598, 1.603, 1.611, 1.619, 1.63, 1.641, 1.65, 1.66, + 1.705, 1.687, 1.67, 1.655, 1.641, 1.63, 1.619, 1.611, 1.604, 1.598, 1.592, 1.588, 1.585, 1.582, 1.58, 1.579, 1.578, 1.578, 1.58, 1.582, 1.583, 1.585, 1.59, 1.594, 1.599, 1.604, 1.612, 1.621, 1.632, 1.643, 1.653, 1.663, + 1.707, 1.689, 1.672, 1.657, 1.642, 1.631, 1.62, 1.612, 1.605, 1.599, 1.593, 1.589, 1.585, 1.583, 1.581, 1.58, 1.579, 1.579, 1.58, 1.582, 1.584, 1.586, 1.59, 1.595, 1.6, 1.605, 1.614, 1.623, 1.634, 1.646, 1.655, 1.665, + 1.709, 1.692, 1.674, 1.659, 1.645, 1.633, 1.621, 1.613, 1.606, 1.6, 1.595, 1.59, 1.587, 1.584, 1.583, 1.581, 1.58, 1.58, 1.581, 1.582, 1.585, 1.587, 1.592, 1.597, 1.602, 1.608, 1.616, 1.625, 1.637, 1.648, 1.658, 1.668, + 1.711, 1.695, 1.678, 1.662, 1.647, 1.635, 1.623, 1.615, 1.608, 1.602, 1.597, 1.593, 1.59, 1.587, 1.584, 1.582, 1.581, 1.581, 1.582, 1.584, 1.586, 1.589, 1.594, 1.599, 1.605, 1.611, 1.619, 1.628, 1.639, 1.651, 1.66, 1.67, + 1.714, 1.698, 1.681, 1.666, 1.65, 1.637, 1.624, 1.616, 1.609, 1.604, 1.6, 1.596, 1.592, 1.589, 1.585, 1.584, 1.583, 1.583, 1.583, 1.585, 1.587, 1.59, 1.595, 1.601, 1.608, 1.615, 1.622, 1.63, 1.642, 1.653, 1.663, 1.673, + 1.715, 1.7, 1.685, 1.669, 1.653, 1.64, 1.627, 1.619, 1.613, 1.607, 1.603, 1.598, 1.594, 1.591, 1.587, 1.586, 1.586, 1.586, 1.586, 1.588, 1.59, 1.593, 1.598, 1.604, 1.611, 1.618, 1.626, 1.634, 1.646, 1.657, 1.666, 1.675, + 1.717, 1.703, 1.688, 1.673, 1.657, 1.644, 1.63, 1.623, 1.616, 1.611, 1.605, 1.601, 1.596, 1.593, 1.59, 1.588, 1.588, 1.589, 1.589, 1.591, 1.594, 1.597, 1.601, 1.607, 1.614, 1.622, 1.63, 1.639, 1.65, 1.661, 1.67, 1.678, + 1.719, 1.705, 1.692, 1.677, 1.661, 1.647, 1.634, 1.626, 1.62, 1.614, 1.608, 1.603, 1.598, 1.595, 1.592, 1.591, 1.591, 1.591, 1.592, 1.594, 1.597, 1.6, 1.605, 1.61, 1.617, 1.625, 1.634, 1.643, 1.655, 1.666, 1.673, 1.681 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 4000, + "table": + [ + 2.253, 2.26, 2.267, 2.277, 2.288, 2.301, 2.314, 2.327, 2.339, 2.348, 2.356, 2.364, 2.37, 2.375, 2.379, 2.381, 2.381, 2.38, 2.379, 2.376, 2.371, 2.367, 2.363, 2.359, 2.351, 2.343, 2.34, 2.336, 2.324, 2.314, 2.307, 2.301, + 2.256, 2.264, 2.272, 2.284, 2.296, 2.309, 2.321, 2.332, 2.343, 2.352, 2.36, 2.368, 2.374, 2.379, 2.383, 2.385, 2.385, 2.385, 2.383, 2.381, 2.376, 2.371, 2.367, 2.362, 2.355, 2.349, 2.343, 2.337, 2.327, 2.316, 2.31, 2.303, + 2.259, 2.269, 2.278, 2.292, 2.305, 2.316, 2.328, 2.337, 2.347, 2.356, 2.365, 2.372, 2.378, 2.382, 2.386, 2.388, 2.389, 2.389, 2.388, 2.385, 2.38, 2.375, 2.37, 2.365, 2.36, 2.355, 2.347, 2.339, 2.329, 2.319, 2.313, 2.306, + 2.263, 2.274, 2.285, 2.298, 2.313, 2.323, 2.334, 2.342, 2.351, 2.359, 2.369, 2.375, 2.381, 2.386, 2.39, 2.392, 2.393, 2.393, 2.392, 2.39, 2.385, 2.38, 2.373, 2.368, 2.364, 2.36, 2.351, 2.341, 2.332, 2.322, 2.316, 2.309, + 2.268, 2.28, 2.291, 2.303, 2.315, 2.326, 2.337, 2.346, 2.355, 2.363, 2.372, 2.379, 2.384, 2.388, 2.391, 2.393, 2.394, 2.394, 2.394, 2.392, 2.389, 2.385, 2.378, 2.372, 2.367, 2.362, 2.354, 2.346, 2.336, 2.326, 2.32, 2.313, + 2.274, 2.286, 2.298, 2.308, 2.318, 2.33, 2.341, 2.35, 2.359, 2.367, 2.376, 2.382, 2.387, 2.391, 2.393, 2.395, 2.396, 2.396, 2.396, 2.395, 2.393, 2.39, 2.383, 2.376, 2.37, 2.364, 2.357, 2.35, 2.339, 2.329, 2.324, 2.318, + 2.277, 2.29, 2.302, 2.312, 2.321, 2.332, 2.344, 2.353, 2.362, 2.371, 2.379, 2.385, 2.389, 2.392, 2.394, 2.396, 2.396, 2.397, 2.397, 2.397, 2.396, 2.393, 2.387, 2.38, 2.374, 2.367, 2.36, 2.353, 2.343, 2.333, 2.327, 2.322, + 2.277, 2.29, 2.303, 2.313, 2.323, 2.334, 2.345, 2.355, 2.364, 2.373, 2.381, 2.387, 2.391, 2.393, 2.395, 2.396, 2.396, 2.397, 2.397, 2.397, 2.396, 2.394, 2.389, 2.384, 2.377, 2.37, 2.363, 2.355, 2.345, 2.335, 2.33, 2.324, + 2.277, 2.29, 2.303, 2.314, 2.325, 2.335, 2.346, 2.356, 2.366, 2.375, 2.384, 2.389, 2.392, 2.394, 2.395, 2.396, 2.396, 2.397, 2.397, 2.397, 2.396, 2.395, 2.392, 2.387, 2.38, 2.373, 2.365, 2.357, 2.347, 2.338, 2.332, 2.327, + 2.277, 2.291, 2.304, 2.315, 2.326, 2.337, 2.348, 2.358, 2.368, 2.377, 2.385, 2.39, 2.392, 2.394, 2.395, 2.396, 2.396, 2.397, 2.397, 2.398, 2.397, 2.395, 2.393, 2.39, 2.383, 2.375, 2.367, 2.358, 2.349, 2.34, 2.334, 2.329, + 2.278, 2.292, 2.307, 2.316, 2.326, 2.337, 2.349, 2.36, 2.371, 2.379, 2.386, 2.39, 2.392, 2.394, 2.396, 2.397, 2.397, 2.397, 2.398, 2.398, 2.396, 2.395, 2.393, 2.39, 2.384, 2.378, 2.369, 2.36, 2.351, 2.341, 2.336, 2.33, + 2.279, 2.294, 2.309, 2.318, 2.326, 2.338, 2.351, 2.362, 2.373, 2.381, 2.387, 2.39, 2.392, 2.394, 2.396, 2.397, 2.397, 2.397, 2.398, 2.397, 2.396, 2.395, 2.394, 2.391, 2.386, 2.38, 2.37, 2.361, 2.352, 2.343, 2.337, 2.332, + 2.28, 2.295, 2.31, 2.318, 2.326, 2.339, 2.351, 2.363, 2.374, 2.381, 2.386, 2.39, 2.393, 2.395, 2.396, 2.397, 2.397, 2.397, 2.397, 2.397, 2.396, 2.395, 2.394, 2.392, 2.387, 2.38, 2.372, 2.363, 2.353, 2.344, 2.338, 2.332, + 2.281, 2.295, 2.31, 2.319, 2.327, 2.339, 2.352, 2.363, 2.374, 2.381, 2.386, 2.39, 2.393, 2.395, 2.396, 2.396, 2.396, 2.396, 2.396, 2.397, 2.396, 2.396, 2.395, 2.392, 2.387, 2.381, 2.373, 2.364, 2.354, 2.345, 2.339, 2.333, + 2.282, 2.296, 2.31, 2.319, 2.328, 2.339, 2.352, 2.363, 2.374, 2.38, 2.385, 2.389, 2.394, 2.396, 2.396, 2.396, 2.395, 2.395, 2.396, 2.396, 2.397, 2.396, 2.395, 2.393, 2.387, 2.381, 2.374, 2.366, 2.355, 2.346, 2.339, 2.333, + 2.282, 2.297, 2.311, 2.32, 2.329, 2.34, 2.351, 2.362, 2.373, 2.38, 2.385, 2.39, 2.394, 2.395, 2.396, 2.396, 2.395, 2.395, 2.395, 2.396, 2.396, 2.396, 2.395, 2.393, 2.388, 2.382, 2.374, 2.366, 2.357, 2.348, 2.341, 2.334, + 2.283, 2.297, 2.312, 2.321, 2.331, 2.341, 2.351, 2.362, 2.373, 2.38, 2.386, 2.39, 2.393, 2.395, 2.395, 2.395, 2.395, 2.395, 2.395, 2.396, 2.396, 2.396, 2.395, 2.393, 2.389, 2.383, 2.375, 2.367, 2.359, 2.351, 2.343, 2.335, + 2.283, 2.298, 2.313, 2.322, 2.332, 2.341, 2.351, 2.361, 2.372, 2.38, 2.387, 2.391, 2.393, 2.394, 2.395, 2.395, 2.395, 2.395, 2.395, 2.395, 2.396, 2.396, 2.395, 2.393, 2.389, 2.384, 2.376, 2.367, 2.36, 2.353, 2.345, 2.336, + 2.285, 2.298, 2.311, 2.321, 2.331, 2.341, 2.351, 2.361, 2.371, 2.379, 2.386, 2.39, 2.393, 2.394, 2.395, 2.395, 2.395, 2.395, 2.395, 2.396, 2.396, 2.396, 2.395, 2.393, 2.389, 2.384, 2.376, 2.368, 2.361, 2.353, 2.345, 2.337, + 2.286, 2.298, 2.31, 2.32, 2.33, 2.34, 2.35, 2.36, 2.371, 2.378, 2.385, 2.389, 2.393, 2.394, 2.395, 2.395, 2.395, 2.395, 2.396, 2.396, 2.396, 2.396, 2.395, 2.393, 2.388, 2.383, 2.376, 2.369, 2.361, 2.353, 2.346, 2.338, + 2.287, 2.298, 2.308, 2.319, 2.329, 2.339, 2.349, 2.36, 2.37, 2.377, 2.384, 2.388, 2.392, 2.394, 2.395, 2.395, 2.395, 2.395, 2.396, 2.396, 2.396, 2.396, 2.395, 2.393, 2.388, 2.383, 2.376, 2.37, 2.361, 2.353, 2.346, 2.339, + 2.288, 2.298, 2.307, 2.317, 2.327, 2.338, 2.348, 2.358, 2.368, 2.376, 2.383, 2.388, 2.392, 2.394, 2.395, 2.396, 2.396, 2.396, 2.397, 2.397, 2.397, 2.396, 2.394, 2.392, 2.387, 2.382, 2.375, 2.368, 2.36, 2.353, 2.345, 2.338, + 2.289, 2.298, 2.307, 2.316, 2.326, 2.336, 2.346, 2.356, 2.367, 2.375, 2.383, 2.388, 2.391, 2.394, 2.396, 2.397, 2.397, 2.397, 2.398, 2.397, 2.397, 2.396, 2.394, 2.391, 2.387, 2.382, 2.374, 2.367, 2.359, 2.352, 2.345, 2.337, + 2.289, 2.297, 2.306, 2.315, 2.324, 2.334, 2.344, 2.355, 2.365, 2.374, 2.381, 2.386, 2.39, 2.393, 2.395, 2.397, 2.397, 2.398, 2.398, 2.398, 2.397, 2.396, 2.393, 2.39, 2.386, 2.381, 2.373, 2.366, 2.358, 2.351, 2.343, 2.336, + 2.287, 2.296, 2.304, 2.314, 2.323, 2.333, 2.342, 2.352, 2.362, 2.371, 2.379, 2.385, 2.389, 2.392, 2.394, 2.396, 2.397, 2.398, 2.398, 2.398, 2.397, 2.396, 2.393, 2.389, 2.385, 2.38, 2.373, 2.365, 2.357, 2.348, 2.341, 2.334, + 2.286, 2.295, 2.303, 2.312, 2.321, 2.331, 2.341, 2.35, 2.36, 2.368, 2.377, 2.383, 2.388, 2.391, 2.393, 2.395, 2.396, 2.397, 2.398, 2.398, 2.397, 2.395, 2.392, 2.388, 2.384, 2.38, 2.372, 2.365, 2.356, 2.346, 2.339, 2.333, + 2.286, 2.293, 2.3, 2.309, 2.318, 2.328, 2.337, 2.347, 2.356, 2.365, 2.374, 2.381, 2.385, 2.389, 2.392, 2.394, 2.395, 2.396, 2.397, 2.397, 2.395, 2.393, 2.39, 2.387, 2.383, 2.378, 2.371, 2.364, 2.354, 2.344, 2.337, 2.33, + 2.285, 2.29, 2.296, 2.305, 2.315, 2.324, 2.333, 2.342, 2.353, 2.362, 2.372, 2.378, 2.383, 2.386, 2.39, 2.392, 2.394, 2.395, 2.395, 2.395, 2.393, 2.391, 2.389, 2.385, 2.381, 2.376, 2.369, 2.362, 2.351, 2.341, 2.334, 2.328, + 2.284, 2.288, 2.292, 2.301, 2.311, 2.32, 2.328, 2.338, 2.349, 2.359, 2.369, 2.375, 2.38, 2.384, 2.388, 2.39, 2.392, 2.393, 2.394, 2.393, 2.391, 2.389, 2.387, 2.384, 2.379, 2.373, 2.367, 2.361, 2.349, 2.338, 2.332, 2.325, + 2.284, 2.287, 2.29, 2.299, 2.309, 2.317, 2.325, 2.334, 2.345, 2.355, 2.366, 2.373, 2.377, 2.381, 2.385, 2.388, 2.389, 2.391, 2.391, 2.391, 2.389, 2.387, 2.385, 2.382, 2.377, 2.371, 2.363, 2.355, 2.344, 2.334, 2.328, 2.323, + 2.283, 2.286, 2.289, 2.297, 2.306, 2.314, 2.321, 2.331, 2.341, 2.352, 2.364, 2.37, 2.375, 2.379, 2.382, 2.385, 2.386, 2.388, 2.388, 2.388, 2.387, 2.386, 2.383, 2.38, 2.374, 2.368, 2.358, 2.348, 2.338, 2.329, 2.325, 2.32, + 2.283, 2.285, 2.288, 2.296, 2.304, 2.311, 2.318, 2.327, 2.336, 2.348, 2.361, 2.368, 2.372, 2.376, 2.379, 2.382, 2.383, 2.384, 2.385, 2.386, 2.385, 2.384, 2.381, 2.378, 2.372, 2.365, 2.353, 2.341, 2.333, 2.325, 2.321, 2.318 + ] + }, + { + "ct": 5000, + "table": + [ + 1.897, 1.908, 1.918, 1.929, 1.94, 1.953, 1.966, 1.977, 1.986, 1.994, 2.001, 2.007, 2.012, 2.015, 2.018, 2.019, 2.019, 2.019, 2.018, 2.016, 2.015, 2.013, 2.01, 2.007, 2.002, 1.998, 1.993, 1.987, 1.977, 1.967, 1.956, 1.944, + 1.903, 1.913, 1.923, 1.934, 1.945, 1.958, 1.971, 1.981, 1.99, 1.997, 2.005, 2.01, 2.015, 2.019, 2.021, 2.023, 2.023, 2.023, 2.022, 2.02, 2.018, 2.016, 2.013, 2.009, 2.005, 2.0, 1.995, 1.989, 1.98, 1.97, 1.959, 1.948, + 1.909, 1.918, 1.928, 1.939, 1.951, 1.963, 1.976, 1.985, 1.993, 2.001, 2.008, 2.014, 2.019, 2.022, 2.025, 2.026, 2.027, 2.027, 2.026, 2.024, 2.022, 2.018, 2.015, 2.011, 2.007, 2.003, 1.998, 1.992, 1.982, 1.973, 1.962, 1.952, + 1.915, 1.924, 1.933, 1.944, 1.956, 1.968, 1.981, 1.989, 1.997, 2.005, 2.012, 2.018, 2.022, 2.025, 2.028, 2.03, 2.031, 2.031, 2.03, 2.028, 2.025, 2.022, 2.018, 2.014, 2.01, 2.006, 2.0, 1.994, 1.985, 1.976, 1.966, 1.956, + 1.919, 1.929, 1.939, 1.951, 1.963, 1.974, 1.986, 1.994, 2.002, 2.01, 2.017, 2.022, 2.026, 2.03, 2.032, 2.033, 2.034, 2.034, 2.033, 2.032, 2.029, 2.026, 2.022, 2.018, 2.013, 2.009, 2.003, 1.997, 1.988, 1.979, 1.969, 1.958, + 1.923, 1.934, 1.946, 1.957, 1.969, 1.98, 1.991, 1.999, 2.007, 2.015, 2.022, 2.027, 2.031, 2.034, 2.036, 2.037, 2.037, 2.037, 2.036, 2.035, 2.033, 2.031, 2.026, 2.022, 2.017, 2.012, 2.006, 1.999, 1.99, 1.982, 1.971, 1.961, + 1.926, 1.938, 1.951, 1.963, 1.974, 1.985, 1.995, 2.004, 2.012, 2.019, 2.026, 2.03, 2.034, 2.037, 2.038, 2.039, 2.04, 2.04, 2.039, 2.038, 2.037, 2.034, 2.03, 2.026, 2.02, 2.015, 2.008, 2.002, 1.993, 1.985, 1.974, 1.964, + 1.928, 1.941, 1.954, 1.966, 1.978, 1.989, 1.999, 2.008, 2.016, 2.023, 2.028, 2.033, 2.036, 2.039, 2.04, 2.04, 2.041, 2.042, 2.042, 2.041, 2.039, 2.037, 2.033, 2.028, 2.023, 2.018, 2.011, 2.004, 1.997, 1.989, 1.978, 1.967, + 1.931, 1.943, 1.956, 1.969, 1.982, 1.993, 2.003, 2.012, 2.02, 2.026, 2.031, 2.035, 2.039, 2.04, 2.041, 2.042, 2.043, 2.044, 2.044, 2.043, 2.042, 2.039, 2.035, 2.031, 2.026, 2.02, 2.014, 2.007, 2.0, 1.992, 1.981, 1.97, + 1.934, 1.946, 1.958, 1.972, 1.986, 1.996, 2.006, 2.015, 2.023, 2.028, 2.033, 2.037, 2.04, 2.042, 2.042, 2.043, 2.045, 2.045, 2.045, 2.045, 2.043, 2.041, 2.037, 2.033, 2.028, 2.023, 2.016, 2.009, 2.002, 1.995, 1.983, 1.972, + 1.937, 1.949, 1.961, 1.974, 1.989, 1.999, 2.008, 2.016, 2.025, 2.03, 2.035, 2.038, 2.041, 2.043, 2.043, 2.044, 2.045, 2.046, 2.046, 2.045, 2.044, 2.042, 2.039, 2.035, 2.03, 2.025, 2.018, 2.011, 2.003, 1.995, 1.985, 1.974, + 1.941, 1.952, 1.963, 1.977, 1.991, 2.001, 2.01, 2.018, 2.026, 2.032, 2.036, 2.039, 2.042, 2.044, 2.045, 2.046, 2.046, 2.047, 2.046, 2.046, 2.045, 2.044, 2.041, 2.037, 2.032, 2.027, 2.02, 2.012, 2.004, 1.996, 1.986, 1.976, + 1.943, 1.954, 1.966, 1.98, 1.993, 2.003, 2.011, 2.019, 2.027, 2.033, 2.037, 2.04, 2.043, 2.044, 2.046, 2.047, 2.047, 2.047, 2.047, 2.046, 2.045, 2.044, 2.041, 2.038, 2.033, 2.028, 2.021, 2.014, 2.006, 1.997, 1.987, 1.977, + 1.944, 1.957, 1.969, 1.982, 1.995, 2.004, 2.012, 2.02, 2.028, 2.034, 2.038, 2.041, 2.044, 2.045, 2.046, 2.047, 2.047, 2.047, 2.047, 2.046, 2.045, 2.044, 2.042, 2.039, 2.034, 2.029, 2.023, 2.016, 2.007, 1.998, 1.988, 1.978, + 1.946, 1.959, 1.973, 1.985, 1.997, 2.006, 2.013, 2.021, 2.029, 2.034, 2.039, 2.043, 2.045, 2.046, 2.047, 2.047, 2.048, 2.048, 2.047, 2.046, 2.045, 2.044, 2.042, 2.04, 2.035, 2.03, 2.024, 2.018, 2.008, 1.998, 1.988, 1.978, + 1.947, 1.96, 1.973, 1.986, 1.998, 2.007, 2.014, 2.022, 2.029, 2.035, 2.039, 2.043, 2.045, 2.046, 2.047, 2.047, 2.048, 2.048, 2.048, 2.047, 2.045, 2.044, 2.042, 2.04, 2.034, 2.029, 2.024, 2.018, 2.008, 1.998, 1.988, 1.978, + 1.947, 1.961, 1.974, 1.987, 1.999, 2.008, 2.015, 2.022, 2.029, 2.035, 2.039, 2.043, 2.045, 2.046, 2.047, 2.047, 2.048, 2.048, 2.048, 2.047, 2.046, 2.044, 2.042, 2.04, 2.034, 2.029, 2.023, 2.018, 2.008, 1.998, 1.988, 1.978, + 1.948, 1.961, 1.974, 1.987, 2.0, 2.009, 2.016, 2.023, 2.029, 2.035, 2.039, 2.043, 2.045, 2.046, 2.047, 2.047, 2.048, 2.048, 2.048, 2.047, 2.046, 2.044, 2.042, 2.039, 2.034, 2.028, 2.023, 2.017, 2.007, 1.997, 1.988, 1.978, + 1.948, 1.961, 1.973, 1.987, 2.0, 2.009, 2.016, 2.023, 2.029, 2.034, 2.04, 2.043, 2.045, 2.046, 2.047, 2.048, 2.048, 2.048, 2.048, 2.047, 2.046, 2.044, 2.041, 2.038, 2.033, 2.027, 2.022, 2.016, 2.006, 1.995, 1.987, 1.978, + 1.948, 1.96, 1.973, 1.986, 2.0, 2.009, 2.016, 2.022, 2.028, 2.034, 2.04, 2.043, 2.045, 2.046, 2.047, 2.048, 2.048, 2.048, 2.048, 2.047, 2.045, 2.043, 2.04, 2.037, 2.032, 2.026, 2.02, 2.014, 2.004, 1.994, 1.986, 1.978, + 1.948, 1.96, 1.972, 1.986, 2.0, 2.008, 2.016, 2.022, 2.027, 2.033, 2.039, 2.043, 2.044, 2.046, 2.047, 2.048, 2.048, 2.048, 2.048, 2.047, 2.045, 2.042, 2.039, 2.035, 2.03, 2.025, 2.019, 2.012, 2.002, 1.992, 1.985, 1.977, + 1.947, 1.959, 1.97, 1.984, 1.998, 2.007, 2.015, 2.021, 2.027, 2.033, 2.038, 2.041, 2.044, 2.046, 2.047, 2.047, 2.047, 2.047, 2.047, 2.045, 2.043, 2.041, 2.038, 2.034, 2.029, 2.023, 2.017, 2.01, 2.0, 1.991, 1.983, 1.975, + 1.946, 1.957, 1.969, 1.983, 1.997, 2.006, 2.014, 2.02, 2.027, 2.032, 2.036, 2.04, 2.044, 2.045, 2.046, 2.047, 2.047, 2.047, 2.045, 2.044, 2.042, 2.039, 2.036, 2.032, 2.027, 2.022, 2.015, 2.008, 1.999, 1.989, 1.981, 1.972, + 1.944, 1.956, 1.967, 1.981, 1.995, 2.004, 2.013, 2.019, 2.026, 2.03, 2.035, 2.039, 2.042, 2.044, 2.045, 2.046, 2.046, 2.046, 2.044, 2.042, 2.04, 2.037, 2.034, 2.03, 2.025, 2.019, 2.012, 2.005, 1.996, 1.987, 1.978, 1.97, + 1.942, 1.954, 1.966, 1.979, 1.993, 2.002, 2.011, 2.018, 2.024, 2.029, 2.033, 2.036, 2.039, 2.041, 2.043, 2.044, 2.044, 2.044, 2.042, 2.041, 2.038, 2.036, 2.031, 2.027, 2.021, 2.015, 2.009, 2.002, 1.992, 1.982, 1.975, 1.967, + 1.94, 1.952, 1.964, 1.977, 1.99, 2.0, 2.01, 2.017, 2.023, 2.027, 2.031, 2.034, 2.036, 2.039, 2.041, 2.043, 2.043, 2.042, 2.041, 2.039, 2.037, 2.034, 2.029, 2.024, 2.018, 2.012, 2.005, 1.998, 1.988, 1.978, 1.972, 1.965, + 1.937, 1.949, 1.961, 1.974, 1.987, 1.998, 2.008, 2.015, 2.021, 2.025, 2.029, 2.031, 2.033, 2.036, 2.038, 2.04, 2.04, 2.04, 2.038, 2.036, 2.034, 2.031, 2.026, 2.02, 2.015, 2.009, 2.002, 1.995, 1.984, 1.974, 1.968, 1.962, + 1.935, 1.946, 1.957, 1.97, 1.983, 1.995, 2.006, 2.012, 2.018, 2.022, 2.026, 2.029, 2.031, 2.033, 2.035, 2.036, 2.037, 2.037, 2.035, 2.033, 2.03, 2.027, 2.022, 2.017, 2.012, 2.006, 1.999, 1.991, 1.981, 1.97, 1.965, 1.959, + 1.932, 1.943, 1.953, 1.966, 1.98, 1.992, 2.004, 2.01, 2.015, 2.019, 2.023, 2.026, 2.028, 2.029, 2.031, 2.032, 2.034, 2.034, 2.032, 2.03, 2.027, 2.023, 2.019, 2.014, 2.009, 2.004, 1.996, 1.988, 1.977, 1.966, 1.961, 1.956, + 1.931, 1.94, 1.95, 1.963, 1.977, 1.989, 2.002, 2.008, 2.012, 2.016, 2.02, 2.023, 2.025, 2.027, 2.028, 2.03, 2.031, 2.031, 2.03, 2.028, 2.024, 2.021, 2.016, 2.012, 2.007, 2.001, 1.992, 1.984, 1.973, 1.963, 1.958, 1.953, + 1.929, 1.938, 1.947, 1.96, 1.974, 1.987, 1.999, 2.005, 2.009, 2.013, 2.017, 2.02, 2.022, 2.024, 2.026, 2.028, 2.028, 2.028, 2.028, 2.026, 2.022, 2.018, 2.014, 2.009, 2.004, 1.998, 1.988, 1.979, 1.969, 1.96, 1.955, 1.95, + 1.928, 1.936, 1.943, 1.957, 1.971, 1.984, 1.996, 2.002, 2.006, 2.01, 2.015, 2.017, 2.018, 2.021, 2.024, 2.025, 2.026, 2.026, 2.025, 2.023, 2.02, 2.016, 2.011, 2.007, 2.001, 1.995, 1.984, 1.974, 1.966, 1.958, 1.952, 1.947 + ] + } + ], + "luminance_lut": + [ + 1.877, 1.742, 1.606, 1.507, 1.41, 1.343, 1.281, 1.239, 1.201, 1.17, 1.141, 1.119, 1.1, 1.089, 1.081, 1.076, 1.073, 1.071, 1.07, 1.072, 1.081, 1.094, 1.118, 1.146, 1.188, 1.232, 1.285, 1.34, 1.409, 1.481, 1.593, 1.704, + 1.832, 1.702, 1.573, 1.479, 1.387, 1.324, 1.266, 1.224, 1.186, 1.155, 1.125, 1.104, 1.087, 1.077, 1.072, 1.068, 1.065, 1.063, 1.062, 1.063, 1.07, 1.082, 1.103, 1.13, 1.169, 1.211, 1.261, 1.314, 1.381, 1.45, 1.556, 1.662, + 1.786, 1.663, 1.541, 1.451, 1.364, 1.305, 1.251, 1.21, 1.171, 1.14, 1.11, 1.09, 1.074, 1.066, 1.062, 1.059, 1.058, 1.056, 1.054, 1.055, 1.059, 1.069, 1.089, 1.114, 1.15, 1.19, 1.238, 1.288, 1.352, 1.419, 1.52, 1.621, + 1.743, 1.627, 1.51, 1.425, 1.343, 1.288, 1.237, 1.196, 1.157, 1.125, 1.096, 1.077, 1.063, 1.056, 1.054, 1.052, 1.051, 1.05, 1.048, 1.047, 1.051, 1.059, 1.076, 1.099, 1.133, 1.17, 1.216, 1.264, 1.326, 1.39, 1.486, 1.582, + 1.712, 1.601, 1.49, 1.408, 1.328, 1.274, 1.225, 1.183, 1.144, 1.114, 1.086, 1.069, 1.059, 1.053, 1.052, 1.05, 1.05, 1.049, 1.048, 1.048, 1.05, 1.056, 1.07, 1.089, 1.121, 1.156, 1.2, 1.247, 1.308, 1.371, 1.463, 1.555, + 1.681, 1.576, 1.47, 1.391, 1.314, 1.261, 1.212, 1.171, 1.132, 1.102, 1.076, 1.062, 1.054, 1.05, 1.05, 1.049, 1.048, 1.048, 1.048, 1.049, 1.049, 1.053, 1.064, 1.08, 1.109, 1.141, 1.185, 1.23, 1.289, 1.351, 1.44, 1.528, + 1.655, 1.554, 1.453, 1.376, 1.301, 1.249, 1.201, 1.16, 1.12, 1.092, 1.068, 1.056, 1.051, 1.048, 1.048, 1.048, 1.047, 1.047, 1.048, 1.049, 1.049, 1.052, 1.059, 1.072, 1.099, 1.129, 1.171, 1.215, 1.274, 1.335, 1.42, 1.506, + 1.639, 1.539, 1.438, 1.364, 1.291, 1.239, 1.19, 1.149, 1.11, 1.085, 1.064, 1.054, 1.05, 1.048, 1.048, 1.047, 1.047, 1.047, 1.048, 1.049, 1.05, 1.052, 1.057, 1.068, 1.092, 1.12, 1.161, 1.204, 1.263, 1.324, 1.408, 1.492, + 1.622, 1.523, 1.424, 1.352, 1.281, 1.229, 1.18, 1.139, 1.101, 1.077, 1.059, 1.051, 1.049, 1.047, 1.047, 1.047, 1.047, 1.047, 1.048, 1.049, 1.051, 1.052, 1.055, 1.063, 1.085, 1.111, 1.151, 1.194, 1.253, 1.313, 1.395, 1.477, + 1.607, 1.51, 1.412, 1.342, 1.273, 1.221, 1.171, 1.131, 1.093, 1.071, 1.056, 1.05, 1.047, 1.046, 1.046, 1.046, 1.047, 1.047, 1.048, 1.05, 1.051, 1.053, 1.054, 1.061, 1.08, 1.104, 1.143, 1.185, 1.244, 1.305, 1.385, 1.466, + 1.594, 1.498, 1.403, 1.334, 1.268, 1.215, 1.164, 1.124, 1.086, 1.067, 1.055, 1.049, 1.046, 1.045, 1.045, 1.045, 1.046, 1.047, 1.048, 1.05, 1.051, 1.053, 1.054, 1.059, 1.077, 1.098, 1.137, 1.179, 1.237, 1.297, 1.378, 1.458, + 1.58, 1.487, 1.394, 1.327, 1.262, 1.208, 1.156, 1.117, 1.08, 1.062, 1.053, 1.048, 1.045, 1.044, 1.044, 1.045, 1.045, 1.046, 1.048, 1.05, 1.052, 1.053, 1.054, 1.058, 1.073, 1.092, 1.131, 1.172, 1.231, 1.29, 1.37, 1.449, + 1.572, 1.48, 1.388, 1.322, 1.259, 1.205, 1.152, 1.113, 1.077, 1.061, 1.052, 1.047, 1.045, 1.044, 1.044, 1.044, 1.045, 1.046, 1.047, 1.049, 1.051, 1.052, 1.053, 1.057, 1.07, 1.088, 1.127, 1.168, 1.226, 1.285, 1.364, 1.443, + 1.567, 1.475, 1.384, 1.319, 1.256, 1.202, 1.149, 1.11, 1.075, 1.06, 1.052, 1.047, 1.045, 1.044, 1.044, 1.044, 1.044, 1.045, 1.046, 1.048, 1.049, 1.051, 1.053, 1.057, 1.068, 1.085, 1.123, 1.165, 1.222, 1.281, 1.359, 1.438, + 1.561, 1.47, 1.379, 1.316, 1.253, 1.199, 1.146, 1.108, 1.073, 1.059, 1.051, 1.047, 1.045, 1.044, 1.044, 1.044, 1.044, 1.044, 1.045, 1.046, 1.047, 1.049, 1.052, 1.056, 1.066, 1.081, 1.12, 1.161, 1.218, 1.277, 1.355, 1.432, + 1.564, 1.472, 1.38, 1.315, 1.252, 1.199, 1.146, 1.108, 1.074, 1.06, 1.053, 1.05, 1.047, 1.046, 1.046, 1.046, 1.046, 1.046, 1.047, 1.047, 1.047, 1.048, 1.051, 1.055, 1.064, 1.079, 1.118, 1.159, 1.217, 1.276, 1.353, 1.43, + 1.568, 1.475, 1.382, 1.316, 1.252, 1.198, 1.147, 1.109, 1.075, 1.061, 1.055, 1.052, 1.05, 1.049, 1.049, 1.049, 1.049, 1.049, 1.048, 1.048, 1.048, 1.048, 1.049, 1.052, 1.062, 1.077, 1.116, 1.157, 1.216, 1.276, 1.352, 1.429, + 1.571, 1.478, 1.384, 1.317, 1.251, 1.199, 1.148, 1.11, 1.076, 1.063, 1.057, 1.054, 1.053, 1.052, 1.051, 1.051, 1.051, 1.05, 1.049, 1.048, 1.047, 1.047, 1.047, 1.05, 1.06, 1.076, 1.115, 1.156, 1.216, 1.276, 1.352, 1.428, + 1.575, 1.483, 1.391, 1.323, 1.257, 1.205, 1.154, 1.117, 1.083, 1.069, 1.062, 1.058, 1.056, 1.054, 1.053, 1.052, 1.051, 1.05, 1.048, 1.047, 1.046, 1.045, 1.046, 1.049, 1.061, 1.078, 1.117, 1.16, 1.22, 1.281, 1.357, 1.434, + 1.579, 1.488, 1.397, 1.329, 1.263, 1.211, 1.161, 1.124, 1.089, 1.075, 1.067, 1.062, 1.059, 1.056, 1.054, 1.052, 1.05, 1.049, 1.047, 1.045, 1.044, 1.043, 1.044, 1.048, 1.062, 1.08, 1.12, 1.163, 1.224, 1.286, 1.363, 1.44, + 1.586, 1.496, 1.405, 1.337, 1.27, 1.218, 1.168, 1.131, 1.096, 1.08, 1.072, 1.066, 1.062, 1.058, 1.056, 1.054, 1.051, 1.049, 1.047, 1.045, 1.043, 1.042, 1.043, 1.048, 1.063, 1.084, 1.124, 1.168, 1.229, 1.292, 1.369, 1.447, + 1.601, 1.509, 1.417, 1.347, 1.279, 1.226, 1.176, 1.138, 1.103, 1.086, 1.074, 1.068, 1.065, 1.062, 1.059, 1.057, 1.054, 1.051, 1.048, 1.046, 1.044, 1.044, 1.045, 1.051, 1.069, 1.091, 1.133, 1.177, 1.238, 1.301, 1.379, 1.457, + 1.615, 1.522, 1.428, 1.357, 1.288, 1.234, 1.184, 1.146, 1.11, 1.091, 1.077, 1.071, 1.068, 1.065, 1.063, 1.06, 1.056, 1.053, 1.05, 1.047, 1.046, 1.046, 1.048, 1.055, 1.074, 1.099, 1.141, 1.185, 1.248, 1.311, 1.389, 1.467, + 1.634, 1.538, 1.441, 1.369, 1.299, 1.245, 1.194, 1.155, 1.119, 1.098, 1.082, 1.074, 1.071, 1.068, 1.065, 1.062, 1.059, 1.055, 1.052, 1.049, 1.048, 1.048, 1.052, 1.06, 1.082, 1.108, 1.151, 1.197, 1.259, 1.323, 1.402, 1.481, + 1.658, 1.557, 1.457, 1.384, 1.312, 1.258, 1.206, 1.167, 1.13, 1.107, 1.088, 1.078, 1.073, 1.07, 1.067, 1.064, 1.061, 1.058, 1.055, 1.053, 1.051, 1.052, 1.057, 1.068, 1.092, 1.121, 1.165, 1.211, 1.273, 1.337, 1.417, 1.498, + 1.682, 1.577, 1.472, 1.398, 1.326, 1.271, 1.219, 1.179, 1.141, 1.115, 1.093, 1.082, 1.075, 1.071, 1.069, 1.067, 1.064, 1.061, 1.059, 1.057, 1.054, 1.055, 1.063, 1.076, 1.103, 1.133, 1.178, 1.225, 1.288, 1.351, 1.433, 1.515, + 1.717, 1.606, 1.495, 1.417, 1.342, 1.286, 1.233, 1.192, 1.154, 1.126, 1.103, 1.089, 1.079, 1.074, 1.071, 1.068, 1.065, 1.063, 1.061, 1.06, 1.058, 1.061, 1.071, 1.087, 1.116, 1.149, 1.194, 1.242, 1.304, 1.367, 1.451, 1.535, + 1.759, 1.64, 1.521, 1.439, 1.361, 1.302, 1.247, 1.206, 1.168, 1.139, 1.114, 1.097, 1.085, 1.077, 1.073, 1.069, 1.067, 1.065, 1.063, 1.062, 1.063, 1.068, 1.081, 1.1, 1.131, 1.166, 1.212, 1.26, 1.321, 1.384, 1.47, 1.556, + 1.8, 1.674, 1.547, 1.461, 1.379, 1.319, 1.262, 1.22, 1.182, 1.152, 1.125, 1.106, 1.09, 1.081, 1.075, 1.07, 1.068, 1.066, 1.065, 1.065, 1.068, 1.075, 1.092, 1.113, 1.146, 1.182, 1.23, 1.279, 1.339, 1.401, 1.489, 1.578, + 1.855, 1.721, 1.588, 1.495, 1.405, 1.342, 1.283, 1.239, 1.199, 1.168, 1.141, 1.12, 1.103, 1.091, 1.082, 1.077, 1.075, 1.073, 1.074, 1.076, 1.081, 1.091, 1.109, 1.132, 1.167, 1.204, 1.251, 1.3, 1.362, 1.425, 1.518, 1.611, + 1.912, 1.772, 1.632, 1.531, 1.433, 1.367, 1.306, 1.26, 1.217, 1.186, 1.158, 1.136, 1.117, 1.103, 1.091, 1.085, 1.082, 1.082, 1.084, 1.088, 1.096, 1.108, 1.128, 1.152, 1.188, 1.226, 1.273, 1.322, 1.386, 1.452, 1.549, 1.646, + 1.969, 1.822, 1.676, 1.567, 1.461, 1.392, 1.329, 1.28, 1.235, 1.203, 1.175, 1.152, 1.131, 1.115, 1.101, 1.093, 1.09, 1.091, 1.095, 1.101, 1.111, 1.125, 1.147, 1.173, 1.21, 1.248, 1.295, 1.345, 1.41, 1.478, 1.579, 1.681 + ], + "sigma": 0.00218, + "sigma_Cb": 0.00194 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2900, + "ccm": + [ + 1.44924, -0.12935, -0.31989, + -0.65839, 1.95441, -0.29602, + 0.18344, -1.22282, 2.03938 + ] + }, + { + "ct": 3000, + "ccm": + [ + 1.38736, 0.07714, -0.46451, + -0.59691, 1.84335, -0.24644, + 0.10092, -1.30441, 2.20349 + ] + }, + { + "ct": 3600, + "ccm": + [ + 1.51261, -0.27921, -0.23339, + -0.55129, 1.83241, -0.28111, + 0.11649, -0.93195, 1.81546 + ] + }, + { + "ct": 4600, + "ccm": + [ + 1.47082, -0.18523, -0.28559, + -0.48923, 1.95126, -0.46203, + 0.07951, -0.83987, 1.76036 + ] + }, + { + "ct": 5800, + "ccm": + [ + 1.57294, -0.36229, -0.21065, + -0.42272, 1.80305, -0.38032, + 0.03671, -0.66862, 1.63191 + ] + }, + { + "ct": 8100, + "ccm": + [ + 1.58803, -0.09912, -0.48891, + -0.42594, 2.22303, -0.79709, + -0.00621, -0.90516, 1.91137 + ] + } + ] + } + }, + { + "rpi.sharpen": + { + "threshold": 2.0, + "strength": 0.5, + "limit": 0.5 + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/data/uncalibrated.json b/src/ipa/rpi/pisp/data/uncalibrated.json new file mode 100644 index 00000000..ff1e316e --- /dev/null +++ b/src/ipa/rpi/pisp/data/uncalibrated.json @@ -0,0 +1,135 @@ +{ + "version": 2.0, + "target": "pisp", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.awb": + { + "use_derivatives": 0, + "bayes": 0 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 3.0, 4.0, 6.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.4, + 1000, 0.4 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 4000, + "ccm": + [ + 2.0, -1.0, 0.0, + -0.5, 2.0, -0.5, + 0, -1.0, 2.0 + ] + } + ] + } + }, + { + "rpi.contrast": + { + "ce_enable": 0, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/pisp/meson.build b/src/ipa/rpi/pisp/meson.build new file mode 100644 index 00000000..878e3492 --- /dev/null +++ b/src/ipa/rpi/pisp/meson.build @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: CC0-1.0 + +ipa_name = 'ipa_rpi_pisp' + +pisp_ipa_deps = [ + libcamera_private, + libatomic, + libpisp_dep, +] + +pisp_ipa_libs = [ + rpi_ipa_cam_helper_lib, + rpi_ipa_common_lib, + rpi_ipa_controller_lib +] + +pisp_ipa_includes = [ + ipa_includes, + libipa_includes, +] + +pisp_ipa_sources = files([ + 'pisp.cpp', +]) + +pisp_ipa_includes += include_directories('..') + +mod = shared_module(ipa_name, pisp_ipa_sources, + name_prefix : '', + include_directories : pisp_ipa_includes, + dependencies : pisp_ipa_deps, + link_with : libipa, + link_whole : pisp_ipa_libs, + install : true, + cpp_args : '-Wno-address-of-packed-member', + install_dir : ipa_install_dir) + +if ipa_sign_module + custom_target(ipa_name + '.so.sign', + input : mod, + output : ipa_name + '.so.sign', + command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], + install : false, + build_by_default : true) +endif + +subdir('data') + +ipa_names += ipa_name diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp new file mode 100644 index 00000000..bb50a9e0 --- /dev/null +++ b/src/ipa/rpi/pisp/pisp.cpp @@ -0,0 +1,1068 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * pisp.cpp - Raspberry Pi PiSP IPA + */ +#include <algorithm> +#include <cmath> +#include <mutex> +#include <string> +#include <sys/mman.h> +#include <utility> +#include <vector> + +#include <libcamera/base/log.h> +#include <libcamera/control_ids.h> +#include <libcamera/ipa/ipa_module_info.h> +#include <libipa/pwl.h> + +#include "libpisp/backend/backend.hpp" +#include "libpisp/frontend/frontend.hpp" + +#include "common/ipa_base.h" +#include "controller/af_status.h" +#include "controller/agc_algorithm.h" +#include "controller/alsc_status.h" +#include "controller/awb_algorithm.h" +#include "controller/awb_status.h" +#include "controller/black_level_algorithm.h" +#include "controller/black_level_status.h" +#include "controller/cac_status.h" +#include "controller/ccm_status.h" +#include "controller/contrast_status.h" +#include "controller/denoise_algorithm.h" +#include "controller/denoise_status.h" +#include "controller/dpc_status.h" +#include "controller/geq_status.h" +#include "controller/hdr_status.h" +#include "controller/lux_status.h" +#include "controller/noise_status.h" +#include "controller/saturation_status.h" +#include "controller/sharpen_status.h" +#include "controller/stitch_status.h" +#include "controller/tonemap_status.h" + +using namespace std::literals::chrono_literals; + +namespace libcamera { + +LOG_DECLARE_CATEGORY(IPARPI) + +namespace { + +constexpr unsigned int NumLscCells = PISP_BE_LSC_GRID_SIZE; +constexpr unsigned int NumLscVertexes = NumLscCells + 1; + +inline int32_t clampField(double value, std::size_t fieldBits, std::size_t fracBits = 0, + bool isSigned = false, const char *desc = nullptr) +{ + ASSERT(fracBits <= fieldBits && fieldBits <= 32); + + int min = -(isSigned << (fieldBits - 1)); + int max = (1 << (fieldBits - isSigned)) - 1; + int32_t val = + std::clamp<int32_t>(std::round(value * (1 << fracBits)), min, max); + + if (desc && val / (1 << fracBits) != value) + LOG(IPARPI, Warning) + << desc << " rounded/clamped to " << val / (1 << fracBits); + + return val; +} + +int generateLut(const ipa::Pwl &pwl, uint32_t *lut, std::size_t lutSize, + unsigned int SlopeBits = 14, unsigned int PosBits = 16) +{ + if (pwl.empty()) + return -EINVAL; + + int lastY = 0; + for (unsigned int i = 0; i < lutSize; i++) { + int x, y; + if (i < 32) + x = i * 512; + else if (i < 48) + x = (i - 32) * 1024 + 16384; + else + x = std::min(65535u, (i - 48) * 2048 + 32768); + + y = pwl.eval(x); + if (y < 0 || (i && y < lastY)) { + LOG(IPARPI, Error) + << "Malformed PWL for Gamma, disabling!"; + return -1; + } + + if (i) { + unsigned int slope = y - lastY; + if (slope >= (1u << SlopeBits)) { + slope = (1u << SlopeBits) - 1; + LOG(IPARPI, Info) + << ("Maximum Gamma slope exceeded, adjusting!"); + y = lastY + slope; + } + lut[i - 1] |= slope << PosBits; + } + + lut[i] = y; + lastY = y; + } + + return 0; +} + +void packLscLut(uint32_t packed[NumLscVertexes][NumLscVertexes], + double const rgb[3][NumLscVertexes][NumLscVertexes]) +{ + for (unsigned int y = 0; y < NumLscVertexes; ++y) { + for (unsigned int x = 0; x < NumLscVertexes; ++x) { + /* Jointly encode RGB gains in one of 4 ranges: [0.5:1.5), [0:2), [0:4), [0:8) */ + double lo = std::min({ rgb[0][y][x], rgb[1][y][x], rgb[2][y][x] }); + double hi = std::max({ rgb[0][y][x], rgb[1][y][x], rgb[2][y][x] }); + uint32_t range; + double scale, offset; + if (lo >= 0.5 && hi < 1.5) { + range = 0; + scale = 1024.0; + offset = -511.5; + } else if (hi < 2.0) { + range = 1; + scale = 512.0; + offset = 0.5; + } else if (hi < 4.0) { + range = 2; + scale = 256.0; + offset = 0.5; + } else { + range = 3; + scale = 128.0; + offset = 0.5; + } + int r = clampField(offset + scale * rgb[0][y][x], 10); + int g = clampField(offset + scale * rgb[1][y][x], 10); + int b = clampField(offset + scale * rgb[2][y][x], 10); + packed[y][x] = (range << 30) | (b << 20) | (g << 10) | r; + } + } +} + +/* + * Resamples a srcW x srcH table with central sampling to destW x destH with + * corner sampling. + */ +void resampleTable(double *dest, int destW, int destH, double const *src, + int srcW, int srcH) +{ + /* + * Precalculate and cache the x sampling locations and phases to + * save recomputing them on every row. + */ + ASSERT(destW > 1 && destH > 1 && destW <= 64); + int xLo[64], xHi[64]; + double xf[64]; + double x = -0.5, xInc = srcW / (destW - 1); + for (int i = 0; i < destW; i++, x += xInc) { + xLo[i] = floor(x); + xf[i] = x - xLo[i]; + xHi[i] = xLo[i] < (srcW - 1) ? (xLo[i] + 1) : (srcW - 1); + xLo[i] = xLo[i] > 0 ? xLo[i] : 0; + } + + /* Now march over the output table generating the new values. */ + double y = -0.5, yInc = srcH / (destH - 1); + for (int j = 0; j < destH; j++, y += yInc) { + int yLo = floor(y); + double yf = y - yLo; + int yHi = yLo < (srcH - 1) ? (yLo + 1) : (srcH - 1); + yLo = yLo > 0 ? yLo : 0; + double const *rowAbove = src + yLo * srcW; + double const *rowBelow = src + yHi * srcW; + for (int i = 0; i < destW; i++) { + double above = rowAbove[xLo[i]] * (1 - xf[i]) + + rowAbove[xHi[i]] * xf[i]; + double below = rowBelow[xLo[i]] * (1 - xf[i]) + + rowBelow[xHi[i]] * xf[i]; + *(dest++) = above * (1 - yf) + below * yf; + } + } +} + +} /* namespace */ + +using ::libpisp::BackEnd; +using ::libpisp::FrontEnd; + +namespace ipa::RPi { + +class IpaPiSP final : public IpaBase +{ +public: + IpaPiSP() + : IpaBase(), fe_(nullptr), be_(nullptr) + { + } + + ~IpaPiSP() + { + if (fe_) + munmap(fe_, sizeof(FrontEnd)); + if (be_) + munmap(be_, sizeof(BackEnd)); + } + +private: + int32_t platformInit(const InitParams ¶ms, InitResult *result) override; + int32_t platformStart(const ControlList &controls, StartResult *result) override; + int32_t platformConfigure(const ConfigParams ¶ms, ConfigResult *result) override; + + void platformPrepareIsp(const PrepareParams ¶ms, + RPiController::Metadata &rpiMetadata) override; + RPiController::StatisticsPtr platformProcessStats(Span<uint8_t> mem) override; + + void handleControls(const ControlList &controls) override; + + void applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcStatus, + pisp_be_global_config &global); + void applyDgOnly(const AgcPrepareStatus *agcPrepareStatus, pisp_be_global_config &global); + void applyCAC(const CacStatus *cacStatus, pisp_be_global_config &global); + void applyContrast(const ContrastStatus *contrastStatus, + pisp_be_global_config &global); + void applyCCM(const CcmStatus *ccmStatus, pisp_be_global_config &global); + void applyBlackLevel(const BlackLevelStatus *blackLevelStatus, + pisp_be_global_config &global); + void applyLensShading(const AlscStatus *alscStatus, + pisp_be_global_config &global); + void applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global); + void applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global); + void applyTdn(const TdnStatus *tdnStatus, const DeviceStatus *deviceStatus, + pisp_be_global_config &global); + void applyCdn(const CdnStatus *cdnStatus, pisp_be_global_config &global); + void applyGeq(const GeqStatus *geqStatus, pisp_be_global_config &global); + void applySaturation(const SaturationStatus *geqStatus, + pisp_be_global_config &global); + void applySharpen(const SharpenStatus *sharpenStatus, + pisp_be_global_config &global); + bool applyStitch(const StitchStatus *stitchStatus, const DeviceStatus *deviceStatus, + const AgcStatus *agcStatus, pisp_be_global_config &global); + void applyTonemap(const TonemapStatus *tonemapStatus, + pisp_be_global_config &global); + void applyFocusStats(const NoiseStatus *noiseStatus); + void applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls); + + void setDefaultConfig(); + void setStatsAndDebin(); + void setHistogramWeights(); + + /* Frontend/Backend objects passed in from the pipeline handler. */ + SharedFD feFD_; + SharedFD beFD_; + FrontEnd *fe_; + BackEnd *be_; + + /* TDN/HDR runtime need the following state. */ + bool tdnReset_; + utils::Duration lastExposure_; + std::map<std::string, utils::Duration> lastStitchExposures_; + HdrStatus lastStitchHdrStatus_; +}; + +int32_t IpaPiSP::platformInit(const InitParams ¶ms, + [[maybe_unused]] InitResult *result) +{ + const std::string &target = controller_.getTarget(); + if (target != "pisp") { + LOG(IPARPI, Error) + << "Tuning data file target returned \"" << target << "\"" + << ", expected \"pisp\""; + return -EINVAL; + } + + /* Acquire the Frontend and Backend objects. */ + feFD_ = std::move(params.fe); + beFD_ = std::move(params.be); + + if (!feFD_.isValid() || !beFD_.isValid()) { + LOG(IPARPI, Error) << "Invalid FE/BE handles!"; + return -ENODEV; + } + + fe_ = static_cast<FrontEnd *>(mmap(nullptr, sizeof(FrontEnd), + PROT_READ | PROT_WRITE, MAP_SHARED, + feFD_.get(), 0)); + be_ = static_cast<BackEnd *>(mmap(nullptr, sizeof(BackEnd), + PROT_READ | PROT_WRITE, MAP_SHARED, + beFD_.get(), 0)); + + if (!fe_ || !be_) { + LOG(IPARPI, Error) << "Unable to map FE/BE handles!"; + return -ENODEV; + } + + setDefaultConfig(); + + return 0; +} + +int32_t IpaPiSP::platformStart([[maybe_unused]] const ControlList &controls, + [[maybe_unused]] StartResult *result) +{ + tdnReset_ = true; + + /* Cause the stitch block to be reset correctly. */ + lastStitchHdrStatus_ = HdrStatus(); + + return 0; +} + +int32_t IpaPiSP::platformConfigure([[maybe_unused]] const ConfigParams ¶ms, + [[maybe_unused]] ConfigResult *result) +{ + setStatsAndDebin(); + return 0; +} + +void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams ¶ms, + RPiController::Metadata &rpiMetadata) +{ + std::scoped_lock<RPiController::Metadata> l(rpiMetadata); + + pisp_be_global_config global; + be_->GetGlobal(global); + + global.bayer_enables &= ~(PISP_BE_BAYER_ENABLE_BLC + PISP_BE_BAYER_ENABLE_WBG + + PISP_BE_BAYER_ENABLE_GEQ + PISP_BE_BAYER_ENABLE_LSC + + PISP_BE_BAYER_ENABLE_SDN + PISP_BE_BAYER_ENABLE_CDN + + PISP_BE_BAYER_ENABLE_TDN_OUTPUT + PISP_BE_BAYER_ENABLE_TDN_INPUT + + PISP_BE_BAYER_ENABLE_STITCH_INPUT + PISP_BE_BAYER_ENABLE_STITCH_OUTPUT + + PISP_BE_BAYER_ENABLE_STITCH + PISP_BE_BAYER_ENABLE_TONEMAP); + /* We leave the YCbCr and inverse conversion enabled in case of false colour or sharpening. */ + global.rgb_enables &= ~(PISP_BE_RGB_ENABLE_GAMMA + PISP_BE_RGB_ENABLE_CCM + + PISP_BE_RGB_ENABLE_SHARPEN + PISP_BE_RGB_ENABLE_SAT_CONTROL); + + NoiseStatus *noiseStatus = rpiMetadata.getLocked<NoiseStatus>("noise.status"); + AgcPrepareStatus *agcPrepareStatus = rpiMetadata.getLocked<AgcPrepareStatus>("agc.prepare_status"); + + { + /* All Frontend config goes first, we do not want to hold the FE lock for long! */ + std::scoped_lock<FrontEnd> lf(*fe_); + + if (noiseStatus) + applyFocusStats(noiseStatus); + + BlackLevelStatus *blackLevelStatus = + rpiMetadata.getLocked<BlackLevelStatus>("black_level.status"); + if (blackLevelStatus) + applyBlackLevel(blackLevelStatus, global); + + AwbStatus *awbStatus = rpiMetadata.getLocked<AwbStatus>("awb.status"); + if (awbStatus && agcPrepareStatus) { + /* Applies digital gain as well. */ + applyWBG(awbStatus, agcPrepareStatus, global); + } else if (agcPrepareStatus) { + /* Mono sensor fallback for digital gain. */ + applyDgOnly(agcPrepareStatus, global); + } + } + + CacStatus *cacStatus = rpiMetadata.getLocked<CacStatus>("cac.status"); + if (cacStatus) + applyCAC(cacStatus, global); + + ContrastStatus *contrastStatus = + rpiMetadata.getLocked<ContrastStatus>("contrast.status"); + if (contrastStatus) + applyContrast(contrastStatus, global); + + CcmStatus *ccmStatus = rpiMetadata.getLocked<CcmStatus>("ccm.status"); + if (ccmStatus) + applyCCM(ccmStatus, global); + + AlscStatus *alscStatus = rpiMetadata.getLocked<AlscStatus>("alsc.status"); + if (alscStatus) + applyLensShading(alscStatus, global); + + DpcStatus *dpcStatus = rpiMetadata.getLocked<DpcStatus>("dpc.status"); + if (dpcStatus) + applyDPC(dpcStatus, global); + + SdnStatus *sdnStatus = rpiMetadata.getLocked<SdnStatus>("sdn.status"); + if (sdnStatus) + applySdn(sdnStatus, global); + + DeviceStatus *deviceStatus = rpiMetadata.getLocked<DeviceStatus>("device.status"); + TdnStatus *tdnStatus = rpiMetadata.getLocked<TdnStatus>("tdn.status"); + if (tdnStatus && deviceStatus) + applyTdn(tdnStatus, deviceStatus, global); + + CdnStatus *cdnStatus = rpiMetadata.getLocked<CdnStatus>("cdn.status"); + if (cdnStatus) + applyCdn(cdnStatus, global); + + GeqStatus *geqStatus = rpiMetadata.getLocked<GeqStatus>("geq.status"); + if (geqStatus) + applyGeq(geqStatus, global); + + SaturationStatus *saturationStatus = + rpiMetadata.getLocked<SaturationStatus>("saturation.status"); + if (saturationStatus) + applySaturation(saturationStatus, global); + + SharpenStatus *sharpenStatus = rpiMetadata.getLocked<SharpenStatus>("sharpen.status"); + if (sharpenStatus) + applySharpen(sharpenStatus, global); + + StitchStatus *stitchStatus = rpiMetadata.getLocked<StitchStatus>("stitch.status"); + if (stitchStatus) { + /* + * Note that it's the *delayed* AGC status that contains the HDR mode/channel + * info that pertains to this frame! + */ + AgcStatus *agcStatus = rpiMetadata.getLocked<AgcStatus>("agc.delayed_status"); + /* prepareIsp() will fetch this value. Maybe pass it back differently? */ + stitchSwapBuffers_ = applyStitch(stitchStatus, deviceStatus, agcStatus, global); + } else + lastStitchHdrStatus_ = HdrStatus(); + + TonemapStatus *tonemapStatus = rpiMetadata.getLocked<TonemapStatus>("tonemap.status"); + if (tonemapStatus) + applyTonemap(tonemapStatus, global); + + be_->SetGlobal(global); + + /* Save this for TDN and HDR on the next frame. */ + lastExposure_ = deviceStatus->exposureTime * deviceStatus->analogueGain; + + /* Lens control */ + const AfStatus *afStatus = rpiMetadata.getLocked<AfStatus>("af.status"); + if (afStatus) { + ControlList lensctrls(lensCtrls_); + applyAF(afStatus, lensctrls); + if (!lensctrls.empty()) + setLensControls.emit(lensctrls); + } +} + +RPiController::StatisticsPtr IpaPiSP::platformProcessStats(Span<uint8_t> mem) +{ + using namespace RPiController; + + const pisp_statistics *stats = reinterpret_cast<pisp_statistics *>(mem.data()); + + unsigned int i; + StatisticsPtr statistics = + std::make_unique<Statistics>(Statistics::AgcStatsPos::PostWb, + Statistics::ColourStatsPos::PreLsc); + + /* RGB histograms are not used, so do not populate them. */ + statistics->yHist = RPiController::Histogram(stats->agc.histogram, + PISP_AGC_STATS_NUM_BINS); + + statistics->awbRegions.init({ PISP_AWB_STATS_SIZE, PISP_AWB_STATS_SIZE }); + for (i = 0; i < statistics->awbRegions.numRegions(); i++) + statistics->awbRegions.set(i, { { stats->awb.zones[i].R_sum, + stats->awb.zones[i].G_sum, + stats->awb.zones[i].B_sum }, + stats->awb.zones[i].counted, 0 }); + + /* AGC region sums only get collected on floating zones. */ + statistics->agcRegions.init({ 0, 0 }, PISP_FLOATING_STATS_NUM_ZONES); + for (i = 0; i < statistics->agcRegions.numRegions(); i++) + statistics->agcRegions.setFloating(i, + { { 0, 0, 0, stats->agc.floating[i].Y_sum }, + stats->agc.floating[i].counted, 0 }); + + statistics->focusRegions.init({ PISP_CDAF_STATS_SIZE, PISP_CDAF_STATS_SIZE }); + for (i = 0; i < statistics->focusRegions.numRegions(); i++) + statistics->focusRegions.set(i, { stats->cdaf.foms[i] >> 20, 0, 0 }); + + if (statsMetadataOutput_) { + Span<const uint8_t> statsSpan(reinterpret_cast<const uint8_t *>(stats), + sizeof(pisp_statistics)); + libcameraMetadata_.set(controls::rpi::PispStatsOutput, statsSpan); + } + + return statistics; +} + +void IpaPiSP::handleControls(const ControlList &controls) +{ + for (auto const &ctrl : controls) { + switch (ctrl.first) { + case controls::HDR_MODE: + case controls::AE_METERING_MODE: + setHistogramWeights(); + break; + + case controls::draft::NOISE_REDUCTION_MODE: { + RPiController::DenoiseAlgorithm *denoise = dynamic_cast<RPiController::DenoiseAlgorithm *>( + controller_.getAlgorithm("denoise")); + + if (!denoise) { + LOG(IPARPI, Warning) + << "Could not set NOISE_REDUCTION_MODE - no Denoise algorithm"; + return; + } + + if (ctrl.second.get<int32_t>() == controls::draft::NoiseReductionModeOff) + denoise->setMode(RPiController::DenoiseMode::Off); + else + denoise->setMode(RPiController::DenoiseMode::ColourHighQuality); + + break; + } + } + } +} + +void IpaPiSP::applyWBG(const AwbStatus *awbStatus, const AgcPrepareStatus *agcPrepareStatus, + pisp_be_global_config &global) +{ + pisp_wbg_config wbg; + pisp_fe_rgby_config rgby = {}; + double dg = agcPrepareStatus ? agcPrepareStatus->digitalGain : 1.0; + + wbg.gain_r = clampField(dg * awbStatus->gainR, 14, 10); + wbg.gain_g = clampField(dg * awbStatus->gainG, 14, 10); + wbg.gain_b = clampField(dg * awbStatus->gainB, 14, 10); + + /* + * The YCbCr conversion block should contain the appropriate YCbCr + * matrix. We should not rely on the CSC0 block as that might be + * programmed for RGB outputs. + */ + pisp_be_ccm_config csc; + be_->GetYcbcr(csc); + + /* The CSC coefficients already have the << 10 scaling applied. */ + rgby.gain_r = clampField(csc.coeffs[0] * awbStatus->gainR, 14); + rgby.gain_g = clampField(csc.coeffs[1] * awbStatus->gainG, 14); + rgby.gain_b = clampField(csc.coeffs[2] * awbStatus->gainB, 14); + + LOG(IPARPI, Debug) << "Applying WB R: " << awbStatus->gainR << " B: " + << awbStatus->gainB; + + be_->SetWbg(wbg); + fe_->SetRGBY(rgby); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_WBG; +} + +void IpaPiSP::applyDgOnly(const AgcPrepareStatus *agcPrepareStatus, pisp_be_global_config &global) +{ + pisp_wbg_config wbg; + + wbg.gain_r = clampField(agcPrepareStatus->digitalGain, 14, 10); + wbg.gain_g = clampField(agcPrepareStatus->digitalGain, 14, 10); + wbg.gain_b = clampField(agcPrepareStatus->digitalGain, 14, 10); + + LOG(IPARPI, Debug) << "Applying DG (only) : " << agcPrepareStatus->digitalGain; + + be_->SetWbg(wbg); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_WBG; +} + +void IpaPiSP::applyContrast(const ContrastStatus *contrastStatus, + pisp_be_global_config &global) +{ + pisp_be_gamma_config gamma; + + if (!generateLut(contrastStatus->gammaCurve, gamma.lut, PISP_BE_GAMMA_LUT_SIZE)) { + be_->SetGamma(gamma); + global.rgb_enables |= PISP_BE_RGB_ENABLE_GAMMA; + } +} + +void IpaPiSP::applyCCM(const CcmStatus *ccmStatus, pisp_be_global_config &global) +{ + pisp_be_ccm_config ccm = {}; + + for (unsigned int i = 0; i < 9; i++) + ccm.coeffs[i] = clampField(ccmStatus->matrix[i], 14, 10, true); + + be_->SetCcm(ccm); + global.rgb_enables |= PISP_BE_RGB_ENABLE_CCM; +} + +void IpaPiSP::applyCAC(const CacStatus *cacStatus, pisp_be_global_config &global) +{ + pisp_be_cac_config cac = {}; + + for (int x = 0; x < PISP_BE_CAC_GRID_SIZE + 1; x++) { + for (int y = 0; y < PISP_BE_CAC_GRID_SIZE + 1; y++) { + cac.lut[y][x][0][0] = clampField(cacStatus->lutRx[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][0][1] = clampField(cacStatus->lutRy[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][1][0] = clampField(cacStatus->lutBx[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + cac.lut[y][x][1][1] = clampField(cacStatus->lutBy[y * (PISP_BE_CAC_GRID_SIZE + 1) + x], 7, 5, true); + } + } + + be_->SetCac(cac); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_CAC; +} + +void IpaPiSP::applyBlackLevel(const BlackLevelStatus *blackLevelStatus, pisp_be_global_config &global) +{ + uint16_t minBlackLevel = std::min({ blackLevelStatus->blackLevelR, blackLevelStatus->blackLevelG, + blackLevelStatus->blackLevelB }); + pisp_bla_config bla; + + /* + * Set the Frontend to adjust the black level to the smallest black level + * of all channels (in 16-bits). + */ + bla.black_level_r = blackLevelStatus->blackLevelR; + bla.black_level_gr = blackLevelStatus->blackLevelG; + bla.black_level_gb = blackLevelStatus->blackLevelG; + bla.black_level_b = blackLevelStatus->blackLevelB; + bla.output_black_level = minBlackLevel; + fe_->SetBla(bla); + + /* Frontend Stats and Backend black level correction. */ + bla.black_level_r = bla.black_level_gr = + bla.black_level_gb = bla.black_level_b = minBlackLevel; + bla.output_black_level = 0; + fe_->SetBlc(bla); + be_->SetBlc(bla); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_BLC; +} + +void IpaPiSP::applyLensShading(const AlscStatus *alscStatus, + pisp_be_global_config &global) +{ + pisp_be_lsc_extra lscExtra = {}; + pisp_be_lsc_config lsc = {}; + double rgb[3][NumLscVertexes][NumLscVertexes] = {}; + + resampleTable(&rgb[0][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->r.data(), NumLscCells, NumLscCells); + resampleTable(&rgb[1][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->g.data(), NumLscCells, NumLscCells); + resampleTable(&rgb[2][0][0], NumLscVertexes, NumLscVertexes, + alscStatus->b.data(), NumLscCells, NumLscCells); + packLscLut(lsc.lut_packed, rgb); + be_->SetLsc(lsc, lscExtra); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_LSC; +} + +void IpaPiSP::applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global) +{ + pisp_be_dpc_config dpc = {}; + + switch (dpcStatus->strength) { + case 0: /* "off" */ + break; + case 1: /* "normal" */ + dpc.coeff_level = 1; + dpc.coeff_range = 8; + global.bayer_enables |= PISP_BE_BAYER_ENABLE_DPC; + break; + case 2: /* "strong" */ + dpc.coeff_level = 0; + dpc.coeff_range = 0; + global.bayer_enables |= PISP_BE_BAYER_ENABLE_DPC; + break; + default: + ASSERT(0); + } + + be_->SetDpc(dpc); +} + +void IpaPiSP::applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global) +{ + pisp_be_sdn_config sdn = {}; + pisp_bla_config blc; + + be_->GetBlc(blc); + /* All R/G/B black levels are the same value in the BE after FE alignment */ + sdn.black_level = blc.black_level_r; + /* leakage is "amount of the original pixel we let through", thus 1 - strength */ + sdn.leakage = clampField(1.0 - sdnStatus->strength, 8, 8); + sdn.noise_constant = clampField(sdnStatus->noiseConstant, 16); + sdn.noise_slope = clampField(sdnStatus->noiseSlope, 16, 8); + sdn.noise_constant2 = clampField(sdnStatus->noiseConstant2, 16); + sdn.noise_slope2 = clampField(sdnStatus->noiseSlope2, 16, 8); + be_->SetSdn(sdn); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_SDN; +} + +void IpaPiSP::applyTdn(const TdnStatus *tdnStatus, const DeviceStatus *deviceStatus, + pisp_be_global_config &global) +{ + utils::Duration exposure = deviceStatus->exposureTime * deviceStatus->analogueGain; + pisp_be_tdn_config tdn = {}; + + double ratio = tdnReset_ ? 1.0 : exposure / lastExposure_; + if (ratio >= 4.0) { + /* If the exposure ratio goes above 4x, we need to reset TDN. */ + ratio = 1; + tdnReset_ = true; + } + + LOG(IPARPI, Debug) << "TDN: exposure: " << exposure + << " last: " << lastExposure_ + << " ratio: " << ratio; + + pisp_bla_config blc; + be_->GetBlc(blc); + /* All R/G/B black levels are the same value in the BE after FE alignment */ + tdn.black_level = blc.black_level_r; + tdn.ratio = clampField(ratio, 16, 14); + tdn.noise_constant = clampField(tdnStatus->noiseConstant, 16); + tdn.noise_slope = clampField(tdnStatus->noiseSlope, 16, 8); + tdn.threshold = clampField(tdnStatus->threshold, 16, 16); + + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN + PISP_BE_BAYER_ENABLE_TDN_OUTPUT; + + /* Only enable the TDN Input after a state reset. */ + if (!tdnReset_) { + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN_INPUT; + tdn.reset = 0; + } else + tdn.reset = 1; + + be_->SetTdn(tdn); + tdnReset_ = false; +} + +void IpaPiSP::applyCdn(const CdnStatus *cdnStatus, pisp_be_global_config &global) +{ + pisp_be_cdn_config cdn = {}; + + cdn.thresh = clampField(cdnStatus->threshold, 16); + cdn.iir_strength = clampField(cdnStatus->strength, 8, 8); + cdn.g_adjust = clampField(0, 8, 8); + be_->SetCdn(cdn); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_CDN; +} + +void IpaPiSP::applyGeq(const GeqStatus *geqStatus, pisp_be_global_config &global) +{ + pisp_be_geq_config geq = {}; + + geq.min = 0; + geq.max = 0xffff; + geq.offset = clampField(geqStatus->offset, 16); + geq.slope_sharper = clampField(geqStatus->slope, 10, 10); + be_->SetGeq(geq); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_GEQ; +} + +void IpaPiSP::applySaturation(const SaturationStatus *saturationStatus, + pisp_be_global_config &global) +{ + pisp_be_sat_control_config saturation; + pisp_wbg_config wbg; + + saturation.shift_r = std::min<uint8_t>(2, saturationStatus->shiftR); + saturation.shift_g = std::min<uint8_t>(2, saturationStatus->shiftG); + saturation.shift_b = std::min<uint8_t>(2, saturationStatus->shiftB); + be_->SetSatControl(saturation); + + be_->GetWbg(wbg); + wbg.gain_r >>= saturationStatus->shiftR; + wbg.gain_g >>= saturationStatus->shiftG; + wbg.gain_b >>= saturationStatus->shiftB; + be_->SetWbg(wbg); + + global.rgb_enables |= PISP_BE_RGB_ENABLE_SAT_CONTROL; +} + +void IpaPiSP::applySharpen(const SharpenStatus *sharpenStatus, + pisp_be_global_config &global) +{ + /* + * This threshold scaling is to normalise the VC4 and PiSP parameter + * scales in the tuning config. + */ + static constexpr double ThresholdScaling = 0.25; + const double scaling = sharpenStatus->threshold * ThresholdScaling; + + pisp_be_sh_fc_combine_config shfc; + pisp_be_sharpen_config sharpen; + + be_->InitialiseSharpen(sharpen, shfc); + sharpen.threshold_offset0 = clampField(sharpen.threshold_offset0 * scaling, 16); + sharpen.threshold_offset1 = clampField(sharpen.threshold_offset1 * scaling, 16); + sharpen.threshold_offset2 = clampField(sharpen.threshold_offset2 * scaling, 16); + sharpen.threshold_offset3 = clampField(sharpen.threshold_offset3 * scaling, 16); + sharpen.threshold_offset4 = clampField(sharpen.threshold_offset4 * scaling, 16); + sharpen.threshold_slope0 = clampField(sharpen.threshold_slope0 * scaling, 12); + sharpen.threshold_slope1 = clampField(sharpen.threshold_slope1 * scaling, 12); + sharpen.threshold_slope2 = clampField(sharpen.threshold_slope2 * scaling, 12); + sharpen.threshold_slope3 = clampField(sharpen.threshold_slope3 * scaling, 12); + sharpen.threshold_slope4 = clampField(sharpen.threshold_slope4 * scaling, 12); + sharpen.positive_strength = clampField(sharpen.positive_strength * sharpenStatus->strength, 12); + sharpen.negative_strength = clampField(sharpen.negative_strength * sharpenStatus->strength, 12); + sharpen.positive_pre_limit = clampField(sharpen.positive_pre_limit * sharpenStatus->limit, 16); + sharpen.positive_limit = clampField(sharpen.positive_limit * sharpenStatus->limit, 16); + sharpen.negative_pre_limit = clampField(sharpen.negative_pre_limit * sharpenStatus->limit, 16); + sharpen.negative_limit = clampField(sharpen.negative_limit * sharpenStatus->limit, 16); + + be_->SetSharpen(sharpen); + /* The conversion to YCbCr and back is always enabled. */ + global.rgb_enables |= PISP_BE_RGB_ENABLE_SHARPEN; +} + +bool IpaPiSP::applyStitch(const StitchStatus *stitchStatus, const DeviceStatus *deviceStatus, + const AgcStatus *agcStatus, pisp_be_global_config &global) +{ + /* + * Find out what HDR mode/channel this frame is. Normally this will be in the delayed + * HDR status (in the AGC status), though after a mode switch this will be absent and + * the information will have been stored in the hdrStatus_ field. + */ + const HdrStatus *hdrStatus = &hdrStatus_; + if (agcStatus) + hdrStatus = &agcStatus->hdr; + + bool modeChange = hdrStatus->mode != lastStitchHdrStatus_.mode; + bool channelChange = !modeChange && hdrStatus->channel != lastStitchHdrStatus_.channel; + lastStitchHdrStatus_ = *hdrStatus; + + /* Check for a change of HDR mode. That forces us to start over. */ + if (modeChange) + lastStitchExposures_.clear(); + + if (hdrStatus->channel != "short" && hdrStatus->channel != "long") { + /* The channel *must* be long or short, anything else does not make sense. */ + LOG(IPARPI, Warning) << "Stitch channel is not long or short"; + return false; + } + + /* Whatever happens, we're going to output this buffer now. */ + global.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_OUTPUT; + + utils::Duration exposure = deviceStatus->exposureTime * deviceStatus->analogueGain; + lastStitchExposures_[hdrStatus->channel] = exposure; + + /* If the other channel hasn't been seen there's nothing more we can do. */ + std::string otherChannel = hdrStatus->channel == "short" ? "long" : "short"; + if (lastStitchExposures_.find(otherChannel) == lastStitchExposures_.end()) { + /* The first channel should be "short". */ + if (hdrStatus->channel != "short") + LOG(IPARPI, Warning) << "First frame is not short"; + return false; + } + + /* We have both channels, we need to enable stitching. */ + global.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_INPUT + PISP_BE_BAYER_ENABLE_STITCH; + + utils::Duration otherExposure = lastStitchExposures_[otherChannel]; + bool phaseLong = hdrStatus->channel == "long"; + double ratio = phaseLong ? otherExposure / exposure : exposure / otherExposure; + + pisp_be_stitch_config stitch = {}; + stitch.exposure_ratio = clampField(ratio, 15, 15); + if (phaseLong) + stitch.exposure_ratio |= PISP_BE_STITCH_STREAMING_LONG; + /* These will be filled in correctly once we have implemented the HDR algorithm. */ + stitch.threshold_lo = stitchStatus->thresholdLo; + stitch.threshold_diff_power = stitchStatus->diffPower; + stitch.motion_threshold_256 = stitchStatus->motionThreshold; + be_->SetStitch(stitch); + + return channelChange; +} + +void IpaPiSP::applyTonemap(const TonemapStatus *tonemapStatus, pisp_be_global_config &global) +{ + pisp_be_tonemap_config tonemap = {}; + + tonemap.detail_constant = clampField(tonemapStatus->detailConstant, 16); + tonemap.detail_slope = clampField(tonemapStatus->detailSlope, 16, 8); + tonemap.iir_strength = clampField(tonemapStatus->iirStrength, 12, 4); + tonemap.strength = clampField(tonemapStatus->strength, 12, 8); + + if (!generateLut(tonemapStatus->tonemap, tonemap.lut, PISP_BE_TONEMAP_LUT_SIZE)) { + be_->SetTonemap(tonemap); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TONEMAP; + } +} + +void IpaPiSP::applyFocusStats(const NoiseStatus *noiseStatus) +{ + pisp_fe_cdaf_stats_config cdaf; + fe_->GetCdafStats(cdaf); + + cdaf.noise_constant = noiseStatus->noiseConstant; + cdaf.noise_slope = noiseStatus->noiseSlope; + fe_->SetCdafStats(cdaf); +} + +void IpaPiSP::applyAF(const struct AfStatus *afStatus, ControlList &lensCtrls) +{ + if (afStatus->lensSetting) { + ControlValue v(afStatus->lensSetting.value()); + lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, v); + } +} + +void IpaPiSP::setDefaultConfig() +{ + std::scoped_lock<FrontEnd> l(*fe_); + + pisp_be_global_config beGlobal; + pisp_fe_global_config feGlobal; + + fe_->GetGlobal(feGlobal); + be_->GetGlobal(beGlobal); + /* + * Always go to YCbCr and back. We need them if the false colour block is enabled, + * and even for mono sensors if sharpening is enabled. So we're better off enabling + * them all the time. + */ + beGlobal.rgb_enables |= PISP_BE_RGB_ENABLE_YCBCR + PISP_BE_RGB_ENABLE_YCBCR_INVERSE; + + if (!monoSensor()) { + beGlobal.bayer_enables |= PISP_BE_BAYER_ENABLE_DEMOSAIC; + beGlobal.rgb_enables |= PISP_BE_RGB_ENABLE_FALSE_COLOUR; + } + + /* + * Ask the AWB algorithm for reasonable gain values so that we can program the + * front end stats sensibly. We must also factor in the conversion to luminance. + */ + pisp_fe_rgby_config rgby = {}; + double gainR = 1.5, gainB = 1.5; + RPiController::AwbAlgorithm *awb = dynamic_cast<RPiController::AwbAlgorithm *>( + controller_.getAlgorithm("awb")); + if (awb) + awb->initialValues(gainR, gainB); + /* The BT.601 RGB -> Y coefficients will do. The precise values are not critical. */ + rgby.gain_r = clampField(gainR * 0.299, 14, 10); + rgby.gain_g = clampField(1.0 * .587, 14, 10); + rgby.gain_b = clampField(gainB * .114, 14, 10); + fe_->SetRGBY(rgby); + feGlobal.enables |= PISP_FE_ENABLE_RGBY; + + /* Also get sensible front end black level defaults, for the same reason. */ + RPiController::BlackLevelAlgorithm *blackLevel = dynamic_cast<RPiController::BlackLevelAlgorithm *>( + controller_.getAlgorithm("black_level")); + if (blackLevel) { + uint16_t blackLevelR, blackLevelG, blackLevelB; + BlackLevelStatus blackLevelStatus; + + blackLevel->initialValues(blackLevelR, blackLevelG, blackLevelB); + blackLevelStatus.blackLevelR = blackLevelR; + blackLevelStatus.blackLevelG = blackLevelG; + blackLevelStatus.blackLevelB = blackLevelB; + applyBlackLevel(&blackLevelStatus, beGlobal); + feGlobal.enables |= PISP_FE_ENABLE_BLA + PISP_FE_ENABLE_BLC; + } + + fe_->SetGlobal(feGlobal); + be_->SetGlobal(beGlobal); +} + +void IpaPiSP::setStatsAndDebin() +{ + pisp_fe_crop_config crop{ 0, 0, mode_.width, mode_.height }; + + pisp_fe_awb_stats_config awb = {}; + awb.r_lo = awb.g_lo = awb.b_lo = 0; + awb.r_hi = awb.g_hi = awb.b_hi = 65535 * 0.98; + + pisp_fe_cdaf_stats_config cdaf = {}; + cdaf.mode = (1 << 4) + (1 << 2) + 1; /* Gr / Gb count with weights of (1, 1) */ + + { + std::scoped_lock<FrontEnd> l(*fe_); + pisp_fe_global_config feGlobal; + fe_->GetGlobal(feGlobal); + feGlobal.enables |= PISP_FE_ENABLE_AWB_STATS + PISP_FE_ENABLE_AGC_STATS + + PISP_FE_ENABLE_CDAF_STATS; + + fe_->SetGlobal(feGlobal); + fe_->SetStatsCrop(crop); + fe_->SetAwbStats(awb); + fe_->SetCdafStats(cdaf); + } + + /* + * Apply the correct AGC region weights to the Frontend. Need to do this + * out of the Frontend scoped lock. + */ + setHistogramWeights(); + + pisp_be_global_config beGlobal; + be_->GetGlobal(beGlobal); + + if (mode_.binX > 1 || mode_.binY > 1) { + pisp_be_debin_config debin; + + be_->GetDebin(debin); + debin.h_enable = (mode_.binX > 1); + debin.v_enable = (mode_.binY > 1); + be_->SetDebin(debin); + beGlobal.bayer_enables |= PISP_BE_BAYER_ENABLE_DEBIN; + } else + beGlobal.bayer_enables &= ~PISP_BE_BAYER_ENABLE_DEBIN; + + be_->SetGlobal(beGlobal); +} + +void IpaPiSP::setHistogramWeights() +{ + RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>( + controller_.getAlgorithm("agc")); + if (!agc) + return; + + const std::vector<double> &weights = agc->getWeights(); + + pisp_fe_agc_stats_config config; + memset(&config, 0, sizeof(config)); + + /* + * The AGC software gives us a 15x15 table of weights which we + * map onto 16x16 in the hardware, ensuring the rightmost column + * and bottom row all have zero weight. We align everything to + * the native 2x2 Bayer pixel blocks. + */ + const Size &size = controller_.getHardwareConfig().agcZoneWeights; + int width = (mode_.width / size.width) & ~1; + int height = (mode_.height / size.height) & ~1; + config.offset_x = ((mode_.width - size.width * width) / 2) & ~1; + config.offset_y = ((mode_.height - size.height * height) / 2) & ~1; + config.size_x = width; + config.size_y = height; + + unsigned int idx = 0; + for (unsigned int row = 0; row < size.height; row++) { + unsigned int col = 0; + for (; col < size.width / 2; col++) { + int wt0 = clampField(weights[idx++], 4, 0, false, "agc weights"); + int wt1 = clampField(weights[idx++], 4, 0, false, "agc weights"); + config.weights[row * 8 + col] = (wt1 << 4) | wt0; + } + if (size.width & 1) + config.weights[row * 8 + col] = + clampField(weights[idx++], 4, 0, false, "agc weights"); + } + + std::scoped_lock<FrontEnd> l(*fe_); + fe_->SetAgcStats(config); +} + +} /* namespace ipa::RPi */ + +/* + * External IPA module interface + */ +extern "C" { +const IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 1, + "rpi/pisp", + "rpi/pisp", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::RPi::IpaPiSP(); +} + +} /* extern "C" */ + +} /* namespace libcamera */ diff --git a/src/ipa/rpi/vc4/data/imx219.json b/src/ipa/rpi/vc4/data/imx219.json index e8fce164..a020b12f 100644 --- a/src/ipa/rpi/vc4/data/imx219.json +++ b/src/ipa/rpi/vc4/data/imx219.json @@ -131,93 +131,306 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 10000, 30000, 60000, 66666 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] - }, - "short": - { - "shutter": [ 100, 5000, 10000, 20000, 33333 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] }, - "long": { - "shutter": [ 100, 10000, 30000, 60000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 0.125, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ], - "shadows": [ + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "base_ev": 1.5, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.0, - "q_hi": 0.5, - "y_target": - [ - 0, 0.17, - 1000, 0.17 + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } ] } }, @@ -463,6 +676,20 @@ }, { "rpi.sharpen": { } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -}
\ No newline at end of file +} diff --git a/src/ipa/rpi/vc4/data/imx219_noir.json b/src/ipa/rpi/vc4/data/imx219_noir.json index cfedb943..d8bc9639 100644 --- a/src/ipa/rpi/vc4/data/imx219_noir.json +++ b/src/ipa/rpi/vc4/data/imx219_noir.json @@ -47,93 +47,306 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": - { - "shutter": [ 100, 10000, 30000, 60000, 66666 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] - }, - "short": + "channels": [ { - "shutter": [ 100, 5000, 10000, 20000, 33333 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] }, - "long": { - "shutter": [ 100, 10000, 30000, 60000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 0.125, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ], - "shadows": [ + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.0, - "q_hi": 0.5, - "y_target": - [ - 0, 0.17, - 1000, 0.17 + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } ] } }, @@ -397,6 +610,20 @@ }, { "rpi.sharpen": { } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -}
\ No newline at end of file +} diff --git a/src/ipa/rpi/vc4/data/imx283.json b/src/ipa/rpi/vc4/data/imx283.json new file mode 100644 index 00000000..89e945cc --- /dev/null +++ b/src/ipa/rpi/vc4/data/imx283.json @@ -0,0 +1,548 @@ +{ + "version": 2.0, + "target": "bcm2835", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3200 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 10857, + "reference_gain": 1.49, + "reference_aperture": 1.0, + "reference_lux": 1050, + "reference_Y": 13959 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.147 + } + }, + { + "rpi.geq": + { + "offset": 249, + "slope": 0.02036 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 8000 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 6000, + "hi": 6800 + } + }, + "bayes": 1, + "ct_curve": + [ + 2500.0, 0.9429, 0.2809, + 2820.0, 0.8488, 0.3472, + 2830.0, 0.8303, 0.3609, + 2885.0, 0.8177, 0.3703, + 3601.0, 0.6935, 0.4705, + 3615.0, 0.6918, 0.4719, + 3622.0, 0.6894, 0.4741, + 4345.0, 0.5999, 0.5546, + 4410.0, 0.5942, 0.5601, + 4486.0, 0.5878, 0.5661, + 4576.0, 0.5779, 0.5756, + 5672.0, 0.5211, 0.6318, + 5710.0, 0.5168, 0.6362, + 6850.0, 0.4841, 0.6702 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.02601, + "transverse_neg": 0.0246 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 2940, + "table": + [ + 1.021, 1.026, 1.028, 1.029, 1.031, 1.029, 1.029, 1.029, 1.029, 1.031, 1.031, 1.028, 1.027, 1.022, 1.013, 1.008, + 1.022, 1.026, 1.027, 1.028, 1.027, 1.026, 1.026, 1.025, 1.026, 1.026, 1.027, 1.027, 1.027, 1.022, 1.014, 1.009, + 1.023, 1.026, 1.026, 1.027, 1.026, 1.025, 1.024, 1.024, 1.024, 1.025, 1.026, 1.027, 1.026, 1.023, 1.017, 1.012, + 1.024, 1.026, 1.026, 1.026, 1.025, 1.024, 1.024, 1.023, 1.023, 1.024, 1.025, 1.026, 1.026, 1.024, 1.018, 1.013, + 1.024, 1.026, 1.026, 1.026, 1.025, 1.024, 1.023, 1.023, 1.023, 1.023, 1.024, 1.026, 1.026, 1.025, 1.019, 1.013, + 1.025, 1.026, 1.026, 1.026, 1.025, 1.024, 1.023, 1.023, 1.023, 1.023, 1.024, 1.026, 1.026, 1.025, 1.018, 1.013, + 1.025, 1.027, 1.027, 1.027, 1.026, 1.025, 1.024, 1.023, 1.023, 1.024, 1.024, 1.026, 1.026, 1.024, 1.018, 1.013, + 1.025, 1.027, 1.028, 1.028, 1.027, 1.026, 1.025, 1.024, 1.024, 1.024, 1.025, 1.026, 1.026, 1.024, 1.017, 1.012, + 1.024, 1.027, 1.029, 1.029, 1.028, 1.027, 1.026, 1.026, 1.025, 1.025, 1.026, 1.026, 1.025, 1.022, 1.014, 1.009, + 1.024, 1.027, 1.029, 1.031, 1.031, 1.029, 1.028, 1.028, 1.028, 1.028, 1.027, 1.026, 1.025, 1.021, 1.011, 1.007, + 1.022, 1.026, 1.031, 1.031, 1.031, 1.032, 1.031, 1.031, 1.029, 1.029, 1.028, 1.026, 1.022, 1.017, 1.007, 1.003, + 1.019, 1.024, 1.029, 1.031, 1.032, 1.032, 1.032, 1.031, 1.029, 1.029, 1.027, 1.024, 1.019, 1.013, 1.003, 1.001 + ] + }, + { + "ct": 4000, + "table": + [ + 1.027, 1.035, 1.039, 1.041, 1.043, 1.043, 1.043, 1.043, 1.044, 1.044, 1.044, 1.041, 1.041, 1.034, 1.021, 1.014, + 1.029, 1.035, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.041, 1.041, 1.041, 1.041, 1.035, 1.024, 1.017, + 1.029, 1.034, 1.036, 1.038, 1.038, 1.038, 1.039, 1.039, 1.039, 1.039, 1.039, 1.041, 1.039, 1.036, 1.027, 1.021, + 1.031, 1.034, 1.036, 1.036, 1.037, 1.037, 1.038, 1.037, 1.037, 1.038, 1.038, 1.039, 1.039, 1.037, 1.029, 1.021, + 1.031, 1.034, 1.035, 1.036, 1.037, 1.037, 1.037, 1.037, 1.037, 1.037, 1.038, 1.038, 1.038, 1.037, 1.029, 1.022, + 1.031, 1.034, 1.035, 1.036, 1.037, 1.037, 1.037, 1.036, 1.036, 1.036, 1.037, 1.038, 1.038, 1.037, 1.029, 1.022, + 1.031, 1.035, 1.036, 1.037, 1.037, 1.037, 1.037, 1.036, 1.036, 1.036, 1.037, 1.038, 1.038, 1.036, 1.028, 1.021, + 1.031, 1.034, 1.036, 1.037, 1.037, 1.037, 1.036, 1.036, 1.036, 1.036, 1.036, 1.037, 1.037, 1.035, 1.026, 1.019, + 1.028, 1.034, 1.037, 1.037, 1.037, 1.037, 1.037, 1.036, 1.036, 1.036, 1.037, 1.037, 1.037, 1.033, 1.022, 1.016, + 1.028, 1.034, 1.037, 1.038, 1.039, 1.038, 1.037, 1.037, 1.037, 1.037, 1.037, 1.037, 1.035, 1.031, 1.017, 1.011, + 1.025, 1.031, 1.036, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.038, 1.038, 1.036, 1.031, 1.024, 1.011, 1.006, + 1.021, 1.028, 1.034, 1.037, 1.039, 1.039, 1.039, 1.038, 1.038, 1.038, 1.036, 1.033, 1.027, 1.019, 1.006, 1.001 + ] + }, + { + "ct": 6000, + "table": + [ + 1.026, 1.037, 1.048, 1.054, 1.057, 1.058, 1.059, 1.059, 1.061, 1.059, 1.059, 1.056, 1.049, 1.038, 1.019, 1.013, + 1.031, 1.039, 1.049, 1.054, 1.057, 1.058, 1.059, 1.059, 1.059, 1.059, 1.059, 1.056, 1.051, 1.042, 1.026, 1.018, + 1.033, 1.044, 1.051, 1.054, 1.057, 1.058, 1.059, 1.059, 1.059, 1.059, 1.058, 1.058, 1.055, 1.046, 1.031, 1.023, + 1.035, 1.045, 1.051, 1.055, 1.057, 1.059, 1.059, 1.059, 1.059, 1.059, 1.059, 1.058, 1.056, 1.049, 1.035, 1.026, + 1.037, 1.046, 1.052, 1.055, 1.058, 1.059, 1.059, 1.059, 1.059, 1.059, 1.059, 1.058, 1.057, 1.051, 1.037, 1.027, + 1.037, 1.047, 1.053, 1.056, 1.059, 1.059, 1.061, 1.059, 1.059, 1.059, 1.059, 1.058, 1.057, 1.051, 1.037, 1.027, + 1.037, 1.047, 1.053, 1.057, 1.059, 1.059, 1.061, 1.061, 1.059, 1.059, 1.059, 1.058, 1.056, 1.049, 1.036, 1.026, + 1.037, 1.047, 1.054, 1.057, 1.059, 1.059, 1.061, 1.061, 1.059, 1.059, 1.059, 1.058, 1.056, 1.048, 1.034, 1.025, + 1.034, 1.045, 1.054, 1.057, 1.059, 1.059, 1.059, 1.059, 1.059, 1.059, 1.058, 1.057, 1.053, 1.045, 1.029, 1.021, + 1.032, 1.043, 1.052, 1.057, 1.058, 1.059, 1.059, 1.059, 1.059, 1.059, 1.058, 1.055, 1.049, 1.041, 1.022, 1.013, + 1.028, 1.037, 1.048, 1.053, 1.057, 1.059, 1.059, 1.059, 1.059, 1.058, 1.056, 1.051, 1.044, 1.032, 1.013, 1.007, + 1.021, 1.033, 1.044, 1.051, 1.055, 1.058, 1.059, 1.059, 1.058, 1.057, 1.052, 1.047, 1.039, 1.026, 1.007, 1.001 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 2940, + "table": + [ + 1.002, 1.012, 1.031, 1.042, 1.051, 1.056, 1.058, 1.058, 1.058, 1.058, 1.057, 1.055, 1.045, 1.033, 1.017, 1.016, + 1.011, 1.026, 1.041, 1.048, 1.056, 1.063, 1.066, 1.067, 1.067, 1.066, 1.064, 1.061, 1.051, 1.045, 1.028, 1.017, + 1.016, 1.033, 1.047, 1.056, 1.063, 1.067, 1.071, 1.072, 1.072, 1.071, 1.068, 1.064, 1.061, 1.051, 1.033, 1.024, + 1.021, 1.038, 1.051, 1.061, 1.067, 1.071, 1.073, 1.075, 1.075, 1.074, 1.071, 1.068, 1.063, 1.054, 1.036, 1.025, + 1.023, 1.041, 1.054, 1.063, 1.069, 1.073, 1.075, 1.077, 1.077, 1.076, 1.074, 1.069, 1.064, 1.055, 1.038, 1.027, + 1.023, 1.043, 1.055, 1.063, 1.069, 1.074, 1.076, 1.078, 1.078, 1.077, 1.075, 1.071, 1.065, 1.056, 1.039, 1.028, + 1.023, 1.043, 1.055, 1.063, 1.069, 1.074, 1.076, 1.077, 1.078, 1.076, 1.074, 1.071, 1.065, 1.056, 1.039, 1.028, + 1.023, 1.041, 1.052, 1.062, 1.068, 1.072, 1.074, 1.076, 1.076, 1.075, 1.073, 1.069, 1.064, 1.055, 1.038, 1.028, + 1.021, 1.038, 1.051, 1.059, 1.066, 1.069, 1.072, 1.074, 1.074, 1.073, 1.069, 1.067, 1.062, 1.052, 1.036, 1.027, + 1.018, 1.032, 1.046, 1.055, 1.061, 1.066, 1.069, 1.069, 1.069, 1.069, 1.067, 1.062, 1.057, 1.047, 1.031, 1.021, + 1.011, 1.023, 1.039, 1.049, 1.056, 1.061, 1.062, 1.064, 1.065, 1.064, 1.062, 1.058, 1.049, 1.038, 1.021, 1.016, + 1.001, 1.019, 1.035, 1.046, 1.053, 1.058, 1.061, 1.062, 1.062, 1.062, 1.059, 1.053, 1.043, 1.033, 1.016, 1.011 + ] + }, + { + "ct": 4000, + "table": + [ + 1.001, 1.003, 1.011, 1.016, 1.019, 1.019, 1.021, 1.021, 1.019, 1.019, 1.019, 1.017, 1.017, 1.013, 1.007, 1.006, + 1.003, 1.011, 1.015, 1.021, 1.024, 1.026, 1.027, 1.027, 1.027, 1.026, 1.025, 1.023, 1.022, 1.016, 1.012, 1.007, + 1.007, 1.015, 1.021, 1.024, 1.027, 1.029, 1.031, 1.031, 1.031, 1.029, 1.028, 1.026, 1.024, 1.022, 1.015, 1.011, + 1.011, 1.017, 1.023, 1.027, 1.029, 1.032, 1.033, 1.033, 1.033, 1.033, 1.031, 1.028, 1.026, 1.024, 1.016, 1.011, + 1.012, 1.019, 1.025, 1.029, 1.032, 1.033, 1.034, 1.035, 1.035, 1.034, 1.033, 1.031, 1.028, 1.025, 1.018, 1.014, + 1.013, 1.021, 1.026, 1.031, 1.033, 1.034, 1.036, 1.036, 1.036, 1.035, 1.034, 1.032, 1.029, 1.026, 1.019, 1.015, + 1.013, 1.021, 1.026, 1.031, 1.033, 1.035, 1.036, 1.037, 1.037, 1.036, 1.034, 1.032, 1.029, 1.027, 1.019, 1.016, + 1.013, 1.021, 1.026, 1.031, 1.033, 1.035, 1.036, 1.036, 1.036, 1.036, 1.035, 1.033, 1.031, 1.027, 1.021, 1.016, + 1.013, 1.021, 1.025, 1.029, 1.032, 1.034, 1.035, 1.035, 1.036, 1.035, 1.034, 1.032, 1.031, 1.027, 1.021, 1.015, + 1.012, 1.019, 1.024, 1.027, 1.029, 1.032, 1.034, 1.034, 1.034, 1.034, 1.032, 1.031, 1.029, 1.026, 1.019, 1.015, + 1.009, 1.015, 1.022, 1.025, 1.028, 1.029, 1.031, 1.032, 1.032, 1.031, 1.031, 1.029, 1.026, 1.023, 1.017, 1.015, + 1.005, 1.014, 1.021, 1.025, 1.027, 1.029, 1.029, 1.031, 1.031, 1.031, 1.029, 1.029, 1.024, 1.021, 1.016, 1.015 + ] + }, + { + "ct": 6000, + "table": + [ + 1.001, 1.001, 1.006, 1.007, 1.008, 1.009, 1.009, 1.009, 1.009, 1.009, 1.009, 1.011, 1.011, 1.011, 1.009, 1.008, + 1.001, 1.005, 1.008, 1.011, 1.012, 1.013, 1.014, 1.014, 1.014, 1.013, 1.013, 1.014, 1.014, 1.012, 1.011, 1.009, + 1.004, 1.008, 1.011, 1.012, 1.014, 1.016, 1.016, 1.016, 1.016, 1.016, 1.015, 1.015, 1.015, 1.014, 1.012, 1.011, + 1.005, 1.009, 1.012, 1.014, 1.016, 1.017, 1.018, 1.018, 1.018, 1.018, 1.017, 1.016, 1.016, 1.015, 1.012, 1.011, + 1.006, 1.011, 1.013, 1.015, 1.017, 1.018, 1.018, 1.019, 1.019, 1.019, 1.018, 1.017, 1.016, 1.015, 1.012, 1.011, + 1.007, 1.011, 1.013, 1.015, 1.017, 1.018, 1.019, 1.019, 1.019, 1.019, 1.019, 1.018, 1.017, 1.016, 1.013, 1.011, + 1.007, 1.012, 1.013, 1.015, 1.017, 1.018, 1.019, 1.019, 1.019, 1.019, 1.019, 1.018, 1.018, 1.017, 1.014, 1.013, + 1.007, 1.012, 1.013, 1.015, 1.016, 1.018, 1.019, 1.019, 1.019, 1.019, 1.019, 1.018, 1.018, 1.017, 1.015, 1.014, + 1.007, 1.011, 1.012, 1.014, 1.016, 1.017, 1.018, 1.018, 1.019, 1.019, 1.019, 1.018, 1.018, 1.018, 1.016, 1.015, + 1.007, 1.011, 1.012, 1.013, 1.015, 1.016, 1.017, 1.017, 1.018, 1.018, 1.018, 1.018, 1.018, 1.017, 1.016, 1.015, + 1.006, 1.009, 1.012, 1.013, 1.014, 1.015, 1.015, 1.016, 1.017, 1.017, 1.017, 1.017, 1.017, 1.017, 1.017, 1.016, + 1.005, 1.009, 1.012, 1.013, 1.015, 1.015, 1.015, 1.015, 1.016, 1.017, 1.017, 1.017, 1.017, 1.017, 1.017, 1.017 + ] + } + ], + "luminance_lut": + [ + 1.223, 1.187, 1.129, 1.085, 1.061, 1.049, 1.046, 1.046, 1.046, 1.051, 1.061, 1.089, 1.134, 1.212, 1.359, 1.367, + 1.188, 1.141, 1.098, 1.065, 1.048, 1.037, 1.029, 1.029, 1.034, 1.036, 1.046, 1.066, 1.095, 1.158, 1.269, 1.359, + 1.158, 1.109, 1.073, 1.049, 1.035, 1.025, 1.019, 1.016, 1.017, 1.022, 1.033, 1.047, 1.072, 1.127, 1.219, 1.269, + 1.147, 1.092, 1.058, 1.039, 1.026, 1.017, 1.011, 1.007, 1.009, 1.015, 1.022, 1.035, 1.058, 1.107, 1.191, 1.236, + 1.144, 1.082, 1.051, 1.033, 1.021, 1.011, 1.005, 1.002, 1.004, 1.009, 1.017, 1.031, 1.051, 1.097, 1.177, 1.232, + 1.144, 1.081, 1.049, 1.031, 1.018, 1.008, 1.002, 1.001, 1.001, 1.006, 1.015, 1.029, 1.048, 1.096, 1.177, 1.232, + 1.144, 1.084, 1.051, 1.032, 1.018, 1.009, 1.004, 1.001, 1.002, 1.009, 1.016, 1.029, 1.051, 1.098, 1.183, 1.232, + 1.149, 1.096, 1.057, 1.037, 1.022, 1.014, 1.008, 1.005, 1.007, 1.012, 1.019, 1.033, 1.059, 1.113, 1.205, 1.248, + 1.166, 1.117, 1.071, 1.046, 1.031, 1.021, 1.014, 1.012, 1.014, 1.019, 1.029, 1.045, 1.078, 1.141, 1.247, 1.314, + 1.202, 1.151, 1.096, 1.061, 1.044, 1.031, 1.023, 1.021, 1.022, 1.029, 1.044, 1.067, 1.109, 1.182, 1.314, 1.424, + 1.242, 1.202, 1.134, 1.088, 1.061, 1.045, 1.038, 1.036, 1.039, 1.048, 1.066, 1.103, 1.157, 1.248, 1.424, 1.532, + 1.318, 1.238, 1.162, 1.111, 1.078, 1.059, 1.048, 1.048, 1.049, 1.063, 1.089, 1.133, 1.189, 1.296, 1.532, 1.606 + ], + "sigma": 0.00175, + "sigma_Cb": 0.00268 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2500, + "ccm": + [ + 1.82257, -0.40941, -0.41316, + -0.52091, 1.83005, -0.30915, + 0.22503, -1.41259, 2.18757 + ] + }, + { + "ct": 2820, + "ccm": + [ + 1.80564, -0.47587, -0.32977, + -0.47385, 1.83075, -0.35691, + 0.21369, -1.22609, 2.01239 + ] + }, + { + "ct": 2830, + "ccm": + [ + 1.80057, -0.51479, -0.28578, + -0.64031, 2.16074, -0.52044, + 0.11794, -0.95667, 1.83873 + ] + }, + { + "ct": 2885, + "ccm": + [ + 1.78452, -0.49769, -0.28683, + -0.63651, 2.13634, -0.49983, + 0.08547, -0.86501, 1.77954 + ] + }, + { + "ct": 3601, + "ccm": + [ + 1.85165, -0.57008, -0.28156, + -0.56249, 2.08321, -0.52072, + 0.03724, -0.70964, 1.67239 + ] + }, + { + "ct": 3615, + "ccm": + [ + 1.87611, -0.60772, -0.26839, + -0.55497, 2.07257, -0.51761, + 0.04151, -0.70635, 1.66485 + ] + }, + { + "ct": 3622, + "ccm": + [ + 1.85505, -0.58542, -0.26963, + -0.55053, 2.05981, -0.50928, + 0.04005, -0.69302, 1.65297 + ] + }, + { + "ct": 4345, + "ccm": + [ + 1.81872, -0.57511, -0.24361, + -0.49071, 2.16621, -0.67551, + 0.02641, -0.67838, 1.65196 + ] + }, + { + "ct": 4410, + "ccm": + [ + 1.83689, -0.60178, -0.23512, + -0.48204, 2.14729, -0.66525, + 0.02773, -0.67615, 1.64841 + ] + }, + { + "ct": 4486, + "ccm": + [ + 1.85101, -0.60733, -0.24368, + -0.47635, 2.13101, -0.65465, + 0.02229, -0.66412, 1.64183 + ] + }, + { + "ct": 4576, + "ccm": + [ + 1.84076, -0.59449, -0.24626, + -0.47307, 2.13369, -0.66062, + 0.01984, -0.65788, 1.63804 + ] + }, + { + "ct": 5657, + "ccm": + [ + 1.84536, -0.57827, -0.26709, + -0.44532, 2.04086, -0.59554, + -0.01738, -0.52806, 1.54544 + ] + }, + { + "ct": 5672, + "ccm": + [ + 1.84251, -0.57486, -0.26765, + -0.44925, 2.04615, -0.59689, + -0.03179, -0.51748, 1.54928 + ] + }, + { + "ct": 5710, + "ccm": + [ + 1.84081, -0.58127, -0.25953, + -0.44169, 2.03593, -0.59424, + -0.02503, -0.52696, 1.55199 + ] + }, + { + "ct": 6850, + "ccm": + [ + 1.80426, -0.22567, -0.57859, + -0.48629, 2.49024, -1.00395, + -0.10865, -0.63841, 1.74705 + ] + } + ] + } + }, + { + "rpi.sharpen": { } + } + ] +} diff --git a/src/ipa/rpi/vc4/data/imx290.json b/src/ipa/rpi/vc4/data/imx290.json index 8a7cadba..8f41bf51 100644 --- a/src/ipa/rpi/vc4/data/imx290.json +++ b/src/ipa/rpi/vc4/data/imx290.json @@ -52,15 +52,24 @@ { "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] }, "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/imx296.json b/src/ipa/rpi/vc4/data/imx296.json index 7621f759..8f24ce5b 100644 --- a/src/ipa/rpi/vc4/data/imx296.json +++ b/src/ipa/rpi/vc4/data/imx296.json @@ -135,15 +135,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": @@ -431,4 +440,4 @@ } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx296_mono.json b/src/ipa/rpi/vc4/data/imx296_mono.json index d4140c81..fe331569 100644 --- a/src/ipa/rpi/vc4/data/imx296_mono.json +++ b/src/ipa/rpi/vc4/data/imx296_mono.json @@ -38,15 +38,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": @@ -228,4 +237,4 @@ } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx327.json b/src/ipa/rpi/vc4/data/imx327.json new file mode 100644 index 00000000..40a56842 --- /dev/null +++ b/src/ipa/rpi/vc4/data/imx327.json @@ -0,0 +1,215 @@ +{ + "version": 2.0, + "target": "bcm2835", + "description": "This is an interim tuning only. Please consider doing a more formal tuning for your application.", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 6813, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 890, + "reference_Y": 12900 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.67 + } + }, + { + "rpi.geq": + { + "offset": 187, + "slope": 0.00842 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "speed": 0.2, + "metering_modes": + { + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 10, 30000, 60000 ], + "gain": [ 1.0, 2.0, 8.0 ] + }, + "short": + { + "shutter": [ 10, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.16 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.7, + "luminance_lut": + [ + 2.844, 2.349, 2.018, 1.775, 1.599, 1.466, 1.371, 1.321, 1.306, 1.316, 1.357, 1.439, 1.552, 1.705, 1.915, 2.221, + 2.576, 2.151, 1.851, 1.639, 1.478, 1.358, 1.272, 1.231, 1.218, 1.226, 1.262, 1.335, 1.438, 1.571, 1.766, 2.067, + 2.381, 2.005, 1.739, 1.545, 1.389, 1.278, 1.204, 1.166, 1.153, 1.161, 1.194, 1.263, 1.356, 1.489, 1.671, 1.943, + 2.242, 1.899, 1.658, 1.481, 1.329, 1.225, 1.156, 1.113, 1.096, 1.107, 1.143, 1.201, 1.289, 1.423, 1.607, 1.861, + 2.152, 1.831, 1.602, 1.436, 1.291, 1.193, 1.121, 1.069, 1.047, 1.062, 1.107, 1.166, 1.249, 1.384, 1.562, 1.801, + 2.104, 1.795, 1.572, 1.407, 1.269, 1.174, 1.099, 1.041, 1.008, 1.029, 1.083, 1.146, 1.232, 1.364, 1.547, 1.766, + 2.104, 1.796, 1.572, 1.403, 1.264, 1.171, 1.097, 1.036, 1.001, 1.025, 1.077, 1.142, 1.231, 1.363, 1.549, 1.766, + 2.148, 1.827, 1.594, 1.413, 1.276, 1.184, 1.114, 1.062, 1.033, 1.049, 1.092, 1.153, 1.242, 1.383, 1.577, 1.795, + 2.211, 1.881, 1.636, 1.455, 1.309, 1.214, 1.149, 1.104, 1.081, 1.089, 1.125, 1.184, 1.273, 1.423, 1.622, 1.846, + 2.319, 1.958, 1.698, 1.516, 1.362, 1.262, 1.203, 1.156, 1.137, 1.142, 1.171, 1.229, 1.331, 1.484, 1.682, 1.933, + 2.459, 2.072, 1.789, 1.594, 1.441, 1.331, 1.261, 1.219, 1.199, 1.205, 1.232, 1.301, 1.414, 1.571, 1.773, 2.052, + 2.645, 2.206, 1.928, 1.728, 1.559, 1.451, 1.352, 1.301, 1.282, 1.289, 1.319, 1.395, 1.519, 1.685, 1.904, 2.227 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.sharpen": { } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 3900, + "ccm": + [ + 1.54659, -0.17707, -0.36953, + -0.51471, 1.72733, -0.21262, + 0.06667, -0.92279, 1.85612 + ] + } + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx378.json b/src/ipa/rpi/vc4/data/imx378.json index f7b68011..363b47e1 100644 --- a/src/ipa/rpi/vc4/data/imx378.json +++ b/src/ipa/rpi/vc4/data/imx378.json @@ -133,15 +133,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/imx415.json b/src/ipa/rpi/vc4/data/imx415.json new file mode 100755 index 00000000..6ed16b17 --- /dev/null +++ b/src/ipa/rpi/vc4/data/imx415.json @@ -0,0 +1,413 @@ +{ + "version": 2.0, + "target": "bcm2835", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 19230, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 1198, + "reference_Y": 14876 + } + }, + { + "rpi.noise": + { + "reference_constant": 17, + "reference_slope": 3.439 + } + }, + { + "rpi.geq": + { + "offset": 193, + "slope": 0.00902 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 8000 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + "cloudy": + { + "lo": 7000, + "hi": 8600 + } + }, + "bayes": 1, + "ct_curve": + [ + 2698.0, 0.7681, 0.2026, + 2930.0, 0.7515, 0.2116, + 3643.0, 0.6355, 0.2858, + 4605.0, 0.4992, 0.4041, + 5658.0, 0.4498, 0.4574 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.0112, + "transverse_neg": 0.01424 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 3000, + "table": + [ + 1.025, 1.016, 1.013, 1.011, 1.008, 1.005, 1.003, 1.001, 1.003, 1.005, 1.008, 1.011, 1.014, 1.019, 1.027, 1.035, + 1.025, 1.017, 1.013, 1.011, 1.008, 1.005, 1.003, 1.003, 1.004, 1.005, 1.009, 1.012, 1.017, 1.023, 1.029, 1.035, + 1.022, 1.017, 1.013, 1.009, 1.007, 1.005, 1.003, 1.003, 1.004, 1.006, 1.009, 1.012, 1.017, 1.023, 1.029, 1.035, + 1.019, 1.015, 1.011, 1.007, 1.005, 1.003, 1.001, 1.001, 1.003, 1.004, 1.007, 1.009, 1.015, 1.022, 1.028, 1.035, + 1.018, 1.014, 1.009, 1.006, 1.004, 1.002, 1.001, 1.001, 1.001, 1.003, 1.006, 1.009, 1.015, 1.021, 1.028, 1.035, + 1.018, 1.013, 1.011, 1.006, 1.003, 1.002, 1.001, 1.001, 1.001, 1.003, 1.006, 1.009, 1.015, 1.022, 1.028, 1.036, + 1.018, 1.014, 1.011, 1.007, 1.004, 1.002, 1.001, 1.001, 1.001, 1.004, 1.007, 1.009, 1.015, 1.023, 1.029, 1.036, + 1.019, 1.014, 1.012, 1.008, 1.005, 1.003, 1.002, 1.001, 1.003, 1.005, 1.008, 1.012, 1.016, 1.024, 1.031, 1.037, + 1.021, 1.016, 1.013, 1.009, 1.008, 1.005, 1.003, 1.003, 1.005, 1.008, 1.011, 1.014, 1.019, 1.026, 1.033, 1.039, + 1.025, 1.021, 1.016, 1.013, 1.009, 1.008, 1.006, 1.006, 1.008, 1.011, 1.014, 1.019, 1.024, 1.031, 1.038, 1.046, + 1.029, 1.025, 1.021, 1.018, 1.014, 1.013, 1.011, 1.011, 1.012, 1.015, 1.019, 1.023, 1.028, 1.035, 1.046, 1.051, + 1.032, 1.029, 1.023, 1.021, 1.018, 1.015, 1.014, 1.014, 1.015, 1.018, 1.022, 1.027, 1.033, 1.041, 1.051, 1.054 + ] + }, + { + "ct": 5000, + "table": + [ + 1.025, 1.011, 1.009, 1.005, 1.004, 1.003, 1.001, 1.001, 1.002, 1.006, 1.009, 1.012, 1.016, 1.021, 1.031, 1.041, + 1.025, 1.014, 1.009, 1.007, 1.005, 1.004, 1.003, 1.003, 1.004, 1.007, 1.009, 1.013, 1.021, 1.028, 1.037, 1.041, + 1.023, 1.014, 1.009, 1.007, 1.005, 1.004, 1.003, 1.003, 1.005, 1.007, 1.011, 1.014, 1.021, 1.028, 1.037, 1.048, + 1.022, 1.012, 1.007, 1.005, 1.002, 1.001, 1.001, 1.001, 1.003, 1.005, 1.009, 1.014, 1.019, 1.028, 1.039, 1.048, + 1.022, 1.011, 1.006, 1.003, 1.001, 1.001, 1.001, 1.001, 1.002, 1.005, 1.009, 1.014, 1.021, 1.029, 1.039, 1.051, + 1.022, 1.012, 1.007, 1.003, 1.002, 1.001, 1.001, 1.001, 1.002, 1.005, 1.009, 1.015, 1.021, 1.031, 1.041, 1.053, + 1.023, 1.013, 1.009, 1.005, 1.003, 1.003, 1.001, 1.002, 1.004, 1.006, 1.011, 1.015, 1.022, 1.031, 1.042, 1.056, + 1.024, 1.015, 1.012, 1.008, 1.005, 1.004, 1.004, 1.004, 1.006, 1.009, 1.013, 1.018, 1.024, 1.034, 1.045, 1.057, + 1.027, 1.017, 1.015, 1.012, 1.009, 1.007, 1.007, 1.008, 1.009, 1.013, 1.018, 1.023, 1.029, 1.038, 1.051, 1.061, + 1.029, 1.023, 1.017, 1.015, 1.014, 1.012, 1.011, 1.011, 1.014, 1.018, 1.024, 1.029, 1.036, 1.044, 1.056, 1.066, + 1.034, 1.028, 1.023, 1.022, 1.019, 1.019, 1.018, 1.018, 1.021, 1.025, 1.031, 1.035, 1.042, 1.053, 1.066, 1.074, + 1.041, 1.034, 1.027, 1.025, 1.025, 1.023, 1.023, 1.023, 1.025, 1.031, 1.035, 1.041, 1.049, 1.059, 1.074, 1.079 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 3000, + "table": + [ + 1.001, 1.001, 1.007, 1.015, 1.027, 1.034, 1.038, 1.041, 1.042, 1.043, 1.043, 1.043, 1.041, 1.039, 1.049, 1.054, + 1.011, 1.011, 1.013, 1.023, 1.032, 1.039, 1.044, 1.047, 1.052, 1.056, 1.059, 1.059, 1.055, 1.051, 1.054, 1.056, + 1.015, 1.015, 1.019, 1.032, 1.039, 1.044, 1.047, 1.052, 1.055, 1.059, 1.061, 1.066, 1.063, 1.058, 1.061, 1.064, + 1.016, 1.017, 1.023, 1.032, 1.041, 1.045, 1.048, 1.053, 1.056, 1.061, 1.066, 1.069, 1.067, 1.064, 1.065, 1.068, + 1.018, 1.019, 1.025, 1.033, 1.042, 1.045, 1.049, 1.054, 1.058, 1.063, 1.071, 1.072, 1.071, 1.068, 1.069, 1.071, + 1.023, 1.024, 1.029, 1.035, 1.043, 1.048, 1.052, 1.057, 1.061, 1.065, 1.074, 1.075, 1.075, 1.072, 1.072, 1.075, + 1.027, 1.028, 1.031, 1.038, 1.045, 1.051, 1.054, 1.059, 1.064, 1.068, 1.075, 1.079, 1.078, 1.075, 1.076, 1.081, + 1.029, 1.031, 1.033, 1.044, 1.048, 1.054, 1.059, 1.064, 1.067, 1.073, 1.079, 1.082, 1.082, 1.079, 1.081, 1.085, + 1.033, 1.033, 1.035, 1.047, 1.053, 1.058, 1.064, 1.067, 1.073, 1.079, 1.084, 1.086, 1.086, 1.084, 1.089, 1.091, + 1.037, 1.037, 1.038, 1.049, 1.057, 1.062, 1.068, 1.073, 1.079, 1.084, 1.089, 1.092, 1.092, 1.092, 1.096, 1.104, + 1.041, 1.041, 1.043, 1.051, 1.061, 1.068, 1.073, 1.079, 1.083, 1.089, 1.092, 1.094, 1.097, 1.099, 1.105, 1.115, + 1.048, 1.044, 1.044, 1.051, 1.063, 1.071, 1.076, 1.082, 1.088, 1.091, 1.094, 1.097, 1.099, 1.104, 1.115, 1.126 + ] + }, + { + "ct": 5000, + "table": + [ + 1.001, 1.001, 1.005, 1.011, 1.014, 1.018, 1.019, 1.019, 1.019, 1.021, 1.021, 1.021, 1.019, 1.017, 1.014, 1.014, + 1.009, 1.009, 1.011, 1.014, 1.019, 1.024, 1.026, 1.029, 1.031, 1.032, 1.032, 1.031, 1.027, 1.023, 1.022, 1.022, + 1.011, 1.012, 1.015, 1.018, 1.024, 1.026, 1.029, 1.032, 1.035, 1.036, 1.036, 1.034, 1.031, 1.027, 1.025, 1.025, + 1.012, 1.013, 1.015, 1.019, 1.025, 1.029, 1.032, 1.035, 1.036, 1.038, 1.038, 1.036, 1.034, 1.029, 1.026, 1.026, + 1.013, 1.014, 1.016, 1.019, 1.027, 1.031, 1.034, 1.037, 1.039, 1.039, 1.041, 1.039, 1.036, 1.031, 1.028, 1.027, + 1.014, 1.014, 1.017, 1.021, 1.027, 1.033, 1.037, 1.039, 1.041, 1.041, 1.042, 1.042, 1.039, 1.033, 1.029, 1.028, + 1.015, 1.015, 1.018, 1.021, 1.027, 1.033, 1.037, 1.041, 1.041, 1.042, 1.042, 1.042, 1.039, 1.034, 1.029, 1.029, + 1.015, 1.016, 1.018, 1.022, 1.027, 1.033, 1.037, 1.041, 1.041, 1.042, 1.043, 1.043, 1.041, 1.035, 1.031, 1.031, + 1.015, 1.016, 1.018, 1.022, 1.027, 1.032, 1.037, 1.041, 1.042, 1.042, 1.044, 1.043, 1.041, 1.036, 1.034, 1.033, + 1.016, 1.017, 1.017, 1.022, 1.027, 1.032, 1.036, 1.039, 1.042, 1.042, 1.043, 1.043, 1.041, 1.039, 1.036, 1.034, + 1.017, 1.017, 1.018, 1.022, 1.027, 1.031, 1.035, 1.039, 1.041, 1.042, 1.042, 1.042, 1.042, 1.039, 1.039, 1.039, + 1.018, 1.017, 1.017, 1.021, 1.027, 1.031, 1.033, 1.038, 1.041, 1.041, 1.042, 1.042, 1.041, 1.041, 1.041, 1.041 + ] + } + ], + "luminance_lut": + [ + 2.102, 1.903, 1.658, 1.483, 1.358, 1.267, 1.202, 1.202, 1.202, 1.242, 1.323, 1.431, 1.585, 1.797, 2.096, 2.351, + 1.996, 1.776, 1.549, 1.385, 1.273, 1.204, 1.138, 1.133, 1.133, 1.185, 1.252, 1.343, 1.484, 1.679, 1.954, 2.228, + 1.923, 1.689, 1.474, 1.318, 1.204, 1.138, 1.079, 1.071, 1.071, 1.133, 1.185, 1.284, 1.415, 1.597, 1.854, 2.146, + 1.881, 1.631, 1.423, 1.272, 1.159, 1.079, 1.051, 1.026, 1.046, 1.071, 1.144, 1.245, 1.369, 1.543, 1.801, 2.095, + 1.867, 1.595, 1.391, 1.242, 1.131, 1.051, 1.013, 1.002, 1.013, 1.046, 1.121, 1.219, 1.343, 1.511, 1.752, 2.079, + 1.867, 1.589, 1.385, 1.236, 1.125, 1.048, 1.001, 1.001, 1.003, 1.045, 1.118, 1.217, 1.342, 1.511, 1.746, 2.079, + 1.867, 1.589, 1.385, 1.236, 1.125, 1.048, 1.011, 1.003, 1.011, 1.046, 1.118, 1.217, 1.343, 1.511, 1.746, 2.079, + 1.884, 1.621, 1.411, 1.261, 1.149, 1.071, 1.048, 1.024, 1.046, 1.069, 1.141, 1.239, 1.369, 1.541, 1.781, 2.093, + 1.913, 1.675, 1.459, 1.304, 1.191, 1.125, 1.071, 1.065, 1.069, 1.124, 1.181, 1.278, 1.413, 1.592, 1.842, 2.133, + 1.981, 1.755, 1.529, 1.368, 1.251, 1.191, 1.125, 1.124, 1.124, 1.181, 1.242, 1.337, 1.479, 1.669, 1.935, 2.207, + 2.078, 1.867, 1.625, 1.453, 1.344, 1.251, 1.202, 1.201, 1.201, 1.242, 1.333, 1.418, 1.571, 1.776, 2.063, 2.321, + 2.217, 2.011, 1.747, 1.562, 1.431, 1.331, 1.278, 1.278, 1.278, 1.313, 1.407, 1.523, 1.686, 1.911, 2.226, 2.484 + ], + "sigma": 0.00135, + "sigma_Cb": 0.00279 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2698, + "ccm": + [ + 1.57227, -0.32596, -0.24631, + -0.61264, 1.70791, -0.09526, + -0.43254, 0.48489, 0.94765 + ] + }, + { + "ct": 2930, + "ccm": + [ + 1.69455, -0.52724, -0.16731, + -0.67131, 1.78468, -0.11338, + -0.41609, 0.54693, 0.86916 + ] + }, + { + "ct": 3643, + "ccm": + [ + 1.74041, -0.77553, 0.03512, + -0.44073, 1.34131, 0.09943, + -0.11035, -0.93919, 2.04954 + ] + }, + { + "ct": 4605, + "ccm": + [ + 1.49865, -0.41638, -0.08227, + -0.39445, 1.70114, -0.30669, + 0.01319, -0.88009, 1.86689 + ] + }, + { + "ct": 5658, + "ccm": + [ + 1.38601, -0.23128, -0.15472, + -0.37641, 1.70444, -0.32803, + -0.01575, -0.71466, 1.73041 + ] + } + ] + } + }, + { + "rpi.sharpen": { } + } + ] +} diff --git a/src/ipa/rpi/vc4/data/imx462.json b/src/ipa/rpi/vc4/data/imx462.json new file mode 100644 index 00000000..40a56842 --- /dev/null +++ b/src/ipa/rpi/vc4/data/imx462.json @@ -0,0 +1,215 @@ +{ + "version": 2.0, + "target": "bcm2835", + "description": "This is an interim tuning only. Please consider doing a more formal tuning for your application.", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 3840 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 6813, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 890, + "reference_Y": 12900 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.67 + } + }, + { + "rpi.geq": + { + "offset": 187, + "slope": 0.00842 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.awb": + { + "bayes": 0 + } + }, + { + "rpi.agc": + { + "speed": 0.2, + "metering_modes": + { + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + }, + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 10, 30000, 60000 ], + "gain": [ 1.0, 2.0, 8.0 ] + }, + "short": + { + "shutter": [ 10, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.16 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.7, + "luminance_lut": + [ + 2.844, 2.349, 2.018, 1.775, 1.599, 1.466, 1.371, 1.321, 1.306, 1.316, 1.357, 1.439, 1.552, 1.705, 1.915, 2.221, + 2.576, 2.151, 1.851, 1.639, 1.478, 1.358, 1.272, 1.231, 1.218, 1.226, 1.262, 1.335, 1.438, 1.571, 1.766, 2.067, + 2.381, 2.005, 1.739, 1.545, 1.389, 1.278, 1.204, 1.166, 1.153, 1.161, 1.194, 1.263, 1.356, 1.489, 1.671, 1.943, + 2.242, 1.899, 1.658, 1.481, 1.329, 1.225, 1.156, 1.113, 1.096, 1.107, 1.143, 1.201, 1.289, 1.423, 1.607, 1.861, + 2.152, 1.831, 1.602, 1.436, 1.291, 1.193, 1.121, 1.069, 1.047, 1.062, 1.107, 1.166, 1.249, 1.384, 1.562, 1.801, + 2.104, 1.795, 1.572, 1.407, 1.269, 1.174, 1.099, 1.041, 1.008, 1.029, 1.083, 1.146, 1.232, 1.364, 1.547, 1.766, + 2.104, 1.796, 1.572, 1.403, 1.264, 1.171, 1.097, 1.036, 1.001, 1.025, 1.077, 1.142, 1.231, 1.363, 1.549, 1.766, + 2.148, 1.827, 1.594, 1.413, 1.276, 1.184, 1.114, 1.062, 1.033, 1.049, 1.092, 1.153, 1.242, 1.383, 1.577, 1.795, + 2.211, 1.881, 1.636, 1.455, 1.309, 1.214, 1.149, 1.104, 1.081, 1.089, 1.125, 1.184, 1.273, 1.423, 1.622, 1.846, + 2.319, 1.958, 1.698, 1.516, 1.362, 1.262, 1.203, 1.156, 1.137, 1.142, 1.171, 1.229, 1.331, 1.484, 1.682, 1.933, + 2.459, 2.072, 1.789, 1.594, 1.441, 1.331, 1.261, 1.219, 1.199, 1.205, 1.232, 1.301, 1.414, 1.571, 1.773, 2.052, + 2.645, 2.206, 1.928, 1.728, 1.559, 1.451, 1.352, 1.301, 1.282, 1.289, 1.319, 1.395, 1.519, 1.685, 1.904, 2.227 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.sharpen": { } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 3900, + "ccm": + [ + 1.54659, -0.17707, -0.36953, + -0.51471, 1.72733, -0.21262, + 0.06667, -0.92279, 1.85612 + ] + } + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx477.json b/src/ipa/rpi/vc4/data/imx477.json index 0e39d419..fa25ee86 100644 --- a/src/ipa/rpi/vc4/data/imx477.json +++ b/src/ipa/rpi/vc4/data/imx477.json @@ -115,16 +115,16 @@ "ct_curve": [ 2360.0, 0.6009, 0.3093, - 2848.0, 0.5071, 0.4000, + 2848.0, 0.5071, 0.4, 2903.0, 0.4905, 0.4392, 3628.0, 0.4261, 0.5564, 3643.0, 0.4228, 0.5623, - 4660.0, 0.3529, 0.6800, - 5579.0, 0.3227, 0.7000, - 6125.0, 0.3129, 0.7100, - 6671.0, 0.3065, 0.7200, - 7217.0, 0.3014, 0.7300, - 7763.0, 0.2950, 0.7400, + 4660.0, 0.3529, 0.68, + 5579.0, 0.3227, 0.7, + 6125.0, 0.3129, 0.71, + 6671.0, 0.3065, 0.72, + 7217.0, 0.3014, 0.73, + 7763.0, 0.295, 0.74, 9505.0, 0.2524, 0.7856 ], "sensitivity_r": 1.05, @@ -136,93 +136,306 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 10000, 30000, 60000, 66666 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] - }, - "short": - { - "shutter": [ 100, 5000, 10000, 20000, 33333 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] }, - "long": { - "shutter": [ 100, 10000, 30000, 60000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 0.125, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.3, - 1000, 0.3 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.3, - 1000, 0.3 - ] + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ], - "shadows": [ + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "base_ev": 1.5, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.0, - "q_hi": 0.5, - "y_target": - [ - 0, 0.17, - 1000, 0.17 + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } ] } }, @@ -468,6 +681,20 @@ }, { "rpi.sharpen": { } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] }
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx477_noir.json b/src/ipa/rpi/vc4/data/imx477_noir.json index 52d7f072..472f33fe 100644 --- a/src/ipa/rpi/vc4/data/imx477_noir.json +++ b/src/ipa/rpi/vc4/data/imx477_noir.json @@ -47,93 +47,306 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": - { - "shutter": [ 100, 10000, 30000, 60000, 66666 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] - }, - "short": + "channels": [ { - "shutter": [ 100, 5000, 10000, 20000, 33333 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] }, - "long": { - "shutter": [ 100, 10000, 30000, 60000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 0.125, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.3, - 1000, 0.3 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.3, - 1000, 0.3 - ] + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ], - "shadows": [ + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.0, - "q_hi": 0.5, - "y_target": - [ - 0, 0.17, - 1000, 0.17 + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.3, + 1000, 0.3 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } ] } }, @@ -424,6 +637,20 @@ }, { "rpi.sharpen": { } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -}
\ No newline at end of file +} diff --git a/src/ipa/rpi/vc4/data/imx477_scientific.json b/src/ipa/rpi/vc4/data/imx477_scientific.json index 26c692fd..9dc32eb1 100644 --- a/src/ipa/rpi/vc4/data/imx477_scientific.json +++ b/src/ipa/rpi/vc4/data/imx477_scientific.json @@ -148,15 +148,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/imx477_v1.json b/src/ipa/rpi/vc4/data/imx477_v1.json index d6402009..55e4adc1 100644 --- a/src/ipa/rpi/vc4/data/imx477_v1.json +++ b/src/ipa/rpi/vc4/data/imx477_v1.json @@ -138,15 +138,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/imx519.json b/src/ipa/rpi/vc4/data/imx519.json index 1b0a7747..ce194256 100644 --- a/src/ipa/rpi/vc4/data/imx519.json +++ b/src/ipa/rpi/vc4/data/imx519.json @@ -133,15 +133,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/imx708.json b/src/ipa/rpi/vc4/data/imx708.json index c40a5994..4de6f079 100644 --- a/src/ipa/rpi/vc4/data/imx708.json +++ b/src/ipa/rpi/vc4/data/imx708.json @@ -139,85 +139,280 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 15000, 30000, 60000, 120000 ], - "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "short": { - "shutter": [ 100, 5000, 10000, 20000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + "base_ev": 0.125, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "long": { - "shutter": [ 1000, 30000, 60000, 90000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 - ], - "startup_frames": 5, - "convergence_frames": 6, - "speed": 0.15 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 + } + ] } }, { @@ -457,6 +652,20 @@ "skip_frames": 5, "map": [ 0.0, 445, 15.0, 925 ] } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx708_noir.json b/src/ipa/rpi/vc4/data/imx708_noir.json index 8d498153..7b7ee874 100644 --- a/src/ipa/rpi/vc4/data/imx708_noir.json +++ b/src/ipa/rpi/vc4/data/imx708_noir.json @@ -139,85 +139,280 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 15000, 30000, 60000, 120000 ], - "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "short": { - "shutter": [ 100, 5000, 10000, 20000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + "base_ev": 0.125, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "long": { - "shutter": [ 1000, 30000, 60000, 90000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 - ], - "startup_frames": 5, - "convergence_frames": 6, - "speed": 0.15 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 + } + ] } }, { @@ -556,6 +751,20 @@ "skip_frames": 5, "map": [ 0.0, 445, 15.0, 925 ] } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx708_wide.json b/src/ipa/rpi/vc4/data/imx708_wide.json index 65543628..6f45aafc 100644 --- a/src/ipa/rpi/vc4/data/imx708_wide.json +++ b/src/ipa/rpi/vc4/data/imx708_wide.json @@ -129,85 +129,280 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 15000, 30000, 60000, 120000 ], - "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "short": { - "shutter": [ 100, 5000, 10000, 20000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + "base_ev": 0.125, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "long": { - "shutter": [ 1000, 30000, 60000, 90000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 - ], - "startup_frames": 5, - "convergence_frames": 6, - "speed": 0.15 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 + } + ] } }, { @@ -468,6 +663,20 @@ "skip_frames": 5, "map": [ 0.0, 420, 35.0, 920 ] } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/imx708_wide_noir.json b/src/ipa/rpi/vc4/data/imx708_wide_noir.json index 49442c0f..b9a5227e 100644 --- a/src/ipa/rpi/vc4/data/imx708_wide_noir.json +++ b/src/ipa/rpi/vc4/data/imx708_wide_noir.json @@ -129,85 +129,280 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": - { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": + "channels": [ { - "shutter": [ 100, 15000, 30000, 60000, 120000 ], - "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "short": { - "shutter": [ 100, 5000, 10000, 20000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + "base_ev": 0.125, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 }, - "long": { - "shutter": [ 1000, 30000, 60000, 90000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 1.5, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.2, - 1000, 0.2 - ] + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 1.0, 2.0, 4.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.2, + 1000, 0.2 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 - ], - "startup_frames": 5, - "convergence_frames": 6, - "speed": 0.15 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "startup_frames": 5, + "convergence_frames": 6, + "speed": 0.15 + } + ] } }, { @@ -459,6 +654,20 @@ "skip_frames": 5, "map": [ 0.0, 420, 35.0, 920 ] } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -} +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/meson.build b/src/ipa/rpi/vc4/data/meson.build index bcf5658b..7a8001ee 100644 --- a/src/ipa/rpi/vc4/data/meson.build +++ b/src/ipa/rpi/vc4/data/meson.build @@ -3,10 +3,14 @@ conf_files = files([ 'imx219.json', 'imx219_noir.json', + 'imx283.json', 'imx290.json', 'imx296.json', 'imx296_mono.json', + 'imx327.json', 'imx378.json', + 'imx415.json', + 'imx462.json', 'imx477.json', 'imx477_noir.json', 'imx477_scientific.json', @@ -17,10 +21,13 @@ conf_files = files([ 'imx708_wide_noir.json', 'ov5647.json', 'ov5647_noir.json', + 'ov64a40.json', + 'ov7251_mono.json', 'ov9281_mono.json', 'se327m12.json', 'uncalibrated.json', ]) install_data(conf_files, - install_dir : ipa_data_dir / 'rpi' / 'vc4') + install_dir : ipa_data_dir / 'rpi' / 'vc4', + install_tag : 'runtime') diff --git a/src/ipa/rpi/vc4/data/ov5647.json b/src/ipa/rpi/vc4/data/ov5647.json index a1b42a18..40c6059c 100644 --- a/src/ipa/rpi/vc4/data/ov5647.json +++ b/src/ipa/rpi/vc4/data/ov5647.json @@ -131,95 +131,308 @@ { "rpi.agc": { - "metering_modes": - { - "centre-weighted": - { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] - }, - "spot": + "channels": [ { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] - }, - "matrix": - { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] - } - }, - "exposure_modes": - { - "normal": - { - "shutter": [ 100, 10000, 30000, 60000, 66666 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] - }, - "short": - { - "shutter": [ 100, 5000, 10000, 20000, 33333 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ], + "base_ev": 1.25 }, - "long": { - "shutter": [ 100, 10000, 30000, 60000, 120000 ], - "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] - } - }, - "constraint_modes": - { - "normal": [ + "base_ev": 1.25, + "metering_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] - } - ], - "highlight": [ + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": { - "bound": "LOWER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.5, - 1000, 0.5 - ] + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } }, + "constraint_modes": { - "bound": "UPPER", - "q_lo": 0.98, - "q_hi": 1.0, - "y_target": - [ - 0, 0.8, - 1000, 0.8 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ], - "shadows": [ + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "base_ev": 1.25, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": { - "bound": "LOWER", - "q_lo": 0.0, - "q_hi": 0.5, - "y_target": - [ - 0, 0.17, - 1000, 0.17 + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } ] - } - ] - }, - "y_target": - [ - 0, 0.16, - 1000, 0.165, - 10000, 0.17 - ], - "base_ev": 1.25 + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + ] } }, { @@ -464,6 +677,20 @@ }, { "rpi.sharpen": { } + }, + { + "rpi.hdr": + { + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": + { + "short": 1, + "long": 2 + } + } + } } ] -}
\ No newline at end of file +} diff --git a/src/ipa/rpi/vc4/data/ov5647_noir.json b/src/ipa/rpi/vc4/data/ov5647_noir.json index a6c6722f..488b7119 100644 --- a/src/ipa/rpi/vc4/data/ov5647_noir.json +++ b/src/ipa/rpi/vc4/data/ov5647_noir.json @@ -51,15 +51,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/ov64a40.json b/src/ipa/rpi/vc4/data/ov64a40.json new file mode 100644 index 00000000..096f0b1e --- /dev/null +++ b/src/ipa/rpi/vc4/data/ov64a40.json @@ -0,0 +1,422 @@ +{ + "version": 2.0, + "target": "bcm2835", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.dpc": { } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 17861, + "reference_gain": 2.0, + "reference_aperture": 1.0, + "reference_lux": 1073, + "reference_Y": 9022 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.984 + } + }, + { + "rpi.geq": + { + "offset": 215, + "slope": 0.01121 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.awb": + { + "priors": [ + { + "lux": 0, + "prior": + [ + 2000, 1.0, + 3000, 0.0, + 13000, 0.0 + ] + }, + { + "lux": 800, + "prior": + [ + 2000, 0.0, + 6000, 2.0, + 13000, 2.0 + ] + }, + { + "lux": 1500, + "prior": + [ + 2000, 0.0, + 4000, 1.0, + 6000, 6.0, + 6500, 7.0, + 7000, 1.0, + 13000, 1.0 + ] + } + ], + "modes": + { + "auto": + { + "lo": 2500, + "hi": 8000 + }, + "incandescent": + { + "lo": 2500, + "hi": 3000 + }, + "tungsten": + { + "lo": 3000, + "hi": 3500 + }, + "fluorescent": + { + "lo": 4000, + "hi": 4700 + }, + "indoor": + { + "lo": 3000, + "hi": 5000 + }, + "daylight": + { + "lo": 5500, + "hi": 6500 + }, + }, + "bayes": 1, + "ct_curve": + [ + 2300.0, 1.0522, 0.4091, + 2700.0, 0.7884, 0.4327, + 3000.0, 0.7597, 0.4421, + 4000.0, 0.5972, 0.5404, + 4150.0, 0.5598, 0.5779, + 6500.0, 0.4388, 0.7582 + ], + "sensitivity_r": 1.0, + "sensitivity_b": 1.0, + "transverse_pos": 0.0558, + "transverse_neg": 0.04278 + } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "omega": 1.3, + "n_iter": 100, + "luminance_strength": 0.8, + "calibrations_Cr": [ + { + "ct": 6500, + "table": + [ + 2.437, 2.415, 2.392, 2.378, 2.369, 2.353, 2.344, 2.336, 2.329, 2.325, 2.325, 2.325, 2.333, 2.344, 2.366, 2.381, + 2.434, 2.405, 2.386, 2.369, 2.361, 2.334, 2.314, 2.302, 2.295, 2.289, 2.289, 2.303, 2.327, 2.334, 2.356, 2.378, + 2.434, 2.405, 2.385, 2.363, 2.334, 2.314, 2.289, 2.277, 2.269, 2.262, 2.262, 2.283, 2.303, 2.328, 2.352, 2.375, + 2.434, 2.405, 2.385, 2.348, 2.315, 2.289, 2.277, 2.258, 2.251, 2.242, 2.249, 2.258, 2.283, 2.321, 2.352, 2.375, + 2.434, 2.413, 2.385, 2.343, 2.311, 2.282, 2.258, 2.251, 2.229, 2.233, 2.242, 2.251, 2.281, 2.321, 2.356, 2.375, + 2.437, 2.418, 2.388, 2.343, 2.311, 2.282, 2.251, 2.229, 2.221, 2.226, 2.233, 2.251, 2.281, 2.322, 2.361, 2.381, + 2.444, 2.422, 2.393, 2.351, 2.314, 2.284, 2.251, 2.227, 2.221, 2.227, 2.234, 2.256, 2.287, 2.326, 2.366, 2.389, + 2.445, 2.424, 2.395, 2.353, 2.316, 2.287, 2.266, 2.251, 2.228, 2.234, 2.251, 2.259, 2.289, 2.331, 2.371, 2.395, + 2.445, 2.424, 2.399, 2.364, 2.329, 2.308, 2.287, 2.266, 2.259, 2.254, 2.259, 2.283, 2.304, 2.343, 2.375, 2.395, + 2.445, 2.425, 2.407, 2.385, 2.364, 2.329, 2.308, 2.299, 2.291, 2.284, 2.284, 2.304, 2.335, 2.354, 2.381, 2.399, + 2.449, 2.427, 2.418, 2.407, 2.385, 2.364, 2.349, 2.338, 2.333, 2.326, 2.326, 2.335, 2.354, 2.374, 2.389, 2.408, + 2.458, 2.441, 2.427, 2.411, 2.403, 2.395, 2.391, 2.383, 2.375, 2.369, 2.369, 2.369, 2.369, 2.385, 2.408, 2.418 + ] + } + ], + "calibrations_Cb": [ + { + "ct": 6500, + "table": + [ + 1.297, 1.297, 1.289, 1.289, 1.289, 1.291, 1.293, 1.294, 1.294, 1.294, 1.294, 1.296, 1.298, 1.304, 1.312, 1.313, + 1.297, 1.289, 1.286, 1.286, 1.287, 1.289, 1.292, 1.294, 1.294, 1.294, 1.294, 1.294, 1.296, 1.298, 1.306, 1.312, + 1.289, 1.286, 1.283, 1.283, 1.285, 1.287, 1.291, 1.294, 1.294, 1.292, 1.291, 1.289, 1.293, 1.294, 1.298, 1.304, + 1.283, 1.282, 1.279, 1.281, 1.282, 1.285, 1.287, 1.294, 1.294, 1.291, 1.289, 1.289, 1.289, 1.293, 1.294, 1.298, + 1.281, 1.279, 1.279, 1.279, 1.281, 1.283, 1.287, 1.292, 1.292, 1.291, 1.291, 1.289, 1.289, 1.291, 1.294, 1.297, + 1.279, 1.277, 1.277, 1.279, 1.281, 1.282, 1.286, 1.289, 1.291, 1.291, 1.291, 1.291, 1.289, 1.291, 1.293, 1.297, + 1.277, 1.275, 1.275, 1.278, 1.279, 1.281, 1.284, 1.287, 1.289, 1.291, 1.291, 1.291, 1.289, 1.289, 1.292, 1.297, + 1.277, 1.275, 1.274, 1.275, 1.277, 1.278, 1.279, 1.284, 1.285, 1.285, 1.286, 1.288, 1.289, 1.289, 1.292, 1.297, + 1.277, 1.272, 1.272, 1.274, 1.274, 1.277, 1.279, 1.282, 1.284, 1.284, 1.285, 1.286, 1.288, 1.289, 1.292, 1.297, + 1.277, 1.272, 1.272, 1.273, 1.274, 1.276, 1.279, 1.282, 1.284, 1.284, 1.286, 1.286, 1.288, 1.289, 1.293, 1.297, + 1.279, 1.272, 1.271, 1.272, 1.274, 1.276, 1.279, 1.283, 1.284, 1.284, 1.285, 1.286, 1.288, 1.291, 1.294, 1.299, + 1.281, 1.274, 1.271, 1.271, 1.273, 1.276, 1.278, 1.282, 1.284, 1.284, 1.285, 1.286, 1.286, 1.291, 1.295, 1.302 + ] + } + ], + "luminance_lut": + [ + 3.811, 3.611, 3.038, 2.632, 2.291, 2.044, 1.967, 1.957, 1.957, 1.957, 2.009, 2.222, 2.541, 2.926, 3.455, 3.652, + 3.611, 3.135, 2.636, 2.343, 2.044, 1.846, 1.703, 1.626, 1.626, 1.671, 1.796, 1.983, 2.266, 2.549, 3.007, 3.455, + 3.135, 2.781, 2.343, 2.044, 1.831, 1.554, 1.411, 1.337, 1.337, 1.379, 1.502, 1.749, 1.983, 2.266, 2.671, 3.007, + 2.903, 2.538, 2.149, 1.831, 1.554, 1.401, 1.208, 1.145, 1.145, 1.183, 1.339, 1.502, 1.749, 2.072, 2.446, 2.801, + 2.812, 2.389, 2.018, 1.684, 1.401, 1.208, 1.139, 1.028, 1.028, 1.109, 1.183, 1.339, 1.604, 1.939, 2.309, 2.723, + 2.799, 2.317, 1.948, 1.606, 1.327, 1.139, 1.028, 1.019, 1.001, 1.021, 1.109, 1.272, 1.531, 1.869, 2.246, 2.717, + 2.799, 2.317, 1.948, 1.606, 1.327, 1.139, 1.027, 1.006, 1.001, 1.007, 1.109, 1.272, 1.531, 1.869, 2.246, 2.717, + 2.799, 2.372, 1.997, 1.661, 1.378, 1.184, 1.118, 1.019, 1.012, 1.103, 1.158, 1.326, 1.589, 1.926, 2.302, 2.717, + 2.884, 2.507, 2.116, 1.795, 1.511, 1.361, 1.184, 1.118, 1.118, 1.158, 1.326, 1.461, 1.726, 2.056, 2.434, 2.799, + 3.083, 2.738, 2.303, 1.989, 1.783, 1.511, 1.361, 1.291, 1.291, 1.337, 1.461, 1.726, 1.942, 2.251, 2.657, 2.999, + 3.578, 3.083, 2.589, 2.303, 1.989, 1.783, 1.637, 1.563, 1.563, 1.613, 1.743, 1.942, 2.251, 2.537, 2.999, 3.492, + 3.764, 3.578, 2.999, 2.583, 2.237, 1.986, 1.913, 1.905, 1.905, 1.905, 1.962, 2.196, 2.525, 2.932, 3.492, 3.659 + ], + "sigma": 0.005, + "sigma_Cb": 0.005 + } + }, + { + "rpi.contrast": + { + "ce_enable": 1, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + }, + { + "rpi.ccm": + { + "ccms": [ + { + "ct": 2300, + "ccm": + [ + 1.77644, -0.14825, -0.62819, + -0.25816, 1.66348, -0.40532, + -0.21633, -1.95132, 3.16765 + ] + }, + { + "ct": 2700, + "ccm": + [ + 1.53605, 0.03047, -0.56652, + -0.27159, 1.78525, -0.51366, + -0.13581, -1.22128, 2.35709 + ] + }, + { + "ct": 3000, + "ccm": + [ + 1.72928, -0.18819, -0.54108, + -0.44398, 2.04756, -0.60358, + -0.13203, -0.94711, 2.07913 + ] + }, + { + "ct": 4000, + "ccm": + [ + 1.69895, -0.23055, -0.46841, + -0.33934, 1.80391, -0.46456, + -0.13902, -0.75385, 1.89287 + ] + }, + { + "ct": 4150, + "ccm": + [ + 2.08494, -0.68698, -0.39796, + -0.37928, 1.78795, -0.40867, + -0.11537, -0.74686, 1.86223 + ] + }, + { + "ct": 6500, + "ccm": + [ + 1.69813, -0.27304, -0.42509, + -0.23364, 1.87586, -0.64221, + -0.07587, -0.62348, 1.69935 + ] + } + ] + } + }, + { + "rpi.sharpen": { } + }, + { + "rpi.af": + { + "ranges": + { + "normal": + { + "min": 0.0, + "max": 12.0, + "default": 1.0 + }, + "macro": + { + "min": 3.0, + "max": 15.0, + "default": 4.0 + } + }, + "speeds": + { + "normal": + { + "step_coarse": 1.0, + "step_fine": 0.25, + "contrast_ratio": 0.75, + "pdaf_gain": -0.02, + "pdaf_squelch": 0.125, + "max_slew": 2.0, + "pdaf_frames": 0, + "dropout_frames": 0, + "step_frames": 4 + } + }, + "conf_epsilon": 8, + "conf_thresh": 16, + "conf_clip": 512, + "skip_frames": 5, + "map": [ 0.0, 0, 15.0, 1023 ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/ov7251_mono.json b/src/ipa/rpi/vc4/data/ov7251_mono.json new file mode 100644 index 00000000..a9d05a01 --- /dev/null +++ b/src/ipa/rpi/vc4/data/ov7251_mono.json @@ -0,0 +1,136 @@ +{ + "version": 2.0, + "target": "bcm2835", + "algorithms": [ + { + "rpi.black_level": + { + "black_level": 4096 + } + }, + { + "rpi.lux": + { + "reference_shutter_speed": 2000, + "reference_gain": 1.0, + "reference_aperture": 1.0, + "reference_lux": 800, + "reference_Y": 20000 + } + }, + { + "rpi.noise": + { + "reference_constant": 0, + "reference_slope": 2.5 + } + }, + { + "rpi.sdn": { } + }, + { + "rpi.agc": + { + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 15000, 30000, 60000, 120000 ], + "gain": [ 1.0, 2.0, 3.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 30000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 8.0 ] + }, + "long": + { + "shutter": [ 1000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 6.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.4, + 1000, 0.4 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + } + }, + { + "rpi.alsc": + { + "n_iter": 0, + "luminance_strength": 1.0, + "corner_strength": 1.5 + } + }, + { + "rpi.contrast": + { + "ce_enable": 0, + "gamma_curve": + [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + } + } + ] +}
\ No newline at end of file diff --git a/src/ipa/rpi/vc4/data/ov9281_mono.json b/src/ipa/rpi/vc4/data/ov9281_mono.json index 2b7292ec..a9d05a01 100644 --- a/src/ipa/rpi/vc4/data/ov9281_mono.json +++ b/src/ipa/rpi/vc4/data/ov9281_mono.json @@ -35,7 +35,10 @@ { "centre-weighted": { - "weights": [ 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/se327m12.json b/src/ipa/rpi/vc4/data/se327m12.json index 8552ed92..948169db 100644 --- a/src/ipa/rpi/vc4/data/se327m12.json +++ b/src/ipa/rpi/vc4/data/se327m12.json @@ -133,15 +133,24 @@ { "centre-weighted": { - "weights": [ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] }, "spot": { - "weights": [ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + "weights": + [ + 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] }, "matrix": { - "weights": [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/data/uncalibrated.json b/src/ipa/rpi/vc4/data/uncalibrated.json index 7654defa..cdc56b32 100644 --- a/src/ipa/rpi/vc4/data/uncalibrated.json +++ b/src/ipa/rpi/vc4/data/uncalibrated.json @@ -22,7 +22,10 @@ { "centre-weighted": { - "weights": [ 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 ] + "weights": + [ + 4, 4, 4, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0 + ] } }, "exposure_modes": diff --git a/src/ipa/rpi/vc4/meson.build b/src/ipa/rpi/vc4/meson.build index 590e9197..c10fa17e 100644 --- a/src/ipa/rpi/vc4/meson.build +++ b/src/ipa/rpi/vc4/meson.build @@ -15,7 +15,6 @@ vc4_ipa_libs = [ vc4_ipa_includes = [ ipa_includes, - libipa_includes, ] vc4_ipa_sources = files([ @@ -24,12 +23,10 @@ vc4_ipa_sources = files([ vc4_ipa_includes += include_directories('..') -mod = shared_module(ipa_name, - [vc4_ipa_sources, libcamera_generated_ipa_headers], +mod = shared_module(ipa_name, vc4_ipa_sources, name_prefix : '', include_directories : vc4_ipa_includes, - dependencies : vc4_ipa_deps, - link_with : libipa, + dependencies : [vc4_ipa_deps, libipa_dep], link_whole : vc4_ipa_libs, install : true, install_dir : ipa_install_dir) diff --git a/src/ipa/rpi/vc4/vc4.cpp b/src/ipa/rpi/vc4/vc4.cpp index 4a4d720c..ba43e474 100644 --- a/src/ipa/rpi/vc4/vc4.cpp +++ b/src/ipa/rpi/vc4/vc4.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2021, Raspberry Pi Ltd * - * rpi.cpp - Raspberry Pi VC4/BCM2835 ISP IPA. + * Raspberry Pi VC4/BCM2835 ISP IPA. */ #include <string.h> @@ -11,6 +11,8 @@ #include <linux/bcm2835-isp.h> #include <libcamera/base/log.h> +#include <libcamera/base/span.h> +#include <libcamera/control_ids.h> #include <libcamera/ipa/ipa_module_info.h> #include "common/ipa_base.h" @@ -244,12 +246,48 @@ RPiController::StatisticsPtr IpaVc4::platformProcessStats(Span<uint8_t> mem) stats->focus_stats[i].contrast_val_num[1][1], stats->focus_stats[i].contrast_val_num[1][0] }); + if (statsMetadataOutput_) { + Span<const uint8_t> statsSpan(reinterpret_cast<const uint8_t *>(stats), + sizeof(bcm2835_isp_stats)); + libcameraMetadata_.set(controls::rpi::Bcm2835StatsOutput, statsSpan); + } + return statistics; } -void IpaVc4::handleControls([[maybe_unused]] const ControlList &controls) +void IpaVc4::handleControls(const ControlList &controls) { - /* No controls require any special updates to the hardware configuration. */ + static const std::map<int32_t, RPiController::DenoiseMode> DenoiseModeTable = { + { controls::draft::NoiseReductionModeOff, RPiController::DenoiseMode::Off }, + { controls::draft::NoiseReductionModeFast, RPiController::DenoiseMode::ColourFast }, + { controls::draft::NoiseReductionModeHighQuality, RPiController::DenoiseMode::ColourHighQuality }, + { controls::draft::NoiseReductionModeMinimal, RPiController::DenoiseMode::ColourOff }, + { controls::draft::NoiseReductionModeZSL, RPiController::DenoiseMode::ColourHighQuality }, + }; + + for (auto const &ctrl : controls) { + switch (ctrl.first) { + case controls::draft::NOISE_REDUCTION_MODE: { + RPiController::DenoiseAlgorithm *sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>( + controller_.getAlgorithm("SDN")); + /* Some platforms may have a combined "denoise" algorithm instead. */ + if (!sdn) + sdn = dynamic_cast<RPiController::DenoiseAlgorithm *>( + controller_.getAlgorithm("denoise")); + if (!sdn) { + LOG(IPARPI, Warning) + << "Could not set NOISE_REDUCTION_MODE - no SDN algorithm"; + return; + } + + int32_t idx = ctrl.second.get<int32_t>(); + auto mode = DenoiseModeTable.find(idx); + if (mode != DenoiseModeTable.end()) + sdn->setMode(mode->second); + break; + } + } + } } bool IpaVc4::validateIspControls() @@ -545,7 +583,7 @@ extern "C" { const struct IPAModuleInfo ipaModuleInfo = { IPA_MODULE_API_VERSION, 1, - "PipelineHandlerVc4", + "rpi/vc4", "rpi/vc4", }; diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp new file mode 100644 index 00000000..c46bb0eb --- /dev/null +++ b/src/ipa/simple/algorithms/agc.cpp @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Exposure and gain + */ + +#include "agc.h" + +#include <stdint.h> + +#include <libcamera/base/log.h> + +#include "control_ids.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPASoftExposure) + +namespace ipa::soft::algorithms { + +/* + * The number of bins to use for the optimal exposure calculations. + */ +static constexpr unsigned int kExposureBinsCount = 5; + +/* + * The exposure is optimal when the mean sample value of the histogram is + * in the middle of the range. + */ +static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; + +/* + * This implements the hysteresis for the exposure adjustment. + * It is small enough to have the exposure close to the optimal, and is big + * enough to prevent the exposure from wobbling around the optimal value. + */ +static constexpr float kExposureSatisfactory = 0.2; + +Agc::Agc() +{ +} + +void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) +{ + /* + * kExpDenominator of 10 gives ~10% increment/decrement; + * kExpDenominator of 5 - about ~20% + */ + static constexpr uint8_t kExpDenominator = 10; + static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1; + static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1; + + double next; + int32_t &exposure = frameContext.sensor.exposure; + double &again = frameContext.sensor.gain; + + if (exposureMSV < kExposureOptimal - kExposureSatisfactory) { + next = exposure * kExpNumeratorUp / kExpDenominator; + if (next - exposure < 1) + exposure += 1; + else + exposure = next; + if (exposure >= context.configuration.agc.exposureMax) { + next = again * kExpNumeratorUp / kExpDenominator; + if (next - again < context.configuration.agc.againMinStep) + again += context.configuration.agc.againMinStep; + else + again = next; + } + } + + if (exposureMSV > kExposureOptimal + kExposureSatisfactory) { + if (exposure == context.configuration.agc.exposureMax && + again > context.configuration.agc.againMin) { + next = again * kExpNumeratorDown / kExpDenominator; + if (again - next < context.configuration.agc.againMinStep) + again -= context.configuration.agc.againMinStep; + else + again = next; + } else { + next = exposure * kExpNumeratorDown / kExpDenominator; + if (exposure - next < 1) + exposure -= 1; + else + exposure = next; + } + } + + exposure = std::clamp(exposure, context.configuration.agc.exposureMin, + context.configuration.agc.exposureMax); + again = std::clamp(again, context.configuration.agc.againMin, + context.configuration.agc.againMax); + + LOG(IPASoftExposure, Debug) + << "exposureMSV " << exposureMSV + << " exp " << exposure << " again " << again; +} + +void Agc::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) +{ + utils::Duration exposureTime = + context.configuration.agc.lineDuration * frameContext.sensor.exposure; + metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); + metadata.set(controls::AnalogueGain, frameContext.sensor.gain); + + /* + * Calculate Mean Sample Value (MSV) according to formula from: + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf + */ + const auto &histogram = stats->yHistogram; + const unsigned int blackLevelHistIdx = + context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize); + const unsigned int histogramSize = + SwIspStats::kYHistogramSize - blackLevelHistIdx; + const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; + const unsigned int yHistValsPerBinMod = + histogramSize / (histogramSize % kExposureBinsCount + 1); + int exposureBins[kExposureBinsCount] = {}; + unsigned int denom = 0; + unsigned int num = 0; + + for (unsigned int i = 0; i < histogramSize; i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[blackLevelHistIdx + i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom); + updateExposure(context, frameContext, exposureMSV); +} + +REGISTER_IPA_ALGORITHM(Agc, "Agc") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h new file mode 100644 index 00000000..112d9f5a --- /dev/null +++ b/src/ipa/simple/algorithms/agc.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Exposure and gain + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class Agc : public Algorithm +{ +public: + Agc(); + ~Agc() = default; + + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; + +private: + void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV); +}; + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/algorithm.h b/src/ipa/simple/algorithms/algorithm.h new file mode 100644 index 00000000..41f63170 --- /dev/null +++ b/src/ipa/simple/algorithms/algorithm.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + * + * Software ISP control algorithm interface + */ + +#pragma once + +#include <libipa/algorithm.h> + +#include "module.h" + +namespace libcamera { + +namespace ipa::soft { + +using Algorithm = libcamera::ipa::Algorithm<Module>; + +} /* namespace ipa::soft */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp new file mode 100644 index 00000000..cf567e89 --- /dev/null +++ b/src/ipa/simple/algorithms/awb.cpp @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Auto white balance + */ + +#include "awb.h" + +#include <numeric> +#include <stdint.h> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +#include "libipa/colours.h" +#include "simple/ipa_context.h" + +#include "control_ids.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPASoftAwb) + +namespace ipa::soft::algorithms { + +int Awb::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + auto &gains = context.activeState.awb.gains; + gains = { { 1.0, 1.0, 1.0 } }; + + return 0; +} + +void Awb::prepare(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] DebayerParams *params) +{ + auto &gains = context.activeState.awb.gains; + /* Just report, the gains are applied in LUT algorithm. */ + frameContext.gains.red = gains.r(); + frameContext.gains.blue = gains.b(); +} + +void Awb::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) +{ + const SwIspStats::Histogram &histogram = stats->yHistogram; + const uint8_t blackLevel = context.activeState.blc.level; + + const float maxGain = 1024.0; + const float mdGains[] = { + static_cast<float>(frameContext.gains.red / maxGain), + static_cast<float>(frameContext.gains.blue / maxGain) + }; + metadata.set(controls::ColourGains, mdGains); + + /* + * Black level must be subtracted to get the correct AWB ratios, they + * would be off if they were computed from the whole brightness range + * rather than from the sensor range. + */ + const uint64_t nPixels = std::accumulate( + histogram.begin(), histogram.end(), 0); + const uint64_t offset = blackLevel * nPixels; + const uint64_t sumR = stats->sumR_ - offset / 4; + const uint64_t sumG = stats->sumG_ - offset / 2; + const uint64_t sumB = stats->sumB_ - offset / 4; + + /* + * Calculate red and blue gains for AWB. + * Clamp max gain at 4.0, this also avoids 0 division. + */ + auto &gains = context.activeState.awb.gains; + gains = { { + sumR <= sumG / 4 ? 4.0f : static_cast<float>(sumG) / sumR, + 1.0, + sumB <= sumG / 4 ? 4.0f : static_cast<float>(sumG) / sumB, + } }; + + RGB<double> rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } }; + context.activeState.awb.temperatureK = estimateCCT(rgbGains); + metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK); + + LOG(IPASoftAwb, Debug) + << "gain R/B: " << gains << "; temperature: " + << context.activeState.awb.temperatureK; +} + +REGISTER_IPA_ALGORITHM(Awb, "Awb") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h new file mode 100644 index 00000000..ad993f39 --- /dev/null +++ b/src/ipa/simple/algorithms/awb.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024-2025 Red Hat Inc. + * + * Auto white balance + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class Awb : public Algorithm +{ +public: + Awb() = default; + ~Awb() = default; + + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; +}; + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp new file mode 100644 index 00000000..8c1e9ed0 --- /dev/null +++ b/src/ipa/simple/algorithms/blc.cpp @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024-2025, Red Hat Inc. + * + * Black level handling + */ + +#include "blc.h" + +#include <numeric> + +#include <libcamera/base/log.h> + +#include "control_ids.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +LOG_DEFINE_CATEGORY(IPASoftBL) + +BlackLevel::BlackLevel() +{ +} + +int BlackLevel::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + auto blackLevel = tuningData["blackLevel"].get<int16_t>(); + if (blackLevel.has_value()) { + /* + * Convert 16 bit values from the tuning file to 8 bit black + * level for the SoftISP. + */ + definedLevel_ = blackLevel.value() >> 8; + } + return 0; +} + +int BlackLevel::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + if (definedLevel_.has_value()) + context.configuration.black.level = definedLevel_; + context.activeState.blc.level = + context.configuration.black.level.value_or(255); + return 0; +} + +void BlackLevel::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) +{ + /* Assign each of the R G G B channels as the same black level. */ + const int32_t blackLevel = context.activeState.blc.level * 256; + const int32_t blackLevels[] = { + blackLevel, blackLevel, blackLevel, blackLevel + }; + metadata.set(controls::SensorBlackLevels, blackLevels); + + if (context.configuration.black.level.has_value()) + return; + + if (frameContext.sensor.exposure == context.activeState.blc.lastExposure && + frameContext.sensor.gain == context.activeState.blc.lastGain) { + return; + } + + const SwIspStats::Histogram &histogram = stats->yHistogram; + + /* + * The constant is selected to be "good enough", not overly + * conservative or aggressive. There is no magic about the given value. + */ + constexpr float ignoredPercentage = 0.02; + const unsigned int total = + std::accumulate(begin(histogram), end(histogram), 0); + const unsigned int pixelThreshold = ignoredPercentage * total; + const unsigned int histogramRatio = 256 / SwIspStats::kYHistogramSize; + const unsigned int currentBlackIdx = + context.activeState.blc.level / histogramRatio; + + for (unsigned int i = 0, seen = 0; + i < currentBlackIdx && i < SwIspStats::kYHistogramSize; + i++) { + seen += histogram[i]; + if (seen >= pixelThreshold) { + context.activeState.blc.level = i * histogramRatio; + context.activeState.blc.lastExposure = frameContext.sensor.exposure; + context.activeState.blc.lastGain = frameContext.sensor.gain; + LOG(IPASoftBL, Debug) + << "Auto-set black level: " + << i << "/" << SwIspStats::kYHistogramSize + << " (" << 100 * (seen - histogram[i]) / total << "% below, " + << 100 * seen / total << "% at or below)"; + break; + } + }; +} + +REGISTER_IPA_ALGORITHM(BlackLevel, "BlackLevel") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/blc.h b/src/ipa/simple/algorithms/blc.h new file mode 100644 index 00000000..db9e6d63 --- /dev/null +++ b/src/ipa/simple/algorithms/blc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Black level handling + */ + +#pragma once + +#include <optional> +#include <stdint.h> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class BlackLevel : public Algorithm +{ +public: + BlackLevel(); + ~BlackLevel() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; + +private: + std::optional<uint8_t> definedLevel_; +}; + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp new file mode 100644 index 00000000..d5ba928d --- /dev/null +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas On Board + * Copyright (C) 2024-2025, Red Hat Inc. + * + * Color correction matrix + */ + +#include "ccm.h" + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/control_ids.h> + +namespace { + +constexpr unsigned int kTemperatureThreshold = 100; + +} + +namespace libcamera { + +namespace ipa::soft::algorithms { + +LOG_DEFINE_CATEGORY(IPASoftCcm) + +int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(IPASoftCcm, Error) + << "Failed to parse 'ccm' parameter from tuning file."; + return ret; + } + + context.ccmEnabled = true; + + return 0; +} + +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + const unsigned int ct = context.activeState.awb.temperatureK; + + /* Change CCM only on bigger temperature changes. */ + if (frame > 0 && + utils::abs_diff(ct, lastCt_) < kTemperatureThreshold) { + frameContext.ccm.ccm = context.activeState.ccm.ccm; + context.activeState.ccm.changed = false; + return; + } + + lastCt_ = ct; + Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct); + + context.activeState.ccm.ccm = ccm; + frameContext.ccm.ccm = ccm; + context.activeState.ccm.changed = true; +} + +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data()); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h new file mode 100644 index 00000000..f4e2b85b --- /dev/null +++ b/src/ipa/simple/algorithms/ccm.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024-2025, Red Hat Inc. + * + * Color correction matrix + */ + +#pragma once + +#include "libcamera/internal/matrix.h" + +#include <libipa/interpolator.h> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() = default; + ~Ccm() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; + +private: + unsigned int lastCt_; + Interpolator<Matrix<float, 3, 3>> ccm_; +}; + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lut.cpp b/src/ipa/simple/algorithms/lut.cpp new file mode 100644 index 00000000..d1d5f727 --- /dev/null +++ b/src/ipa/simple/algorithms/lut.cpp @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024-2025, Red Hat Inc. + * + * Color lookup tables construction + */ + +#include "lut.h" + +#include <algorithm> +#include <cmath> +#include <optional> +#include <stdint.h> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +#include "simple/ipa_context.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(IPASoftLut) + +namespace ipa::soft::algorithms { + +int Lut::init(IPAContext &context, + [[maybe_unused]] const YamlObject &tuningData) +{ + context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f); + return 0; +} + +int Lut::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + /* Gamma value is fixed */ + context.configuration.gamma = 0.5; + context.activeState.knobs.contrast = std::optional<double>(); + updateGammaTable(context); + + return 0; +} + +void Lut::queueRequest(typename Module::Context &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] typename Module::FrameContext &frameContext, + const ControlList &controls) +{ + const auto &contrast = controls.get(controls::Contrast); + if (contrast.has_value()) { + context.activeState.knobs.contrast = contrast; + LOG(IPASoftLut, Debug) << "Setting contrast to " << contrast.value(); + } +} + +void Lut::updateGammaTable(IPAContext &context) +{ + auto &gammaTable = context.activeState.gamma.gammaTable; + const auto blackLevel = context.activeState.blc.level; + const unsigned int blackIndex = blackLevel * gammaTable.size() / 256; + const auto contrast = context.activeState.knobs.contrast.value_or(1.0); + + std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex, 0); + const float divisor = gammaTable.size() - blackIndex - 1.0; + for (unsigned int i = blackIndex; i < gammaTable.size(); i++) { + double normalized = (i - blackIndex) / divisor; + /* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */ + double contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001)); + /* Apply simple S-curve */ + if (normalized < 0.5) + normalized = 0.5 * std::pow(normalized / 0.5, contrastExp); + else + normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp); + gammaTable[i] = UINT8_MAX * + std::pow(normalized, context.configuration.gamma); + } + + context.activeState.gamma.blackLevel = blackLevel; + context.activeState.gamma.contrast = contrast; +} + +int16_t Lut::ccmValue(unsigned int i, float ccm) const +{ + return std::round(i * ccm); +} + +void Lut::prepare(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) +{ + frameContext.contrast = context.activeState.knobs.contrast; + + /* + * Update the gamma table if needed. This means if black level changes + * and since the black level gets updated only if a lower value is + * observed, it's not permanently prone to minor fluctuations or + * rounding errors. + */ + const bool gammaUpdateNeeded = + context.activeState.gamma.blackLevel != context.activeState.blc.level || + context.activeState.gamma.contrast != context.activeState.knobs.contrast; + if (gammaUpdateNeeded) + updateGammaTable(context); + + auto &gains = context.activeState.awb.gains; + auto &gammaTable = context.activeState.gamma.gammaTable; + const unsigned int gammaTableSize = gammaTable.size(); + const double div = static_cast<double>(DebayerParams::kRGBLookupSize) / + gammaTableSize; + + if (!context.ccmEnabled) { + for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { + /* Apply gamma after gain! */ + const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1); + params->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())]; + params->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())]; + params->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())]; + } + } else if (context.activeState.ccm.changed || gammaUpdateNeeded) { + Matrix<float, 3, 3> gainCcm = { { gains.r(), 0, 0, + 0, gains.g(), 0, + 0, 0, gains.b() } }; + auto ccm = context.activeState.ccm.ccm * gainCcm; + auto &red = params->redCcm; + auto &green = params->greenCcm; + auto &blue = params->blueCcm; + for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { + red[i].r = ccmValue(i, ccm[0][0]); + red[i].g = ccmValue(i, ccm[1][0]); + red[i].b = ccmValue(i, ccm[2][0]); + green[i].r = ccmValue(i, ccm[0][1]); + green[i].g = ccmValue(i, ccm[1][1]); + green[i].b = ccmValue(i, ccm[2][1]); + blue[i].r = ccmValue(i, ccm[0][2]); + blue[i].g = ccmValue(i, ccm[1][2]); + blue[i].b = ccmValue(i, ccm[2][2]); + params->gammaLut[i] = gammaTable[i / div]; + } + } +} + +void Lut::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) +{ + const auto &contrast = frameContext.contrast; + if (contrast) + metadata.set(controls::Contrast, contrast.value()); +} + +REGISTER_IPA_ALGORITHM(Lut, "Lut") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lut.h b/src/ipa/simple/algorithms/lut.h new file mode 100644 index 00000000..ba8b9021 --- /dev/null +++ b/src/ipa/simple/algorithms/lut.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Color lookup tables construction + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::soft::algorithms { + +class Lut : public Algorithm +{ +public: + Lut() = default; + ~Lut() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void queueRequest(typename Module::Context &context, + const uint32_t frame, + typename Module::FrameContext &frameContext, + const ControlList &controls) + override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const SwIspStats *stats, + ControlList &metadata) override; + +private: + void updateGammaTable(IPAContext &context); + int16_t ccmValue(unsigned int i, float ccm) const; +}; + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build new file mode 100644 index 00000000..2d0adb05 --- /dev/null +++ b/src/ipa/simple/algorithms/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: CC0-1.0 + +soft_simple_ipa_algorithms = files([ + 'awb.cpp', + 'agc.cpp', + 'blc.cpp', + 'ccm.cpp', + 'lut.cpp', +]) diff --git a/src/ipa/simple/data/meson.build b/src/ipa/simple/data/meson.build new file mode 100644 index 00000000..92795ee4 --- /dev/null +++ b/src/ipa/simple/data/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: CC0-1.0 + +conf_files = files([ + 'uncalibrated.yaml', +]) + +# The install_dir must match the name from the IPAModuleInfo +install_data(conf_files, + install_dir : ipa_data_dir / 'simple', + install_tag : 'runtime') diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml new file mode 100644 index 00000000..5508e668 --- /dev/null +++ b/src/ipa/simple/data/uncalibrated.yaml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +version: 1 +algorithms: + - BlackLevel: + - Awb: + # Color correction matrices can be defined here. The CCM algorithm + # has a significant performance impact, and should only be enabled + # if tuned. + # - Ccm: + # ccms: + # - ct: 6500 + # ccm: [ 1, 0, 0, + # 0, 1, 0, + # 0, 0, 1] + - Lut: + - Agc: +... diff --git a/src/ipa/simple/ipa_context.cpp b/src/ipa/simple/ipa_context.cpp new file mode 100644 index 00000000..3f94bbeb --- /dev/null +++ b/src/ipa/simple/ipa_context.cpp @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021, Google Inc. + * Copyright (C) 2024 Red Hat Inc. + * + * Software ISP IPA Context + */ + +#include "ipa_context.h" + +/** + * \file ipa_context.h + * \brief Context and state information shared between the algorithms + */ + +namespace libcamera::ipa::soft { + +/** + * \struct IPASessionConfiguration + * \brief Session configuration for the IPA module + * + * The session configuration contains all IPA configuration parameters that + * remain constant during the capture session, from IPA module start to stop. + * It is typically set during the configure() operation of the IPA module, but + * may also be updated in the start() operation. + */ + +/** + * \struct IPAActiveState + * \brief The active state of the IPA algorithms + * + * The IPA is fed with the statistics generated from the latest frame processed. + * The statistics are then processed by the IPA algorithms to compute parameters + * required for the next frame capture and processing. The current state of the + * algorithms is reflected through the IPAActiveState to store the values most + * recently computed by the IPA algorithms. + */ + +/** + * \struct IPAContext + * \brief Global IPA context data shared between all algorithms + * + * \var IPAContext::configuration + * \brief The IPA session configuration, immutable during the session + * + * \var IPAContext::frameContexts + * \brief Ring buffer of the IPAFrameContext(s) + * + * \var IPAContext::activeState + * \brief The current state of IPA algorithms + */ + +/** + * \var IPASessionConfiguration::gamma + * \brief Gamma value to be used in the raw image processing + */ + +/** + * \var IPAActiveState::black + * \brief Context for the Black Level algorithm + * + * \var IPAActiveState::black.level + * \brief Current determined black level + */ + +/** + * \var IPAActiveState::gains + * \brief Context for gains in the Colors algorithm + * + * \var IPAActiveState::gains.red + * \brief Gain of red color + * + * \var IPAActiveState::gains.green + * \brief Gain of green color + * + * \var IPAActiveState::gains.blue + * \brief Gain of blue color + */ + +/** + * \var IPAActiveState::agc + * \brief Context for the AGC algorithm + * + * \var IPAActiveState::agc.exposure + * \brief Current exposure value + * + * \var IPAActiveState::agc.again + * \brief Current analog gain value + */ + +/** + * \var IPAActiveState::gamma + * \brief Context for gamma in the Colors algorithm + * + * \var IPAActiveState::gamma.gammaTable + * \brief Computed gamma table + * + * \var IPAActiveState::gamma.blackLevel + * \brief Black level used for the gamma table computation + */ + +} /* namespace libcamera::ipa::soft */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h new file mode 100644 index 00000000..88cc6c35 --- /dev/null +++ b/src/ipa/simple/ipa_context.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024-2025 Red Hat, Inc. + * + * Simple pipeline IPA Context + */ + +#pragma once + +#include <array> +#include <optional> +#include <stdint.h> + +#include <libcamera/controls.h> + +#include "libcamera/internal/matrix.h" +#include "libcamera/internal/vector.h" + +#include <libipa/fc_queue.h> + +#include "core_ipa_interface.h" + +namespace libcamera { + +namespace ipa::soft { + +struct IPASessionConfiguration { + float gamma; + struct { + int32_t exposureMin, exposureMax; + double againMin, againMax, againMinStep; + utils::Duration lineDuration; + } agc; + struct { + std::optional<uint8_t> level; + } black; +}; + +struct IPAActiveState { + struct { + uint8_t level; + int32_t lastExposure; + double lastGain; + } blc; + + struct { + RGB<float> gains; + unsigned int temperatureK; + } awb; + + static constexpr unsigned int kGammaLookupSize = 1024; + struct { + std::array<double, kGammaLookupSize> gammaTable; + uint8_t blackLevel; + double contrast; + } gamma; + + struct { + Matrix<float, 3, 3> ccm; + bool changed; + } ccm; + + struct { + /* 0..2 range, 1.0 = normal */ + std::optional<double> contrast; + } knobs; +}; + +struct IPAFrameContext : public FrameContext { + struct { + Matrix<float, 3, 3> ccm; + } ccm; + + struct { + int32_t exposure; + double gain; + } sensor; + struct { + double red; + double blue; + } gains; + std::optional<double> contrast; +}; + +struct IPAContext { + IPAContext(unsigned int frameContextSize) + : frameContexts(frameContextSize) + { + } + + IPACameraSensorInfo sensorInfo; + IPASessionConfiguration configuration; + IPAActiveState activeState; + FCQueue<IPAFrameContext> frameContexts; + ControlInfoMap::Map ctrlMap; + bool ccmEnabled = false; +}; + +} /* namespace ipa::soft */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build new file mode 100644 index 00000000..2f9f15f4 --- /dev/null +++ b/src/ipa/simple/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: CC0-1.0 + +subdir('algorithms') +subdir('data') + +ipa_name = 'ipa_soft_simple' + +soft_simple_sources = files([ + 'ipa_context.cpp', + 'soft_simple.cpp', +]) + +soft_simple_sources += soft_simple_ipa_algorithms + +mod = shared_module(ipa_name, soft_simple_sources, + name_prefix : '', + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], + install : true, + install_dir : ipa_install_dir) + +if ipa_sign_module + custom_target(ipa_name + '.so.sign', + input : mod, + output : ipa_name + '.so.sign', + command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], + install : false, + build_by_default : true) +endif + +ipa_names += ipa_name diff --git a/src/ipa/simple/module.h b/src/ipa/simple/module.h new file mode 100644 index 00000000..8d4d53fb --- /dev/null +++ b/src/ipa/simple/module.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + * + * Software ISP IPA Module + */ + +#pragma once + +#include <libcamera/controls.h> + +#include <libcamera/ipa/soft_ipa_interface.h> + +#include "libcamera/internal/software_isp/debayer_params.h" +#include "libcamera/internal/software_isp/swisp_stats.h" + +#include <libipa/module.h> + +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::soft { + +using Module = ipa::Module<IPAContext, IPAFrameContext, IPAConfigInfo, + DebayerParams, SwIspStats>; + +} /* namespace ipa::soft */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp new file mode 100644 index 00000000..c94c4cd5 --- /dev/null +++ b/src/ipa/simple/soft_simple.cpp @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * Simple Software Image Processing Algorithm module + */ + +#include <chrono> +#include <stdint.h> +#include <sys/mman.h> + +#include <linux/v4l2-controls.h> + +#include <libcamera/base/file.h> +#include <libcamera/base/log.h> +#include <libcamera/base/shared_fd.h> + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> + +#include <libcamera/ipa/ipa_interface.h> +#include <libcamera/ipa/ipa_module_info.h> +#include <libcamera/ipa/soft_ipa_interface.h> + +#include "libcamera/internal/software_isp/debayer_params.h" +#include "libcamera/internal/software_isp/swisp_stats.h" +#include "libcamera/internal/yaml_parser.h" + +#include "libipa/camera_sensor_helper.h" + +#include "module.h" + +namespace libcamera { +LOG_DEFINE_CATEGORY(IPASoft) + +using namespace std::literals::chrono_literals; + +namespace ipa::soft { + +/* Maximum number of frame contexts to be held */ +static constexpr uint32_t kMaxFrameContexts = 16; + +class IPASoftSimple : public ipa::soft::IPASoftInterface, public Module +{ +public: + IPASoftSimple() + : context_(kMaxFrameContexts) + { + } + + ~IPASoftSimple(); + + int init(const IPASettings &settings, + const SharedFD &fdStats, + const SharedFD &fdParams, + const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls, + bool *ccmEnabled) override; + int configure(const IPAConfigInfo &configInfo) override; + + int start() override; + void stop() override; + + void queueRequest(const uint32_t frame, const ControlList &controls) override; + void computeParams(const uint32_t frame) override; + void processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls) override; + +protected: + std::string logPrefix() const override; + +private: + void updateExposure(double exposureMSV); + + DebayerParams *params_; + SwIspStats *stats_; + std::unique_ptr<CameraSensorHelper> camHelper_; + ControlInfoMap sensorInfoMap_; + + /* Local parameter storage */ + struct IPAContext context_; +}; + +IPASoftSimple::~IPASoftSimple() +{ + if (stats_) + munmap(stats_, sizeof(SwIspStats)); + if (params_) + munmap(params_, sizeof(DebayerParams)); +} + +int IPASoftSimple::init(const IPASettings &settings, + const SharedFD &fdStats, + const SharedFD &fdParams, + const IPACameraSensorInfo &sensorInfo, + const ControlInfoMap &sensorControls, + ControlInfoMap *ipaControls, + bool *ccmEnabled) +{ + camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!camHelper_) { + LOG(IPASoft, Warning) + << "Failed to create camera sensor helper for " + << settings.sensorModel; + } + + context_.sensorInfo = sensorInfo; + + /* Load the tuning data file */ + File file(settings.configurationFile); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + int ret = file.error(); + LOG(IPASoft, Error) + << "Failed to open configuration file " + << settings.configurationFile << ": " << strerror(-ret); + return ret; + } + + std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file); + if (!data) + return -EINVAL; + + /* \todo Use the IPA configuration file for real. */ + unsigned int version = (*data)["version"].get<uint32_t>(0); + LOG(IPASoft, Debug) << "Tuning file version " << version; + + if (!data->contains("algorithms")) { + LOG(IPASoft, Error) << "Tuning file doesn't contain algorithms"; + return -EINVAL; + } + + int ret = createAlgorithms(context_, (*data)["algorithms"]); + if (ret) + return ret; + + *ccmEnabled = context_.ccmEnabled; + + params_ = nullptr; + stats_ = nullptr; + + if (!fdStats.isValid()) { + LOG(IPASoft, Error) << "Invalid Statistics handle"; + return -ENODEV; + } + + if (!fdParams.isValid()) { + LOG(IPASoft, Error) << "Invalid Parameters handle"; + return -ENODEV; + } + + { + void *mem = mmap(nullptr, sizeof(DebayerParams), PROT_WRITE, + MAP_SHARED, fdParams.get(), 0); + if (mem == MAP_FAILED) { + LOG(IPASoft, Error) << "Unable to map Parameters"; + return -errno; + } + + params_ = static_cast<DebayerParams *>(mem); + } + + { + void *mem = mmap(nullptr, sizeof(SwIspStats), PROT_READ, + MAP_SHARED, fdStats.get(), 0); + if (mem == MAP_FAILED) { + LOG(IPASoft, Error) << "Unable to map Statistics"; + return -errno; + } + + stats_ = static_cast<SwIspStats *>(mem); + } + + ControlInfoMap::Map ctrlMap = context_.ctrlMap; + *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); + + /* + * Check if the sensor driver supports the controls required by the + * Soft IPA. + * Don't save the min and max control values yet, as e.g. the limits + * for V4L2_CID_EXPOSURE depend on the configured sensor resolution. + */ + if (sensorControls.find(V4L2_CID_EXPOSURE) == sensorControls.end()) { + LOG(IPASoft, Error) << "Don't have exposure control"; + return -EINVAL; + } + + if (sensorControls.find(V4L2_CID_ANALOGUE_GAIN) == sensorControls.end()) { + LOG(IPASoft, Error) << "Don't have gain control"; + return -EINVAL; + } + + return 0; +} + +int IPASoftSimple::configure(const IPAConfigInfo &configInfo) +{ + sensorInfoMap_ = configInfo.sensorControls; + + const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second; + + /* Clear the IPA context before the streaming session. */ + context_.configuration = {}; + context_.activeState = {}; + context_.frameContexts.clear(); + + context_.configuration.agc.lineDuration = + context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate; + context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>(); + context_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>(); + if (!context_.configuration.agc.exposureMin) { + LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; + context_.configuration.agc.exposureMin = 1; + } + + int32_t againMin = gainInfo.min().get<int32_t>(); + int32_t againMax = gainInfo.max().get<int32_t>(); + + if (camHelper_) { + context_.configuration.agc.againMin = camHelper_->gain(againMin); + context_.configuration.agc.againMax = camHelper_->gain(againMax); + context_.configuration.agc.againMinStep = + (context_.configuration.agc.againMax - + context_.configuration.agc.againMin) / + 100.0; + if (camHelper_->blackLevel().has_value()) { + /* + * The black level from camHelper_ is a 16 bit value, software ISP + * works with 8 bit pixel values, both regardless of the actual + * sensor pixel width. Hence we obtain the pixel-based black value + * by dividing the value from the helper by 256. + */ + context_.configuration.black.level = + camHelper_->blackLevel().value() / 256; + } + } else { + /* + * The camera sensor gain (g) is usually not equal to the value written + * into the gain register (x). But the way how the AGC algorithm changes + * the gain value to make the total exposure closer to the optimum + * assumes that g(x) is not too far from linear function. If the minimal + * gain is 0, the g(x) is likely to be far from the linear, like + * g(x) = a / (b * x + c). To avoid unexpected changes to the gain by + * the AGC algorithm (abrupt near one edge, and very small near the + * other) we limit the range of the gain values used. + */ + context_.configuration.agc.againMax = againMax; + if (!againMin) { + LOG(IPASoft, Warning) + << "Minimum gain is zero, that can't be linear"; + context_.configuration.agc.againMin = + std::min(100, againMin / 2 + againMax / 2); + } + context_.configuration.agc.againMinStep = 1.0; + } + + for (auto const &algo : algorithms()) { + int ret = algo->configure(context_, configInfo); + if (ret) + return ret; + } + + LOG(IPASoft, Info) + << "Exposure " << context_.configuration.agc.exposureMin << "-" + << context_.configuration.agc.exposureMax + << ", gain " << context_.configuration.agc.againMin << "-" + << context_.configuration.agc.againMax + << " (" << context_.configuration.agc.againMinStep << ")"; + + return 0; +} + +int IPASoftSimple::start() +{ + return 0; +} + +void IPASoftSimple::stop() +{ + context_.frameContexts.clear(); +} + +void IPASoftSimple::queueRequest(const uint32_t frame, const ControlList &controls) +{ + IPAFrameContext &frameContext = context_.frameContexts.alloc(frame); + + for (auto const &algo : algorithms()) + algo->queueRequest(context_, frame, frameContext, controls); +} + +void IPASoftSimple::computeParams(const uint32_t frame) +{ + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + for (auto const &algo : algorithms()) + algo->prepare(context_, frame, frameContext, params_); + setIspParams.emit(); +} + +void IPASoftSimple::processStats(const uint32_t frame, + [[maybe_unused]] const uint32_t bufferId, + const ControlList &sensorControls) +{ + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + frameContext.sensor.exposure = + sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>(); + int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>(); + frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again; + + ControlList metadata(controls::controls); + for (auto const &algo : algorithms()) + algo->process(context_, frame, frameContext, stats_, metadata); + metadataReady.emit(frame, metadata); + + /* Sanity check */ + if (!sensorControls.contains(V4L2_CID_EXPOSURE) || + !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { + LOG(IPASoft, Error) << "Control(s) missing"; + return; + } + + ControlList ctrls(sensorInfoMap_); + + auto &againNew = frameContext.sensor.gain; + ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, + static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew)); + + setSensorControls.emit(ctrls); +} + +std::string IPASoftSimple::logPrefix() const +{ + return "IPASoft"; +} + +} /* namespace ipa::soft */ + +/* + * External IPA module interface + */ +extern "C" { +const struct IPAModuleInfo ipaModuleInfo = { + IPA_MODULE_API_VERSION, + 0, + "simple", + "simple", +}; + +IPAInterface *ipaCreate() +{ + return new ipa::soft::IPASoftSimple(); +} + +} /* extern "C" */ + +} /* namespace libcamera */ diff --git a/src/ipa/vimc/data/meson.build b/src/ipa/vimc/data/meson.build index 42ec651c..628d6a29 100644 --- a/src/ipa/vimc/data/meson.build +++ b/src/ipa/vimc/data/meson.build @@ -5,4 +5,5 @@ conf_files = files([ ]) install_data(conf_files, - install_dir : ipa_data_dir / 'vimc') + install_dir : ipa_data_dir / 'vimc', + install_tag : 'runtime') diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build index 264a2d9a..2cc5f80b 100644 --- a/src/ipa/vimc/meson.build +++ b/src/ipa/vimc/meson.build @@ -2,12 +2,10 @@ ipa_name = 'ipa_vimc' -mod = shared_module(ipa_name, - ['vimc.cpp', libcamera_generated_ipa_headers], +mod = shared_module(ipa_name, 'vimc.cpp', name_prefix : '', - include_directories : [ipa_includes, libipa_includes], - dependencies : libcamera_private, - link_with : libipa, + include_directories : [ipa_includes], + dependencies : [libcamera_private, libipa_dep], install : true, install_dir : ipa_install_dir) diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp index 2c255778..a1351a0f 100644 --- a/src/ipa/vimc/vimc.cpp +++ b/src/ipa/vimc/vimc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * vimc.cpp - Vimc Image Processing Algorithm module + * Vimc Image Processing Algorithm module */ #include <libcamera/ipa/vimc_ipa_interface.h> @@ -14,6 +14,7 @@ #include <iostream> #include <libcamera/base/file.h> +#include <libcamera/base/flags.h> #include <libcamera/base/log.h> #include <libcamera/ipa/ipa_interface.h> @@ -47,7 +48,7 @@ public: void unmapBuffers(const std::vector<unsigned int> &ids) override; void queueRequest(uint32_t frame, const ControlList &controls) override; - void fillParamsBuffer(uint32_t frame, uint32_t bufferId) override; + void computeParams(uint32_t frame, uint32_t bufferId) override; private: void initTrace(); @@ -149,7 +150,7 @@ void IPAVimc::queueRequest([[maybe_unused]] uint32_t frame, { } -void IPAVimc::fillParamsBuffer([[maybe_unused]] uint32_t frame, uint32_t bufferId) +void IPAVimc::computeParams([[maybe_unused]] uint32_t frame, uint32_t bufferId) { auto it = buffers_.find(bufferId); if (it == buffers_.end()) { @@ -158,7 +159,7 @@ void IPAVimc::fillParamsBuffer([[maybe_unused]] uint32_t frame, uint32_t bufferI } Flags<ipa::vimc::TestFlag> flags; - paramsBufferReady.emit(bufferId, flags); + paramsComputed.emit(bufferId, flags); } void IPAVimc::initTrace() @@ -200,7 +201,7 @@ extern "C" { const struct IPAModuleInfo ipaModuleInfo = { IPA_MODULE_API_VERSION, 0, - "PipelineHandlerVimc", + "vimc", "vimc", }; diff --git a/src/libcamera/base/backtrace.cpp b/src/libcamera/base/backtrace.cpp index be30589d..0b04629c 100644 --- a/src/libcamera/base/backtrace.cpp +++ b/src/libcamera/base/backtrace.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas on Board Oy * - * backtrace.h - Call stack backtraces + * Call stack backtraces */ #include <libcamera/base/backtrace.h> diff --git a/src/libcamera/base/bound_method.cpp b/src/libcamera/base/bound_method.cpp index 3ecec51c..322029a8 100644 --- a/src/libcamera/base/bound_method.cpp +++ b/src/libcamera/base/bound_method.cpp @@ -2,11 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * bound_method.cpp - Method bind and invocation + * Method bind and invocation */ #include <libcamera/base/bound_method.h> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/semaphore.h> #include <libcamera/base/thread.h> diff --git a/src/libcamera/base/class.cpp b/src/libcamera/base/class.cpp index 9c2d9f21..61998398 100644 --- a/src/libcamera/base/class.cpp +++ b/src/libcamera/base/class.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * class.cpp - Utilities and helpers for classes + * Utilities and helpers for classes */ #include <libcamera/base/class.h> diff --git a/src/libcamera/base/event_dispatcher.cpp b/src/libcamera/base/event_dispatcher.cpp index 4be89e81..5f4a5cb4 100644 --- a/src/libcamera/base/event_dispatcher.cpp +++ b/src/libcamera/base/event_dispatcher.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event_dispatcher.cpp - Event dispatcher + * Event dispatcher */ #include <libcamera/base/event_dispatcher.h> diff --git a/src/libcamera/base/event_dispatcher_poll.cpp b/src/libcamera/base/event_dispatcher_poll.cpp index 7238a316..52bfb34e 100644 --- a/src/libcamera/base/event_dispatcher_poll.cpp +++ b/src/libcamera/base/event_dispatcher_poll.cpp @@ -2,19 +2,18 @@ /* * Copyright (C) 2019, Google Inc. * - * event_dispatcher_poll.cpp - Poll-based event dispatcher + * Poll-based event dispatcher */ #include <libcamera/base/event_dispatcher_poll.h> -#include <algorithm> -#include <chrono> #include <iomanip> #include <poll.h> #include <stdint.h> #include <string.h> #include <sys/eventfd.h> #include <unistd.h> +#include <vector> #include <libcamera/base/event_notifier.h> #include <libcamera/base/log.h> diff --git a/src/libcamera/base/event_notifier.cpp b/src/libcamera/base/event_notifier.cpp index fd93c087..495c281d 100644 --- a/src/libcamera/base/event_notifier.cpp +++ b/src/libcamera/base/event_notifier.cpp @@ -2,12 +2,13 @@ /* * Copyright (C) 2019, Google Inc. * - * event_notifier.cpp - File descriptor event notifier + * File descriptor event notifier */ #include <libcamera/base/event_notifier.h> #include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/log.h> #include <libcamera/base/message.h> #include <libcamera/base/thread.h> @@ -20,6 +21,8 @@ namespace libcamera { +LOG_DECLARE_CATEGORY(Event) + /** * \class EventNotifier * \brief Notify of activity on a file descriptor @@ -104,6 +107,9 @@ EventNotifier::~EventNotifier() */ void EventNotifier::setEnabled(bool enable) { + if (!assertThreadBound("EventNotifier can't be enabled from another thread")) + return; + if (enabled_ == enable) return; diff --git a/src/libcamera/base/file.cpp b/src/libcamera/base/file.cpp index d1ab1aa5..2b83a517 100644 --- a/src/libcamera/base/file.cpp +++ b/src/libcamera/base/file.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * file.cpp - File I/O operations + * File I/O operations */ #include <libcamera/base/file.h> diff --git a/src/libcamera/base/flags.cpp b/src/libcamera/base/flags.cpp index 3e4320ac..9981f2ed 100644 --- a/src/libcamera/base/flags.cpp +++ b/src/libcamera/base/flags.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * flags.cpp - Type-safe enum-based bitfields + * Type-safe enum-based bitfields */ #include <libcamera/base/flags.h> diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp index c8045ef7..8bf3e1da 100644 --- a/src/libcamera/base/log.cpp +++ b/src/libcamera/base/log.cpp @@ -2,18 +2,21 @@ /* * Copyright (C) 2018, Google Inc. * - * log.cpp - Logging infrastructure + * Logging infrastructure */ #include <libcamera/base/log.h> #include <array> +#include <charconv> +#include <fnmatch.h> #include <fstream> #include <iostream> #include <list> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <string_view> #include <syslog.h> #include <time.h> #include <unordered_set> @@ -38,8 +41,8 @@ * The levels are configurable through the LIBCAMERA_LOG_LEVELS environment * variable that contains a comma-separated list of 'category:level' pairs. * - * The category names are strings and can include a wildcard ('*') character at - * the end to match multiple categories. + * The category names are strings and can include a wildcard ('*') character to + * match multiple categories. * * The level are either numeric values, or strings containing the log level * name. The available log levels are DEBUG, INFO, WARN, ERROR and FATAL. Log @@ -311,15 +314,15 @@ private: void parseLogFile(); void parseLogLevels(); - static LogSeverity parseLogLevel(const std::string &level); + static LogSeverity parseLogLevel(std::string_view level); friend LogCategory; - void registerCategory(LogCategory *category); - LogCategory *findCategory(const char *name) const; + LogCategory *findOrCreateCategory(std::string_view name); static bool destroyed_; - std::vector<LogCategory *> categories_; + Mutex mutex_; + std::vector<std::unique_ptr<LogCategory>> categories_ LIBCAMERA_TSA_GUARDED_BY(mutex_); std::list<std::pair<std::string, LogSeverity>> levels_; std::shared_ptr<LogOutput> output_; @@ -436,9 +439,6 @@ void logSetLevel(const char *category, const char *level) Logger::~Logger() { destroyed_ = true; - - for (LogCategory *category : categories_) - delete category; } /** @@ -569,7 +569,9 @@ void Logger::logSetLevel(const char *category, const char *level) if (severity == LogInvalid) return; - for (LogCategory *c : categories_) { + MutexLocker locker(mutex_); + + for (const auto &c : categories_) { if (c->name() == category) { c->setSeverity(severity); break; @@ -640,17 +642,17 @@ void Logger::parseLogLevels() if (!len) continue; - std::string category; - std::string level; + std::string_view category; + std::string_view level; const char *colon = static_cast<const char *>(memchr(pair, ':', len)); if (!colon) { /* 'x' is a shortcut for '*:x'. */ category = "*"; - level = std::string(pair, len); + level = std::string_view(pair, len); } else { - category = std::string(pair, colon - pair); - level = std::string(colon + 1, comma - colon - 1); + category = std::string_view(pair, colon - pair); + level = std::string_view(colon + 1, comma - colon - 1); } /* Both the category and the level must be specified. */ @@ -661,7 +663,7 @@ void Logger::parseLogLevels() if (severity == LogInvalid) continue; - levels_.push_back({ category, severity }); + levels_.emplace_back(category, severity); } } @@ -675,7 +677,7 @@ void Logger::parseLogLevels() * * \return The log severity, or LogInvalid if the string is invalid */ -LogSeverity Logger::parseLogLevel(const std::string &level) +LogSeverity Logger::parseLogLevel(std::string_view level) { static const char *const names[] = { "DEBUG", @@ -685,15 +687,13 @@ LogSeverity Logger::parseLogLevel(const std::string &level) "FATAL", }; - int severity; + unsigned int severity = LogInvalid; if (std::isdigit(level[0])) { - char *endptr; - severity = strtoul(level.c_str(), &endptr, 10); - if (*endptr != '\0' || severity > LogFatal) + auto [end, ec] = std::from_chars(level.data(), level.data() + level.size(), severity); + if (ec != std::errc() || *end != '\0' || severity > LogFatal) severity = LogInvalid; } else { - severity = LogInvalid; for (unsigned int i = 0; i < std::size(names); ++i) { if (names[i] == level) { severity = i; @@ -706,52 +706,28 @@ LogSeverity Logger::parseLogLevel(const std::string &level) } /** - * \brief Register a log category with the logger - * \param[in] category The log category - * - * Log categories must have unique names. It is invalid to call this function - * if a log category with the same name already exists. + * \brief Find an existing log category with the given name or create one + * \param[in] name Name of the log category + * \return The pointer to the log category found or created */ -void Logger::registerCategory(LogCategory *category) +LogCategory *Logger::findOrCreateCategory(std::string_view name) { - categories_.push_back(category); - - const std::string &name = category->name(); - for (const std::pair<std::string, LogSeverity> &level : levels_) { - bool match = true; - - for (unsigned int i = 0; i < level.first.size(); ++i) { - if (level.first[i] == '*') - break; - - if (i >= name.size() || - name[i] != level.first[i]) { - match = false; - break; - } - } + MutexLocker locker(mutex_); - if (match) { - category->setSeverity(level.second); - break; - } + for (const auto &category : categories_) { + if (category->name() == name) + return category.get(); } -} -/** - * \brief Find an existing log category with the given name - * \param[in] name Name of the log category - * \return The pointer to the found log category or nullptr if not found - */ -LogCategory *Logger::findCategory(const char *name) const -{ - if (auto it = std::find_if(categories_.begin(), categories_.end(), - [name](auto c) { return c->name() == name; }); - it != categories_.end()) { - return *it; + LogCategory *category = categories_.emplace_back(std::unique_ptr<LogCategory>(new LogCategory(name))).get(); + const char *categoryName = category->name().c_str(); + + for (const auto &[pattern, severity] : levels_) { + if (fnmatch(pattern.c_str(), categoryName, FNM_NOESCAPE) == 0) + category->setSeverity(severity); } - return nullptr; + return category; } /** @@ -787,25 +763,16 @@ LogCategory *Logger::findCategory(const char *name) const * * \return The pointer to the LogCategory */ -LogCategory *LogCategory::create(const char *name) +LogCategory *LogCategory::create(std::string_view name) { - static Mutex mutex_; - MutexLocker locker(mutex_); - LogCategory *category = Logger::instance()->findCategory(name); - - if (!category) { - category = new LogCategory(name); - Logger::instance()->registerCategory(category); - } - - return category; + return Logger::instance()->findOrCreateCategory(name); } /** * \brief Construct a log category * \param[in] name The category name */ -LogCategory::LogCategory(const char *name) +LogCategory::LogCategory(std::string_view name) : name_(name), severity_(LogSeverity::LogInfo) { } @@ -824,15 +791,12 @@ LogCategory::LogCategory(const char *name) */ /** + * \fn LogCategory::setSeverity(LogSeverity severity) * \brief Set the severity of the log category * * Messages of severity higher than or equal to the severity of the log category * are printed, other messages are discarded. */ -void LogCategory::setSeverity(LogSeverity severity) -{ - severity_ = severity; -} /** * \brief Retrieve the default log category @@ -874,39 +838,12 @@ const LogCategory &LogCategory::defaultCategory() */ LogMessage::LogMessage(const char *fileName, unsigned int line, const LogCategory &category, LogSeverity severity, - const std::string &prefix) - : category_(category), severity_(severity), prefix_(prefix) + std::string prefix) + : category_(category), severity_(severity), + timestamp_(utils::clock::now()), + fileInfo_(static_cast<std::ostringstream &&>(std::ostringstream() << utils::basename(fileName) << ":" << line).str()), + prefix_(std::move(prefix)) { - init(fileName, line); -} - -/** - * \brief Move-construct a log message - * \param[in] other The other message - * - * The move constructor is meant to support the _log() functions. Thanks to copy - * elision it will likely never be called, but C++11 only permits copy elision, - * it doesn't enforce it unlike C++17. To avoid potential link errors depending - * on the compiler type and version, and optimization level, the move - * constructor is defined even if it will likely never be called, and ensures - * that the destructor of the \a other message will not output anything to the - * log by setting the severity to LogInvalid. - */ -LogMessage::LogMessage(LogMessage &&other) - : msgStream_(std::move(other.msgStream_)), category_(other.category_), - severity_(other.severity_) -{ - other.severity_ = LogInvalid; -} - -void LogMessage::init(const char *fileName, unsigned int line) -{ - /* Log the timestamp, severity and file information. */ - timestamp_ = utils::clock::now(); - - std::ostringstream ossFileInfo; - ossFileInfo << utils::basename(fileName) << ":" << line; - fileInfo_ = ossFileInfo.str(); } LogMessage::~LogMessage() diff --git a/src/libcamera/base/memfd.cpp b/src/libcamera/base/memfd.cpp new file mode 100644 index 00000000..ed0b299b --- /dev/null +++ b/src/libcamera/base/memfd.cpp @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Anonymous file creation + */ + +#include <libcamera/base/memfd.h> + +#include <fcntl.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <libcamera/base/log.h> + +/** + * \file base/memfd.h + * \brief Anonymous file creation + */ + +#ifndef __DOXYGEN__ +namespace { + +/* uClibc doesn't provide the file sealing API. */ +#if not HAVE_FILE_SEALS +#define F_ADD_SEALS 1033 +#define F_SEAL_SHRINK 0x0002 +#define F_SEAL_GROW 0x0004 +#endif + +#if not HAVE_MEMFD_CREATE +int memfd_create(const char *name, unsigned int flags) +{ + return syscall(SYS_memfd_create, name, flags); +} +#endif + +} /* namespace */ +#endif /* __DOXYGEN__ */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(File) + +/** + * \class MemFd + * \brief Helper class to create anonymous files + * + * Anonymous files behave like regular files, and can be modified, truncated, + * memory-mapped and so on. Unlike regular files, they however live in RAM and + * don't have permanent backing storage. + */ + +/** + * \enum MemFd::Seal + * \brief Seals for the MemFd::create() function + * \var MemFd::Seal::None + * \brief No seals (used as default value) + * \var MemFd::Seal::Shrink + * \brief Prevent the memfd from shrinking + * \var MemFd::Seal::Grow + * \brief Prevent the memfd from growing + */ + +/** + * \typedef MemFd::Seals + * \brief A bitwise combination of MemFd::Seal values + */ + +/** + * \brief Create an anonymous file + * \param[in] name The file name (displayed in symbolic links in /proc/self/fd/) + * \param[in] size The file size + * \param[in] seals The file seals + * + * This function is a helper that wraps anonymous file (memfd) creation and + * sets the file size and optional seals. + * + * \return The descriptor of the anonymous file if creation succeeded, or an + * invalid UniqueFD otherwise + */ +UniqueFD MemFd::create(const char *name, std::size_t size, Seals seals) +{ + int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); + if (ret < 0) { + ret = errno; + LOG(File, Error) + << "Failed to allocate memfd storage for " << name + << ": " << strerror(ret); + return {}; + } + + UniqueFD memfd(ret); + + ret = ftruncate(memfd.get(), size); + if (ret < 0) { + ret = errno; + LOG(File, Error) + << "Failed to set memfd size for " << name + << ": " << strerror(ret); + return {}; + } + + if (seals) { + int fileSeals = (seals & Seal::Shrink ? F_SEAL_SHRINK : 0) + | (seals & Seal::Grow ? F_SEAL_GROW : 0); + + ret = fcntl(memfd.get(), F_ADD_SEALS, fileSeals); + if (ret < 0) { + ret = errno; + LOG(File, Error) + << "Failed to seal the memfd for " << name + << ": " << strerror(ret); + return {}; + } + } + + return memfd; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build index 7a7fd7e4..a742dfdf 100644 --- a/src/libcamera/base/meson.build +++ b/src/libcamera/base/meson.build @@ -1,24 +1,28 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_base_sources = files([ - 'backtrace.cpp', - 'class.cpp', +libcamera_base_public_sources = files([ 'bound_method.cpp', + 'class.cpp', + 'flags.cpp', + 'object.cpp', + 'shared_fd.cpp', + 'signal.cpp', + 'unique_fd.cpp', +]) + +libcamera_base_internal_sources = files([ + 'backtrace.cpp', 'event_dispatcher.cpp', 'event_dispatcher_poll.cpp', 'event_notifier.cpp', 'file.cpp', - 'flags.cpp', 'log.cpp', + 'memfd.cpp', 'message.cpp', 'mutex.cpp', - 'object.cpp', 'semaphore.cpp', - 'shared_fd.cpp', - 'signal.cpp', 'thread.cpp', 'timer.cpp', - 'unique_fd.cpp', 'utils.cpp', ]) @@ -49,7 +53,11 @@ libcamera_base_deps = [ libcamera_base_args = [ '-DLIBCAMERA_BASE_PRIVATE' ] libcamera_base_lib = shared_library('libcamera-base', - [libcamera_base_sources, libcamera_base_headers], + [ + libcamera_base_public_sources, + libcamera_base_internal_sources, + libcamera_base_headers, + ], version : libcamera_version, soversion : libcamera_soversion, name_prefix : '', diff --git a/src/libcamera/base/message.cpp b/src/libcamera/base/message.cpp index 2da2a7ed..098faac6 100644 --- a/src/libcamera/base/message.cpp +++ b/src/libcamera/base/message.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * message.cpp - Message queue support + * Message queue support */ #include <libcamera/base/message.h> diff --git a/src/libcamera/base/mutex.cpp b/src/libcamera/base/mutex.cpp index e34e8618..2a4542c4 100644 --- a/src/libcamera/base/mutex.cpp +++ b/src/libcamera/base/mutex.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * mutex.cpp - Mutex classes with clang thread safety annotation + * Mutex classes with clang thread safety annotation */ #include <libcamera/base/mutex.h> diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp index 92cecd22..37d133cc 100644 --- a/src/libcamera/base/object.cpp +++ b/src/libcamera/base/object.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object.cpp - Base object + * Base object */ #include <libcamera/base/object.h> @@ -40,8 +40,9 @@ LOG_DEFINE_CATEGORY(Object) * Object class. * * Deleting an object from a thread other than the one the object is bound to is - * unsafe, unless the caller ensures that the object isn't processing any - * message concurrently. + * unsafe, unless the caller ensures that the object's thread is stopped and no + * parent or child of the object gets deleted concurrently. See + * Object::~Object() for more information. * * Object slots connected to signals will also run in the context of the * object's thread, regardless of whether the signal is emitted in the same or @@ -84,9 +85,20 @@ Object::Object(Object *parent) * Object instances shall be destroyed from the thread they are bound to, * otherwise undefined behaviour may occur. If deletion of an Object needs to * be scheduled from a different thread, deleteLater() shall be used. + * + * As an exception to this rule, Object instances may be deleted from a + * different thread if the thread the instance is bound to is stopped through + * the whole duration of the object's destruction, *and* the parent and children + * of the object do not get deleted concurrently. The caller is responsible for + * fulfilling those requirements. + * + * In all cases Object instances shall be deleted before the Thread they are + * bound to. */ Object::~Object() { + ASSERT(Thread::current() == thread_ || !thread_->isRunning()); + /* * Move signals to a private list to avoid concurrent iteration and * deletion of items from Signal::disconnect(). @@ -116,8 +128,9 @@ Object::~Object() * event loop that the object belongs to. This ensures the object is destroyed * from the right context, as required by the libcamera threading model. * - * If this function is called before the thread's event loop is started, the - * object will be deleted when the event loop starts. + * If this function is called before the thread's event loop is started or after + * it has stopped, the object will be deleted when the event loop (re)starts. If + * this never occurs, the object will be leaked. * * Deferred deletion can be used to control the destruction context with shared * pointers. An object managed with shared pointers is deleted when the last @@ -213,6 +226,35 @@ void Object::message(Message *msg) } /** + * \fn Object::assertThreadBound() + * \brief Check if the caller complies with thread-bound constraints + * \param[in] message The message to be printed on error + * + * This function verifies the calling constraints required by the \threadbound + * definition. It shall be called at the beginning of member functions of an + * Object subclass that are explicitly marked as thread-bound in their + * documentation. + * + * If the thread-bound constraints are not met, the function prints \a message + * as an error message. For debug builds, it additionally causes an assertion + * error. + * + * \todo Verify the thread-bound requirements for functions marked as + * thread-bound at the class level. + * + * \return True if the call is thread-bound compliant, false otherwise + */ +bool Object::assertThreadBound(const char *message) +{ + if (Thread::current() == thread_) + return true; + + LOG(Object, Error) << message; + ASSERT(false); + return false; +} + +/** * \fn R Object::invokeMethod() * \brief Invoke a method asynchronously on an Object instance * \param[in] func The object method to invoke @@ -259,11 +301,12 @@ void Object::message(Message *msg) * Moving an object that has a parent is not allowed, and causes undefined * behaviour. * - * \context This function is thread-bound. + * \context This function is \threadbound. */ void Object::moveToThread(Thread *thread) { - ASSERT(Thread::current() == thread_); + if (!assertThreadBound("Object can't be moved from another thread")) + return; if (thread_ == thread) return; @@ -307,7 +350,7 @@ void Object::connect(SignalBase *signal) void Object::disconnect(SignalBase *signal) { - for (auto iter = signals_.begin(); iter != signals_.end(); ) { + for (auto iter = signals_.begin(); iter != signals_.end();) { if (*iter == signal) iter = signals_.erase(iter); else diff --git a/src/libcamera/base/semaphore.cpp b/src/libcamera/base/semaphore.cpp index 6217e386..862f3b31 100644 --- a/src/libcamera/base/semaphore.cpp +++ b/src/libcamera/base/semaphore.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * semaphore.cpp - General-purpose counting semaphore + * General-purpose counting semaphore */ #include <libcamera/base/semaphore.h> diff --git a/src/libcamera/base/shared_fd.cpp b/src/libcamera/base/shared_fd.cpp index c711cf57..7afc8ca5 100644 --- a/src/libcamera/base/shared_fd.cpp +++ b/src/libcamera/base/shared_fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * shared_fd.cpp - File descriptor wrapper with shared ownership + * File descriptor wrapper with shared ownership */ #include <libcamera/base/shared_fd.h> diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp index a46386a0..7876a21d 100644 --- a/src/libcamera/base/signal.cpp +++ b/src/libcamera/base/signal.cpp @@ -2,12 +2,13 @@ /* * Copyright (C) 2019, Google Inc. * - * signal.cpp - Signal & slot implementation + * Signal & slot implementation */ #include <libcamera/base/signal.h> #include <libcamera/base/mutex.h> +#include <libcamera/base/object.h> /** * \file base/signal.h @@ -48,7 +49,7 @@ void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match) { MutexLocker locker(signalsLock); - for (auto iter = slots_.begin(); iter != slots_.end(); ) { + for (auto iter = slots_.begin(); iter != slots_.end();) { if (match(iter)) { Object *object = (*iter)->object(); if (object) @@ -74,7 +75,7 @@ SignalBase::SlotList SignalBase::slots() * * Signals and slots are a language construct aimed at communication between * objects through the observer pattern without the need for boilerplate code. - * See http://doc.qt.io/qt-5/signalsandslots.html for more information. + * See http://doc.qt.io/qt-6/signalsandslots.html for more information. * * Signals model events that can be observed from objects unrelated to the event * source. Slots are functions that are called in response to a signal. Signals diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp index b96951ac..d8fe0d69 100644 --- a/src/libcamera/base/thread.cpp +++ b/src/libcamera/base/thread.cpp @@ -2,13 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * thread.cpp - Thread support + * Thread support */ #include <libcamera/base/thread.h> #include <atomic> #include <list> +#include <optional> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> @@ -18,6 +19,7 @@ #include <libcamera/base/log.h> #include <libcamera/base/message.h> #include <libcamera/base/mutex.h> +#include <libcamera/base/object.h> /** * \page thread Thread Support @@ -63,42 +65,6 @@ * receiver's event loop, running in the receiver's thread. This mechanism can * be overridden by selecting a different connection type when calling * Signal::connect(). - * - * \section thread-reentrancy Reentrancy and Thread-Safety - * - * Through the documentation, several terms are used to define how classes and - * their member functions can be used from multiple threads. - * - * - A **reentrant** function may be called simultaneously from multiple - * threads if and only if each invocation uses a different instance of the - * class. This is the default for all member functions not explictly marked - * otherwise. - * - * - \anchor thread-safe A **thread-safe** function may be called - * simultaneously from multiple threads on the same instance of a class. A - * thread-safe function is thus reentrant. Thread-safe functions may also be - * called simultaneously with any other reentrant function of the same class - * on the same instance. - * - * - \anchor thread-bound A **thread-bound** function may be called only from - * the thread that the class instances lives in (see section \ref - * thread-objects). For instances of classes that do not derive from the - * Object class, this is the thread in which the instance was created. A - * thread-bound function is not thread-safe, and may or may not be reentrant. - * - * Neither reentrancy nor thread-safety, in this context, mean that a function - * may be called simultaneously from the same thread, for instance from a - * callback invoked by the function. This may deadlock and isn't allowed unless - * separately documented. - * - * A class is defined as reentrant, thread-safe or thread-bound if all its - * member functions are reentrant, thread-safe or thread-bound respectively. - * Some member functions may additionally be documented as having additional - * thread-related attributes. - * - * Most classes are reentrant but not thread-safe, as making them fully - * thread-safe would incur locking costs considered prohibitive for the - * expected use cases. */ /** @@ -163,6 +129,8 @@ private: int exitCode_; MessageQueue messages_; + + std::optional<cpu_set_t> cpuset_; }; /** @@ -289,6 +257,8 @@ void Thread::start() data_->exit_.store(false, std::memory_order_relaxed); thread_ = std::thread(&Thread::startThread, this); + + setThreadAffinityInternal(); } void Thread::startThread() @@ -370,6 +340,12 @@ void Thread::run() void Thread::finishThread() { + /* + * Objects may have been scheduled for deletion right before the thread + * exited. Ensure they get deleted now, before the thread stops. + */ + dispatchMessages(Message::Type::DeferredDelete); + data_->mutex_.lock(); data_->running_ = false; data_->mutex_.unlock(); @@ -440,6 +416,48 @@ bool Thread::wait(utils::duration duration) } /** + * \brief Set the CPU affinity mask of the thread + * \param[in] cpus The list of CPU indices that the thread is set affinity to + * + * The CPU indices should be within [0, std::thread::hardware_concurrency()). + * If any index is invalid, this function won't modify the thread affinity and + * will return an error. + * + * \return 0 if all indices are valid, -EINVAL otherwise + */ +int Thread::setThreadAffinity(const Span<const unsigned int> &cpus) +{ + const unsigned int numCpus = std::thread::hardware_concurrency(); + + MutexLocker locker(data_->mutex_); + data_->cpuset_ = cpu_set_t(); + CPU_ZERO(&data_->cpuset_.value()); + + for (const unsigned int &cpu : cpus) { + if (cpu >= numCpus) { + LOG(Thread, Error) << "Invalid CPU " << cpu << "for thread affinity"; + return -EINVAL; + } + + CPU_SET(cpu, &data_->cpuset_.value()); + } + + if (data_->running_) + setThreadAffinityInternal(); + + return 0; +} + +void Thread::setThreadAffinityInternal() +{ + if (!data_->cpuset_) + return; + + const cpu_set_t &cpuset = data_->cpuset_.value(); + pthread_setaffinity_np(thread_.native_handle(), sizeof(cpuset), &cpuset); +} + +/** * \brief Check if the thread is running * * A Thread instance is considered as running once the underlying thread has @@ -586,10 +604,12 @@ void Thread::removeMessages(Object *receiver) /** * \brief Dispatch posted messages for this thread * \param[in] type The message type + * \param[in] receiver The receiver whose messages to dispatch * - * This function immediately dispatches all the messages previously posted for - * this thread with postMessage() that match the message \a type. If the \a type - * is Message::Type::None, all messages are dispatched. + * This function immediately dispatches all the messages of the given \a type + * previously posted to this thread for the \a receiver with postMessage(). If + * the \a type is Message::Type::None, all messages types are dispatched. If the + * \a receiver is null, messages to all receivers are dispatched. * * Messages shall only be dispatched from the current thread, typically within * the thread from the run() function. Calling this function outside of the @@ -599,7 +619,7 @@ void Thread::removeMessages(Object *receiver) * same thread from an object's message handler. It guarantees delivery of * messages in the order they have been posted in all cases. */ -void Thread::dispatchMessages(Message::Type type) +void Thread::dispatchMessages(Message::Type type, Object *receiver) { ASSERT(data_ == ThreadData::current()); @@ -616,6 +636,9 @@ void Thread::dispatchMessages(Message::Type type) if (type != Message::Type::None && msg->type() != type) continue; + if (receiver && receiver != msg->receiver_) + continue; + /* * Move the message, setting the entry in the list to null. It * will cause recursive calls to ignore the entry, and the erase @@ -623,12 +646,12 @@ void Thread::dispatchMessages(Message::Type type) */ std::unique_ptr<Message> message = std::move(msg); - Object *receiver = message->receiver_; - ASSERT(data_ == receiver->thread()->data_); - receiver->pendingMessages_--; + Object *messageReceiver = message->receiver_; + ASSERT(data_ == messageReceiver->thread()->data_); + messageReceiver->pendingMessages_--; locker.unlock(); - receiver->message(message.get()); + messageReceiver->message(message.get()); message.reset(); locker.lock(); } @@ -639,7 +662,7 @@ void Thread::dispatchMessages(Message::Type type) * the outer calls. */ if (!--data_->messages_.recursion_) { - for (auto iter = messages.begin(); iter != messages.end(); ) { + for (auto iter = messages.begin(); iter != messages.end();) { if (!*iter) iter = messages.erase(iter); else diff --git a/src/libcamera/base/timer.cpp b/src/libcamera/base/timer.cpp index 74b060af..7b0f3725 100644 --- a/src/libcamera/base/timer.cpp +++ b/src/libcamera/base/timer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * timer.cpp - Generic timer + * Generic timer */ #include <libcamera/base/timer.h> @@ -85,10 +85,8 @@ void Timer::start(std::chrono::milliseconds duration) */ void Timer::start(std::chrono::steady_clock::time_point deadline) { - if (Thread::current() != thread()) { - LOG(Timer, Error) << "Timer " << this << " << can't be started from another thread"; + if (!assertThreadBound("Timer can't be started from another thread")) return; - } deadline_ = deadline; @@ -114,13 +112,11 @@ void Timer::start(std::chrono::steady_clock::time_point deadline) */ void Timer::stop() { - if (!isRunning()) + if (!assertThreadBound("Timer can't be stopped from another thread")) return; - if (Thread::current() != thread()) { - LOG(Timer, Error) << "Timer " << this << " can't be stopped from another thread"; + if (!isRunning()) return; - } unregisterTimer(); } diff --git a/src/libcamera/base/unique_fd.cpp b/src/libcamera/base/unique_fd.cpp index 83d6919c..d0649e4d 100644 --- a/src/libcamera/base/unique_fd.cpp +++ b/src/libcamera/base/unique_fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * unique_fd.cpp - File descriptor wrapper that owns a file descriptor + * File descriptor wrapper that owns a file descriptor */ #include <libcamera/base/unique_fd.h> diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp index 3b73b442..bcfc1941 100644 --- a/src/libcamera/base/utils.cpp +++ b/src/libcamera/base/utils.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * utils.cpp - Miscellaneous utility functions + * Miscellaneous utility functions */ #include <libcamera/base/utils.h> @@ -276,21 +276,6 @@ std::string details::StringSplitter::iterator::operator*() const return ss_->str_.substr(pos_, count); } -bool details::StringSplitter::iterator::operator!=(const details::StringSplitter::iterator &other) const -{ - return pos_ != other.pos_; -} - -details::StringSplitter::iterator details::StringSplitter::begin() const -{ - return iterator(this, 0); -} - -details::StringSplitter::iterator details::StringSplitter::end() const -{ - return iterator(this, std::string::npos); -} - /** * \fn template<typename Container, typename UnaryOp> \ * std::string utils::join(const Container &items, const std::string &sep, UnaryOp op) @@ -517,10 +502,152 @@ double strtod(const char *__restrict nptr, char **__restrict endptr) * If the libc implementation doesn't provide locale object support, * assume that strtod() is locale-independent. */ - return strtod(nptr, endptr); + return ::strtod(nptr, endptr); #endif } +/** + * \fn to_underlying(Enum e) + * \brief Convert an enumeration to its underlygin type + * \param[in] e Enumeration value to convert + * + * This function is equivalent to the C++23 std::to_underlying(). + * + * \return The value of e converted to its underlying type + */ + +/** + * \class ScopeExitActions + * \brief An object that performs actions upon destruction + * + * The ScopeExitActions class is a simple object that performs user-provided + * actions upon destruction. It is meant to simplify cleanup tasks in error + * handling paths. + * + * When the code flow performs multiple sequential actions that each need a + * corresponding cleanup action, error handling quickly become tedious: + * + * \code{.cpp} + * { + * int ret = allocateMemory(); + * if (ret) + * return ret; + * + * ret = startProducer(); + * if (ret) { + * freeMemory(); + * return ret; + * } + * + * ret = startConsumer(); + * if (ret) { + * stopProducer(); + * freeMemory(); + * return ret; + * } + * + * return 0; + * } + * \endcode + * + * This is prone to programming mistakes, as cleanup actions can easily be + * forgotten or ordered incorrectly. One strategy to simplify error handling is + * to use goto statements: + * + * \code{.cpp} + * { + * int ret = allocateMemory(); + * if (ret) + * return ret; + * + * ret = startProducer(); + * if (ret) + * goto error_free; + * + * ret = startConsumer(); + * if (ret) + * goto error_stop; + * + * return 0; + * + * error_stop: + * stopProducer(); + * error_free: + * freeMemory(); + * return ret; + * } + * \endcode + * + * While this may be considered better, this solution is still quite + * error-prone. Beside the risk of picking the wrong error label, the error + * handling logic is separated from the normal code flow, which increases the + * risk of error when refactoring the code. Additionally, C++ doesn't allow + * goto statements to jump over local variable declarations, which can make + * usage of this pattern more difficult. + * + * The ScopeExitActions class solves these issues by allowing code that + * requires cleanup actions to be grouped with its corresponding error handling + * code: + * + * \code{.cpp} + * { + * ScopeExitActions actions; + * + * int ret = allocateMemory(); + * if (ret) + * return ret; + * + * actions += [&]() { freeMemory(); }; + * + * ret = startProducer(); + * if (ret) + * return ret; + * + * actions += [&]() { stopProducer(); }; + * + * ret = startConsumer(); + * if (ret) + * return ret; + * + * actions.release(); + * return 0; + * } + * \endcode + * + * Error handlers are executed when the ScopeExitActions instance is destroyed, + * in the reverse order of their addition. + */ + +ScopeExitActions::~ScopeExitActions() +{ + for (const auto &action : utils::reverse(actions_)) + action(); +} + +/** + * \brief Add an exit action + * \param[in] action The action + * + * Add an exit action to the ScopeExitActions. Actions will be called upon + * destruction in the reverse order of their addition. + */ +void ScopeExitActions::operator+=(std::function<void()> &&action) +{ + actions_.push_back(std::move(action)); +} + +/** + * \brief Remove all exit actions + * + * This function should be called in scope exit paths that don't need the + * actions to be executed, such as success return paths from a function when + * the ScopeExitActions is used for error cleanup. + */ +void ScopeExitActions::release() +{ + actions_.clear(); +} + } /* namespace utils */ #ifndef __DOXYGEN__ diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp index 3bf15fb4..3dab91fc 100644 --- a/src/libcamera/bayer_format.cpp +++ b/src/libcamera/bayer_format.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * bayer_format.cpp - Class to represent Bayer formats + * Class to represent Bayer formats */ #include "libcamera/internal/bayer_format.h" @@ -61,6 +61,10 @@ namespace libcamera { * \brief Format uses MIPI CSI-2 style packing * \var BayerFormat::Packing::IPU3 * \brief Format uses IPU3 style packing + * \var BayerFormat::Packing::PISP1 + * \brief Format uses PISP mode 1 compression + * \var BayerFormat::Packing::PISP2 + * \brief Format uses PISP mode 2 compression */ namespace { @@ -164,12 +168,28 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{ { formats::SGRBG16, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG16) } }, { { BayerFormat::RGGB, 16, BayerFormat::Packing::None }, { formats::SRGGB16, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16) } }, + { { BayerFormat::BGGR, 16, BayerFormat::Packing::PISP1 }, + { formats::BGGR_PISP_COMP1, V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_BGGR) } }, + { { BayerFormat::GBRG, 16, BayerFormat::Packing::PISP1 }, + { formats::GBRG_PISP_COMP1, V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GBRG) } }, + { { BayerFormat::GRBG, 16, BayerFormat::Packing::PISP1 }, + { formats::GRBG_PISP_COMP1, V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GRBG) } }, + { { BayerFormat::RGGB, 16, BayerFormat::Packing::PISP1 }, + { formats::RGGB_PISP_COMP1, V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_RGGB) } }, { { BayerFormat::MONO, 8, BayerFormat::Packing::None }, { formats::R8, V4L2PixelFormat(V4L2_PIX_FMT_GREY) } }, { { BayerFormat::MONO, 10, BayerFormat::Packing::None }, { formats::R10, V4L2PixelFormat(V4L2_PIX_FMT_Y10) } }, { { BayerFormat::MONO, 10, BayerFormat::Packing::CSI2 }, { formats::R10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y10P) } }, + { { BayerFormat::MONO, 12, BayerFormat::Packing::None }, + { formats::R12, V4L2PixelFormat(V4L2_PIX_FMT_Y12) } }, + { { BayerFormat::MONO, 12, BayerFormat::Packing::CSI2 }, + { formats::R12_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y12P) } }, + { { BayerFormat::MONO, 16, BayerFormat::Packing::None }, + { formats::R16, V4L2PixelFormat(V4L2_PIX_FMT_Y16) } }, + { { BayerFormat::MONO, 16, BayerFormat::Packing::PISP1 }, + { formats::MONO_PISP_COMP1, V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_MONO) } }, }; const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{ @@ -205,9 +225,14 @@ const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{ { MEDIA_BUS_FMT_SGBRG16_1X16, { BayerFormat::GBRG, 16, BayerFormat::Packing::None } }, { MEDIA_BUS_FMT_SGRBG16_1X16, { BayerFormat::GRBG, 16, BayerFormat::Packing::None } }, { MEDIA_BUS_FMT_SRGGB16_1X16, { BayerFormat::RGGB, 16, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SBGGR20_1X20, { BayerFormat::BGGR, 20, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SGBRG20_1X20, { BayerFormat::GBRG, 20, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SGRBG20_1X20, { BayerFormat::GRBG, 20, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_SRGGB20_1X20, { BayerFormat::RGGB, 20, BayerFormat::Packing::None } }, { MEDIA_BUS_FMT_Y8_1X8, { BayerFormat::MONO, 8, BayerFormat::Packing::None } }, { MEDIA_BUS_FMT_Y10_1X10, { BayerFormat::MONO, 10, BayerFormat::Packing::None } }, { MEDIA_BUS_FMT_Y12_1X12, { BayerFormat::MONO, 12, BayerFormat::Packing::None } }, + { MEDIA_BUS_FMT_Y16_1X16, { BayerFormat::MONO, 16, BayerFormat::Packing::None } }, }; } /* namespace */ @@ -298,6 +323,10 @@ std::ostream &operator<<(std::ostream &out, const BayerFormat &f) out << "-CSI2P"; else if (f.packing == BayerFormat::Packing::IPU3) out << "-IPU3P"; + else if (f.packing == BayerFormat::Packing::PISP1) + out << "-PISP1"; + else if (f.packing == BayerFormat::Packing::PISP2) + out << "-PISP2"; return out; } diff --git a/src/libcamera/byte_stream_buffer.cpp b/src/libcamera/byte_stream_buffer.cpp index 881cd371..fba9a6f3 100644 --- a/src/libcamera/byte_stream_buffer.cpp +++ b/src/libcamera/byte_stream_buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * byte_stream_buffer.cpp - Byte stream buffer + * Byte stream buffer */ #include "libcamera/internal/byte_stream_buffer.h" diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 0ad1a4b5..c180a5fd 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -2,26 +2,31 @@ /* * Copyright (C) 2018, Google Inc. * - * camera.cpp - Camera device + * Camera device */ #include <libcamera/camera.h> #include <array> #include <atomic> -#include <iomanip> +#include <ios> +#include <memory> +#include <optional> +#include <set> +#include <sstream> #include <libcamera/base/log.h> #include <libcamera/base/thread.h> #include <libcamera/color_space.h> +#include <libcamera/control_ids.h> #include <libcamera/framebuffer_allocator.h> +#include <libcamera/property_ids.h> #include <libcamera/request.h> #include <libcamera/stream.h> #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_controls.h" -#include "libcamera/internal/formats.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" @@ -117,6 +122,12 @@ * of view is affected by the pipeline. */ +/** + * \internal + * \file libcamera/internal/camera.h + * \brief Internal camera device handling + */ + namespace libcamera { LOG_DECLARE_CATEGORY(Camera) @@ -327,7 +338,7 @@ void CameraConfiguration::addConfiguration(const StreamConfiguration &cfg) * \retval CameraConfiguration::Invalid The configuration is invalid and can't * be adjusted. This may only occur in extreme cases such as when the * configuration is empty. - * \retval CameraConfigutation::Adjusted The configuration has been adjusted + * \retval CameraConfiguration::Adjusted The configuration has been adjusted * and is now valid. Parameters may have changed for any stream, and stream * configurations may have been removed. The caller shall check the * configuration carefully. @@ -494,7 +505,7 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF std::optional<ColorSpace> colorSpace; Size size; - for (auto [i, cfg] : utils::enumerate(config_)) { + for (StreamConfiguration &cfg : config_) { if (!cfg.colorSpace) continue; @@ -559,6 +570,7 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF * \brief The vector of stream configurations */ +#ifndef __DOXYGEN_PUBLIC__ /** * \class Camera::Private * \brief Base class for camera private data @@ -576,7 +588,8 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF * \param[in] pipe The pipeline handler responsible for the camera device */ Camera::Private::Private(PipelineHandler *pipe) - : requestSequence_(0), pipe_(pipe->shared_from_this()), + : controlInfo_({}, controls::controls), properties_(properties::properties), + requestSequence_(0), pipe_(pipe->shared_from_this()), disconnected_(false), state_(CameraAvailable) { } @@ -594,6 +607,11 @@ Camera::Private::~Private() */ /** + * \fn Camera::Private::pipe() const + * \copydoc Camera::Private::pipe() + */ + +/** * \fn Camera::Private::validator() * \brief Retrieve the control validator related to this camera * \return The control validator associated with this camera @@ -719,6 +737,7 @@ void Camera::Private::setState(State state) { state_.store(state, std::memory_order_release); } +#endif /* __DOXYGEN_PUBLIC__ */ /** * \class Camera @@ -813,6 +832,7 @@ void Camera::Private::setState(State state) */ /** + * \internal * \brief Create a camera instance * \param[in] d Camera private data * \param[in] id The ID of the camera device @@ -986,7 +1006,8 @@ int Camera::acquire() if (ret < 0) return ret == -EACCES ? -EBUSY : ret; - if (!d->pipe_->acquire()) { + if (!d->pipe_->invokeMethod(&PipelineHandler::acquire, + ConnectionTypeBlocking, this)) { LOG(Camera, Info) << "Pipeline handler in use by another process"; return -EBUSY; @@ -1021,7 +1042,8 @@ int Camera::release() return ret == -EACCES ? -EBUSY : ret; if (d->isAcquired()) - d->pipe_->release(this); + d->pipe_->invokeMethod(&PipelineHandler::release, + ConnectionTypeBlocking, this); d->setState(Private::CameraAvailable); @@ -1164,8 +1186,8 @@ int Camera::configure(CameraConfiguration *config) if (ret < 0) return ret; - for (auto it : *config) - it.setStream(nullptr); + for (auto &cfg : *config) + cfg.setStream(nullptr); if (config->validate() != CameraConfiguration::Valid) { LOG(Camera, Error) @@ -1306,6 +1328,25 @@ int Camera::queueRequest(Request *request) } } + /* Pre-process AeEnable. */ + ControlList &controls = request->controls(); + const auto &aeEnable = controls.get(controls::AeEnable); + if (aeEnable) { + if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) && + !controls.contains(controls::AnalogueGainMode.id())) { + controls.set(controls::AnalogueGainMode, + *aeEnable ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); + } + + if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) && + !controls.contains(controls::ExposureTimeMode.id())) { + controls.set(controls::ExposureTimeMode, + *aeEnable ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + } + } + d->pipe_->invokeMethod(&PipelineHandler::queueRequest, ConnectionTypeQueued, request); diff --git a/src/libcamera/camera_controls.cpp b/src/libcamera/camera_controls.cpp index cabdcf75..b672c7cf 100644 --- a/src/libcamera/camera_controls.cpp +++ b/src/libcamera/camera_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_controls.cpp - Camera controls + * Camera controls */ #include "libcamera/internal/camera_controls.h" diff --git a/src/libcamera/camera_lens.cpp b/src/libcamera/camera_lens.cpp index b3d48199..ccc2a6a6 100644 --- a/src/libcamera/camera_lens.cpp +++ b/src/libcamera/camera_lens.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_lens.cpp - A camera lens + * A camera lens */ #include "libcamera/internal/camera_lens.h" diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index 355f3ada..e62e7193 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * camera_manager.h - Camera management + * Camera management */ #include "libcamera/internal/camera_manager.h" @@ -15,6 +15,7 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/pipeline_handler.h" /** @@ -23,6 +24,7 @@ */ /** + * \internal * \file libcamera/internal/camera_manager.h * \brief Internal camera manager support */ @@ -34,9 +36,11 @@ namespace libcamera { LOG_DEFINE_CATEGORY(Camera) +#ifndef __DOXYGEN_PUBLIC__ CameraManager::Private::Private() : initialized_(false) { + ipaManager_ = std::make_unique<IPAManager>(); } int CameraManager::Private::start() @@ -76,8 +80,10 @@ void CameraManager::Private::run() mutex_.unlock(); cv_.notify_one(); - if (ret < 0) + if (ret < 0) { + cleanup(); return; + } /* Now start processing events and messages. */ exec(); @@ -99,16 +105,37 @@ int CameraManager::Private::init() void CameraManager::Private::createPipelineHandlers() { - CameraManager *const o = LIBCAMERA_O_PTR(); - /* * \todo Try to read handlers and order from configuration - * file and only fallback on all handlers if there is no - * configuration file. + * file and only fallback on environment variable or all handlers, if + * there is no configuration file. */ + const char *pipesList = + utils::secure_getenv("LIBCAMERA_PIPELINES_MATCH_LIST"); + if (pipesList) { + /* + * When a list of preferred pipelines is defined, iterate + * through the ordered list to match the enumerated devices. + */ + for (const auto &pipeName : utils::split(pipesList, ",")) { + const PipelineHandlerFactoryBase *factory; + factory = PipelineHandlerFactoryBase::getFactoryByName(pipeName); + if (!factory) + continue; + + LOG(Camera, Debug) + << "Found listed pipeline handler '" + << pipeName << "'"; + pipelineFactoryMatch(factory); + } + + return; + } + const std::vector<PipelineHandlerFactoryBase *> &factories = PipelineHandlerFactoryBase::factories(); + /* Match all the registered pipeline handlers. */ for (const PipelineHandlerFactoryBase *factory : factories) { LOG(Camera, Debug) << "Found registered pipeline handler '" @@ -117,15 +144,23 @@ void CameraManager::Private::createPipelineHandlers() * Try each pipeline handler until it exhaust * all pipelines it can provide. */ - while (1) { - std::shared_ptr<PipelineHandler> pipe = factory->create(o); - if (!pipe->match(enumerator_.get())) - break; + pipelineFactoryMatch(factory); + } +} - LOG(Camera, Debug) - << "Pipeline handler \"" << factory->name() - << "\" matched"; - } +void CameraManager::Private::pipelineFactoryMatch(const PipelineHandlerFactoryBase *factory) +{ + CameraManager *const o = LIBCAMERA_O_PTR(); + + /* Provide as many matching pipelines as possible. */ + while (1) { + std::shared_ptr<PipelineHandler> pipe = factory->create(o); + if (!pipe->match(enumerator_.get())) + break; + + LOG(Camera, Debug) + << "Pipeline handler \"" << factory->name() + << "\" matched"; } } @@ -167,24 +202,24 @@ void CameraManager::Private::addCamera(std::shared_ptr<Camera> camera) { ASSERT(Thread::current() == this); - MutexLocker locker(mutex_); + { + MutexLocker locker(mutex_); - for (const std::shared_ptr<Camera> &c : cameras_) { - if (c->id() == camera->id()) { - LOG(Camera, Fatal) - << "Trying to register a camera with a duplicated ID '" - << camera->id() << "'"; - return; + for (const std::shared_ptr<Camera> &c : cameras_) { + if (c->id() == camera->id()) { + LOG(Camera, Fatal) + << "Trying to register a camera with a duplicated ID '" + << camera->id() << "'"; + return; + } } - } - - cameras_.push_back(std::move(camera)); - unsigned int index = cameras_.size() - 1; + cameras_.push_back(camera); + } /* Report the addition to the public signal */ CameraManager *const o = LIBCAMERA_O_PTR(); - o->cameraAdded.emit(cameras_[index]); + o->cameraAdded.emit(camera); } /** @@ -201,26 +236,33 @@ void CameraManager::Private::removeCamera(std::shared_ptr<Camera> camera) { ASSERT(Thread::current() == this); - MutexLocker locker(mutex_); + { + MutexLocker locker(mutex_); - auto iter = std::find_if(cameras_.begin(), cameras_.end(), - [camera](std::shared_ptr<Camera> &c) { - return c.get() == camera.get(); - }); - if (iter == cameras_.end()) - return; + auto iter = std::find(cameras_.begin(), cameras_.end(), camera); + if (iter == cameras_.end()) + return; + + cameras_.erase(iter); + } LOG(Camera, Debug) << "Unregistering camera '" << camera->id() << "'"; - cameras_.erase(iter); - /* Report the removal to the public signal */ CameraManager *const o = LIBCAMERA_O_PTR(); o->cameraRemoved.emit(camera); } /** + * \fn CameraManager::Private::ipaManager() const + * \brief Retrieve the IPAManager + * \context This function is \threadsafe. + * \return The IPAManager for this CameraManager + */ +#endif /* __DOXYGEN_PUBLIC__ */ + +/** * \class CameraManager * \brief Provide access and manage all cameras in the system * @@ -339,13 +381,13 @@ std::vector<std::shared_ptr<Camera>> CameraManager::cameras() const * * \return Shared pointer to Camera object or nullptr if camera not found */ -std::shared_ptr<Camera> CameraManager::get(const std::string &id) +std::shared_ptr<Camera> CameraManager::get(std::string_view id) { Private *const d = _d(); MutexLocker locker(d->mutex_); - for (std::shared_ptr<Camera> camera : d->cameras_) { + for (const std::shared_ptr<Camera> &camera : d->cameras_) { if (camera->id() == id) return camera; } diff --git a/src/libcamera/color_space.cpp b/src/libcamera/color_space.cpp index 7356bf7d..3d1c456c 100644 --- a/src/libcamera/color_space.cpp +++ b/src/libcamera/color_space.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Raspberry Pi Ltd * - * color_space.cpp - color spaces. + * color spaces. */ #include <libcamera/color_space.h> diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in index 5fb1c2c3..65668d48 100644 --- a/src/libcamera/control_ids.cpp.in +++ b/src/libcamera/control_ids.cpp.in @@ -2,61 +2,122 @@ /* * Copyright (C) 2019, Google Inc. * - * control_ids.cpp : Control ID list + * {{mode}} ID list * * This file is auto-generated. Do not edit. */ -#include <libcamera/control_ids.h> +#include <libcamera/{{filename}}.h> #include <libcamera/controls.h> /** - * \file control_ids.h - * \brief Camera control identifiers + * \file {{filename}}.h + * \brief Camera {{mode}} identifiers */ namespace libcamera { /** - * \brief Namespace for libcamera controls + * \brief Namespace for libcamera {{mode}} */ -namespace controls { +namespace {{mode}} { -${controls_doc} +{%- for vendor, ctrls in controls -%} +{%- if vendor != 'libcamera' %} /** - * \brief Namespace for libcamera draft controls + * \brief Namespace for {{vendor}} {{mode}} */ -namespace draft { +namespace {{vendor}} { +{%- endif -%} -${draft_controls_doc} +{% for ctrl in ctrls %} -} /* namespace draft */ +{% if ctrl.is_enum -%} +/** + * \enum {{ctrl.name}}Enum + * \brief Supported {{ctrl.name}} values +{%- for enum in ctrl.enum_values %} + * + * \var {{enum.name}} + * \brief {{enum.description|format_description}} +{%- endfor %} + */ + +/** + * \var {{ctrl.name}}Values + * \brief List of all {{ctrl.name}} supported values + */ + +/** + * \var {{ctrl.name}}NameValueMap + * \brief Map of all {{ctrl.name}} supported value names (in std::string format) to value + */ + +{% endif -%} +/** + * \var {{ctrl.name}} + * \brief {{ctrl.description|format_description}} + */ +{%- endfor %} +{% if vendor != 'libcamera' %} +} /* namespace {{vendor}} */ +{% endif -%} + +{%- endfor %} #ifndef __DOXYGEN__ /* - * Keep the controls definitions hidden from doxygen as it incorrectly parses + * Keep the {{mode}} definitions hidden from doxygen as it incorrectly parses * them as functions. */ -${controls_def} +{% for vendor, ctrls in controls -%} + +{% if vendor != 'libcamera' %} +namespace {{vendor}} { +{% endif %} -namespace draft { +{%- for ctrl in ctrls %} +{% if ctrl.is_enum -%} +extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values = { +{%- for enum in ctrl.enum_values %} + static_cast<{{ctrl.type}}>({{enum.name}}), +{%- endfor %} +}; +extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = { +{%- for enum in ctrl.enum_values %} + { "{{enum.name}}", {{enum.name}} }, +{%- endfor %} +}; +extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}", "{{vendor}}", {{ctrl.direction}}, {{ctrl.name}}NameValueMap); +{% else -%} +extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}", "{{vendor}}", {{ctrl.direction}}); +{% endif -%} +{%- endfor %} -${draft_controls_def} +{% if vendor != 'libcamera' %} +} /* namespace {{vendor}} */ +{% endif -%} -} /* namespace draft */ -#endif +{%- endfor %} +#endif /* __DOXYGEN__ */ /** - * \brief List of all supported libcamera controls + * \brief List of all supported libcamera {{mode}} +{%- if mode == 'controls' %} * * Unless otherwise stated, all controls are bi-directional, i.e. they can be * set through Request::controls() and returned out through Request::metadata(). +{%- endif %} */ -extern const ControlIdMap controls { -${controls_map} +extern const ControlIdMap {{mode}} { +{%- for vendor, ctrls in controls -%} +{%- for ctrl in ctrls %} + { {{ctrl.namespace}}{{ctrl.name|snake_case|upper}}, &{{ctrl.namespace}}{{ctrl.name}} }, +{%- endfor -%} +{%- endfor %} }; -} /* namespace controls */ +} /* namespace {{mode}} */ } /* namespace libcamera */ diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml deleted file mode 100644 index f2e542f4..00000000 --- a/src/libcamera/control_ids.yaml +++ /dev/null @@ -1,1009 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -# -# Copyright (C) 2019, Google Inc. -# -%YAML 1.1 ---- -# Unless otherwise stated, all controls are bi-directional, i.e. they can be -# set through Request::controls() and returned out through Request::metadata(). -controls: - - AeEnable: - type: bool - description: | - Enable or disable the AE. - - \sa ExposureTime AnalogueGain - - - AeLocked: - type: bool - description: | - Report the lock status of a running AE algorithm. - - If the AE algorithm is locked the value shall be set to true, if it's - converging it shall be set to false. If the AE algorithm is not - running the control shall not be present in the metadata control list. - - \sa AeEnable - - # AeMeteringMode needs further attention: - # - Auto-generate max enum value. - # - Better handling of custom types. - - AeMeteringMode: - type: int32_t - description: | - Specify a metering mode for the AE algorithm to use. The metering - modes determine which parts of the image are used to determine the - scene brightness. Metering modes may be platform specific and not - all metering modes may be supported. - enum: - - name: MeteringCentreWeighted - value: 0 - description: Centre-weighted metering mode. - - name: MeteringSpot - value: 1 - description: Spot metering mode. - - name: MeteringMatrix - value: 2 - description: Matrix metering mode. - - name: MeteringCustom - value: 3 - description: Custom metering mode. - - # AeConstraintMode needs further attention: - # - Auto-generate max enum value. - # - Better handling of custom types. - - AeConstraintMode: - type: int32_t - description: | - Specify a constraint mode for the AE algorithm to use. These determine - how the measured scene brightness is adjusted to reach the desired - target exposure. Constraint modes may be platform specific, and not - all constraint modes may be supported. - enum: - - name: ConstraintNormal - value: 0 - description: Default constraint mode. - This mode aims to balance the exposure of different parts of the - image so as to reach a reasonable average level. However, highlights - in the image may appear over-exposed and lowlights may appear - under-exposed. - - name: ConstraintHighlight - value: 1 - description: Highlight constraint mode. - This mode adjusts the exposure levels in order to try and avoid - over-exposing the brightest parts (highlights) of an image. - Other non-highlight parts of the image may appear under-exposed. - - name: ConstraintShadows - value: 2 - description: Shadows constraint mode. - This mode adjusts the exposure levels in order to try and avoid - under-exposing the dark parts (shadows) of an image. Other normally - exposed parts of the image may appear over-exposed. - - name: ConstraintCustom - value: 3 - description: Custom constraint mode. - - # AeExposureMode needs further attention: - # - Auto-generate max enum value. - # - Better handling of custom types. - - AeExposureMode: - type: int32_t - description: | - Specify an exposure mode for the AE algorithm to use. These specify - how the desired total exposure is divided between the shutter time - and the sensor's analogue gain. The exposure modes are platform - specific, and not all exposure modes may be supported. - enum: - - name: ExposureNormal - value: 0 - description: Default exposure mode. - - name: ExposureShort - value: 1 - description: Exposure mode allowing only short exposure times. - - name: ExposureLong - value: 2 - description: Exposure mode allowing long exposure times. - - name: ExposureCustom - value: 3 - description: Custom exposure mode. - - - ExposureValue: - type: float - description: | - Specify an Exposure Value (EV) parameter. The EV parameter will only be - applied if the AE algorithm is currently enabled. - - By convention EV adjusts the exposure as log2. For example - EV = [-2, -1, 0.5, 0, 0.5, 1, 2] results in an exposure adjustment - of [1/4x, 1/2x, 1/sqrt(2)x, 1x, sqrt(2)x, 2x, 4x]. - - \sa AeEnable - - - ExposureTime: - type: int32_t - description: | - Exposure time (shutter speed) for the frame applied in the sensor - device. This value is specified in micro-seconds. - - Setting this value means that it is now fixed and the AE algorithm may - not change it. Setting it back to zero returns it to the control of the - AE algorithm. - - \sa AnalogueGain AeEnable - - \todo Document the interactions between AeEnable and setting a fixed - value for this control. Consider interactions with other AE features, - such as aperture and aperture/shutter priority mode, and decide if - control of which features should be automatically adjusted shouldn't - better be handled through a separate AE mode control. - - - AnalogueGain: - type: float - description: | - Analogue gain value applied in the sensor device. - The value of the control specifies the gain multiplier applied to all - colour channels. This value cannot be lower than 1.0. - - Setting this value means that it is now fixed and the AE algorithm may - not change it. Setting it back to zero returns it to the control of the - AE algorithm. - - \sa ExposureTime AeEnable - - \todo Document the interactions between AeEnable and setting a fixed - value for this control. Consider interactions with other AE features, - such as aperture and aperture/shutter priority mode, and decide if - control of which features should be automatically adjusted shouldn't - better be handled through a separate AE mode control. - - - AeFlickerMode: - type: int32_t - description: | - Set the flicker mode, which determines whether, and how, the AGC/AEC - algorithm attempts to hide flicker effects caused by the duty cycle of - artificial lighting. - - Although implementation dependent, many algorithms for "flicker - avoidance" work by restricting this exposure time to integer multiples - of the cycle period, wherever possible. - - Implementations may not support all of the flicker modes listed below. - - By default the system will start in FlickerAuto mode if this is - supported, otherwise the flicker mode will be set to FlickerOff. - - enum: - - name: FlickerOff - value: 0 - description: No flicker avoidance is performed. - - name: FlickerManual - value: 1 - description: Manual flicker avoidance. - Suppress flicker effects caused by lighting running with a period - specified by the AeFlickerPeriod control. - \sa AeFlickerPeriod - - name: FlickerAuto - value: 2 - description: Automatic flicker period detection and avoidance. - The system will automatically determine the most likely value of - flicker period, and avoid flicker of this frequency. Once flicker - is being corrected, it is implementation dependent whether the - system is still able to detect a change in the flicker period. - \sa AeFlickerDetected - - - AeFlickerPeriod: - type: int32_t - description: Manual flicker period in microseconds. - This value sets the current flicker period to avoid. It is used when - AeFlickerMode is set to FlickerManual. - - To cancel 50Hz mains flicker, this should be set to 10000 (corresponding - to 100Hz), or 8333 (120Hz) for 60Hz mains. - - Setting the mode to FlickerManual when no AeFlickerPeriod has ever been - set means that no flicker cancellation occurs (until the value of this - control is updated). - - Switching to modes other than FlickerManual has no effect on the - value of the AeFlickerPeriod control. - - \sa AeFlickerMode - - - AeFlickerDetected: - type: int32_t - description: Flicker period detected in microseconds. - The value reported here indicates the currently detected flicker - period, or zero if no flicker at all is detected. - - When AeFlickerMode is set to FlickerAuto, there may be a period during - which the value reported here remains zero. Once a non-zero value is - reported, then this is the flicker period that has been detected and is - now being cancelled. - - In the case of 50Hz mains flicker, the value would be 10000 - (corresponding to 100Hz), or 8333 (120Hz) for 60Hz mains flicker. - - It is implementation dependent whether the system can continue to detect - flicker of different periods when another frequency is already being - cancelled. - - \sa AeFlickerMode - - - Brightness: - type: float - description: | - Specify a fixed brightness parameter. Positive values (up to 1.0) - produce brighter images; negative values (up to -1.0) produce darker - images and 0.0 leaves pixels unchanged. - - - Contrast: - type: float - description: | - Specify a fixed contrast parameter. Normal contrast is given by the - value 1.0; larger values produce images with more contrast. - - - Lux: - type: float - description: | - Report an estimate of the current illuminance level in lux. The Lux - control can only be returned in metadata. - - - AwbEnable: - type: bool - description: | - Enable or disable the AWB. - - \sa ColourGains - - # AwbMode needs further attention: - # - Auto-generate max enum value. - # - Better handling of custom types. - - AwbMode: - type: int32_t - description: | - Specify the range of illuminants to use for the AWB algorithm. The modes - supported are platform specific, and not all modes may be supported. - enum: - - name: AwbAuto - value: 0 - description: Search over the whole colour temperature range. - - name: AwbIncandescent - value: 1 - description: Incandescent AWB lamp mode. - - name: AwbTungsten - value: 2 - description: Tungsten AWB lamp mode. - - name: AwbFluorescent - value: 3 - description: Fluorescent AWB lamp mode. - - name: AwbIndoor - value: 4 - description: Indoor AWB lighting mode. - - name: AwbDaylight - value: 5 - description: Daylight AWB lighting mode. - - name: AwbCloudy - value: 6 - description: Cloudy AWB lighting mode. - - name: AwbCustom - value: 7 - description: Custom AWB mode. - - - AwbLocked: - type: bool - description: | - Report the lock status of a running AWB algorithm. - - If the AWB algorithm is locked the value shall be set to true, if it's - converging it shall be set to false. If the AWB algorithm is not - running the control shall not be present in the metadata control list. - - \sa AwbEnable - - - ColourGains: - type: float - description: | - Pair of gain values for the Red and Blue colour channels, in that - order. ColourGains can only be applied in a Request when the AWB is - disabled. - - \sa AwbEnable - size: [2] - - - ColourTemperature: - type: int32_t - description: Report the current estimate of the colour temperature, in - kelvin, for this frame. The ColourTemperature control can only be - returned in metadata. - - - Saturation: - type: float - description: | - Specify a fixed saturation parameter. Normal saturation is given by - the value 1.0; larger values produce more saturated colours; 0.0 - produces a greyscale image. - - - SensorBlackLevels: - type: int32_t - description: | - Reports the sensor black levels used for processing a frame, in the - order R, Gr, Gb, B. These values are returned as numbers out of a 16-bit - pixel range (as if pixels ranged from 0 to 65535). The SensorBlackLevels - control can only be returned in metadata. - size: [4] - - - Sharpness: - type: float - description: | - A value of 0.0 means no sharpening. The minimum value means - minimal sharpening, and shall be 0.0 unless the camera can't - disable sharpening completely. The default value shall give a - "reasonable" level of sharpening, suitable for most use cases. - The maximum value may apply extremely high levels of sharpening, - higher than anyone could reasonably want. Negative values are - not allowed. Note also that sharpening is not applied to raw - streams. - - - FocusFoM: - type: int32_t - description: | - Reports a Figure of Merit (FoM) to indicate how in-focus the frame is. - A larger FocusFoM value indicates a more in-focus frame. This singular - value may be based on a combination of statistics gathered from - multiple focus regions within an image. The number of focus regions and - method of combination is platform dependent. In this respect, it is not - necessarily aimed at providing a way to implement a focus algorithm by - the application, rather an indication of how in-focus a frame is. - - - ColourCorrectionMatrix: - type: float - description: | - The 3x3 matrix that converts camera RGB to sRGB within the - imaging pipeline. This should describe the matrix that is used - after pixels have been white-balanced, but before any gamma - transformation. The 3x3 matrix is stored in conventional reading - order in an array of 9 floating point values. - - size: [3,3] - - - ScalerCrop: - type: Rectangle - description: | - Sets the image portion that will be scaled to form the whole of - the final output image. The (x,y) location of this rectangle is - relative to the PixelArrayActiveAreas that is being used. The units - remain native sensor pixels, even if the sensor is being used in - a binning or skipping mode. - - This control is only present when the pipeline supports scaling. Its - maximum valid value is given by the properties::ScalerCropMaximum - property, and the two can be used to implement digital zoom. - - - DigitalGain: - type: float - description: | - Digital gain value applied during the processing steps applied - to the image as captured from the sensor. - - The global digital gain factor is applied to all the colour channels - of the RAW image. Different pipeline models are free to - specify how the global gain factor applies to each separate - channel. - - If an imaging pipeline applies digital gain in distinct - processing steps, this value indicates their total sum. - Pipelines are free to decide how to adjust each processing - step to respect the received gain factor and shall report - their total value in the request metadata. - - - FrameDuration: - type: int64_t - description: | - The instantaneous frame duration from start of frame exposure to start - of next exposure, expressed in microseconds. This control is meant to - be returned in metadata. - - - FrameDurationLimits: - type: int64_t - description: | - The minimum and maximum (in that order) frame duration, expressed in - microseconds. - - When provided by applications, the control specifies the sensor frame - duration interval the pipeline has to use. This limits the largest - exposure time the sensor can use. For example, if a maximum frame - duration of 33ms is requested (corresponding to 30 frames per second), - the sensor will not be able to raise the exposure time above 33ms. - A fixed frame duration is achieved by setting the minimum and maximum - values to be the same. Setting both values to 0 reverts to using the - camera defaults. - - The maximum frame duration provides the absolute limit to the shutter - speed computed by the AE algorithm and it overrides any exposure mode - setting specified with controls::AeExposureMode. Similarly, when a - manual exposure time is set through controls::ExposureTime, it also - gets clipped to the limits set by this control. When reported in - metadata, the control expresses the minimum and maximum frame - durations used after being clipped to the sensor provided frame - duration limits. - - \sa AeExposureMode - \sa ExposureTime - - \todo Define how to calculate the capture frame rate by - defining controls to report additional delays introduced by - the capture pipeline or post-processing stages (ie JPEG - conversion, frame scaling). - - \todo Provide an explicit definition of default control values, for - this and all other controls. - - size: [2] - - - SensorTemperature: - type: float - description: | - Temperature measure from the camera sensor in Celsius. This is typically - obtained by a thermal sensor present on-die or in the camera module. The - range of reported temperatures is device dependent. - - The SensorTemperature control will only be returned in metadata if a - themal sensor is present. - - - SensorTimestamp: - type: int64_t - description: | - The time when the first row of the image sensor active array is exposed. - - The timestamp, expressed in nanoseconds, represents a monotonically - increasing counter since the system boot time, as defined by the - Linux-specific CLOCK_BOOTTIME clock id. - - The SensorTimestamp control can only be returned in metadata. - - \todo Define how the sensor timestamp has to be used in the reprocessing - use case. - - - AfMode: - type: int32_t - description: | - Control to set the mode of the AF (autofocus) algorithm. - - An implementation may choose not to implement all the modes. - - enum: - - name: AfModeManual - value: 0 - description: | - The AF algorithm is in manual mode. In this mode it will never - perform any action nor move the lens of its own accord, but an - application can specify the desired lens position using the - LensPosition control. - - In this mode the AfState will always report AfStateIdle. - - If the camera is started in AfModeManual, it will move the focus - lens to the position specified by the LensPosition control. - - This mode is the recommended default value for the AfMode control. - External cameras (as reported by the Location property set to - CameraLocationExternal) may use a different default value. - - name: AfModeAuto - value: 1 - description: | - The AF algorithm is in auto mode. This means that the algorithm - will never move the lens or change state unless the AfTrigger - control is used. The AfTrigger control can be used to initiate a - focus scan, the results of which will be reported by AfState. - - If the autofocus algorithm is moved from AfModeAuto to another - mode while a scan is in progress, the scan is cancelled - immediately, without waiting for the scan to finish. - - When first entering this mode the AfState will report - AfStateIdle. When a trigger control is sent, AfState will - report AfStateScanning for a period before spontaneously - changing to AfStateFocused or AfStateFailed, depending on - the outcome of the scan. It will remain in this state until - another scan is initiated by the AfTrigger control. If a scan is - cancelled (without changing to another mode), AfState will return - to AfStateIdle. - - name: AfModeContinuous - value: 2 - description: | - The AF algorithm is in continuous mode. This means that the lens can - re-start a scan spontaneously at any moment, without any user - intervention. The AfState still reports whether the algorithm is - currently scanning or not, though the application has no ability to - initiate or cancel scans, nor to move the lens for itself. - - However, applications can pause the AF algorithm from continuously - scanning by using the AfPause control. This allows video or still - images to be captured whilst guaranteeing that the focus is fixed. - - When set to AfModeContinuous, the system will immediately initiate a - scan so AfState will report AfStateScanning, and will settle on one - of AfStateFocused or AfStateFailed, depending on the scan result. - - - AfRange: - type: int32_t - description: | - Control to set the range of focus distances that is scanned. An - implementation may choose not to implement all the options here. - enum: - - name: AfRangeNormal - value: 0 - description: | - A wide range of focus distances is scanned, all the way from - infinity down to close distances, though depending on the - implementation, possibly not including the very closest macro - positions. - - name: AfRangeMacro - value: 1 - description: Only close distances are scanned. - - name: AfRangeFull - value: 2 - description: | - The full range of focus distances is scanned just as with - AfRangeNormal but this time including the very closest macro - positions. - - - AfSpeed: - type: int32_t - description: | - Control that determines whether the AF algorithm is to move the lens - as quickly as possible or more steadily. For example, during video - recording it may be desirable not to move the lens too abruptly, but - when in a preview mode (waiting for a still capture) it may be - helpful to move the lens as quickly as is reasonably possible. - enum: - - name: AfSpeedNormal - value: 0 - description: Move the lens at its usual speed. - - name: AfSpeedFast - value: 1 - description: Move the lens more quickly. - - - AfMetering: - type: int32_t - description: | - Instruct the AF algorithm how it should decide which parts of the image - should be used to measure focus. - enum: - - name: AfMeteringAuto - value: 0 - description: The AF algorithm should decide for itself where it will - measure focus. - - name: AfMeteringWindows - value: 1 - description: The AF algorithm should use the rectangles defined by - the AfWindows control to measure focus. If no windows are specified - the behaviour is platform dependent. - - - AfWindows: - type: Rectangle - description: | - Sets the focus windows used by the AF algorithm when AfMetering is set - to AfMeteringWindows. The units used are pixels within the rectangle - returned by the ScalerCropMaximum property. - - In order to be activated, a rectangle must be programmed with non-zero - width and height. Internally, these rectangles are intersected with the - ScalerCropMaximum rectangle. If the window becomes empty after this - operation, then the window is ignored. If all the windows end up being - ignored, then the behaviour is platform dependent. - - On platforms that support the ScalerCrop control (for implementing - digital zoom, for example), no automatic recalculation or adjustment of - AF windows is performed internally if the ScalerCrop is changed. If any - window lies outside the output image after the scaler crop has been - applied, it is up to the application to recalculate them. - - The details of how the windows are used are platform dependent. We note - that when there is more than one AF window, a typical implementation - might find the optimal focus position for each one and finally select - the window where the focal distance for the objects shown in that part - of the image are closest to the camera. - - size: [n] - - - AfTrigger: - type: int32_t - description: | - This control starts an autofocus scan when AfMode is set to AfModeAuto, - and can also be used to terminate a scan early. - - It is ignored if AfMode is set to AfModeManual or AfModeContinuous. - - enum: - - name: AfTriggerStart - value: 0 - description: Start an AF scan. Ignored if a scan is in progress. - - name: AfTriggerCancel - value: 1 - description: Cancel an AF scan. This does not cause the lens to move - anywhere else. Ignored if no scan is in progress. - - - AfPause: - type: int32_t - description: | - This control has no effect except when in continuous autofocus mode - (AfModeContinuous). It can be used to pause any lens movements while - (for example) images are captured. The algorithm remains inactive - until it is instructed to resume. - - enum: - - name: AfPauseImmediate - value: 0 - description: | - Pause the continuous autofocus algorithm immediately, whether or not - any kind of scan is underway. AfPauseState will subsequently report - AfPauseStatePaused. AfState may report any of AfStateScanning, - AfStateFocused or AfStateFailed, depending on the algorithm's state - when it received this control. - - name: AfPauseDeferred - value: 1 - description: | - This is similar to AfPauseImmediate, and if the AfState is currently - reporting AfStateFocused or AfStateFailed it will remain in that - state and AfPauseState will report AfPauseStatePaused. - - However, if the algorithm is scanning (AfStateScanning), - AfPauseState will report AfPauseStatePausing until the scan is - finished, at which point AfState will report one of AfStateFocused - or AfStateFailed, and AfPauseState will change to - AfPauseStatePaused. - - - name: AfPauseResume - value: 2 - description: | - Resume continuous autofocus operation. The algorithm starts again - from exactly where it left off, and AfPauseState will report - AfPauseStateRunning. - - - LensPosition: - type: float - description: | - Acts as a control to instruct the lens to move to a particular position - and also reports back the position of the lens for each frame. - - The LensPosition control is ignored unless the AfMode is set to - AfModeManual, though the value is reported back unconditionally in all - modes. - - This value, which is generally a non-integer, is the reciprocal of the - focal distance in metres, also known as dioptres. That is, to set a - focal distance D, the lens position LP is given by - - \f$LP = \frac{1\mathrm{m}}{D}\f$ - - For example: - - 0 moves the lens to infinity. - 0.5 moves the lens to focus on objects 2m away. - 2 moves the lens to focus on objects 50cm away. - And larger values will focus the lens closer. - - The default value of the control should indicate a good general position - for the lens, often corresponding to the hyperfocal distance (the - closest position for which objects at infinity are still acceptably - sharp). The minimum will often be zero (meaning infinity), and the - maximum value defines the closest focus position. - - \todo Define a property to report the Hyperfocal distance of calibrated - lenses. - - - AfState: - type: int32_t - description: | - Reports the current state of the AF algorithm in conjunction with the - reported AfMode value and (in continuous AF mode) the AfPauseState - value. The possible state changes are described below, though we note - the following state transitions that occur when the AfMode is changed. - - If the AfMode is set to AfModeManual, then the AfState will always - report AfStateIdle (even if the lens is subsequently moved). Changing to - the AfModeManual state does not initiate any lens movement. - - If the AfMode is set to AfModeAuto then the AfState will report - AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent together - then AfState will omit AfStateIdle and move straight to AfStateScanning - (and start a scan). - - If the AfMode is set to AfModeContinuous then the AfState will initially - report AfStateScanning. - - enum: - - name: AfStateIdle - value: 0 - description: | - The AF algorithm is in manual mode (AfModeManual) or in auto mode - (AfModeAuto) and a scan has not yet been triggered, or an - in-progress scan was cancelled. - - name: AfStateScanning - value: 1 - description: | - The AF algorithm is in auto mode (AfModeAuto), and a scan has been - started using the AfTrigger control. The scan can be cancelled by - sending AfTriggerCancel at which point the algorithm will either - move back to AfStateIdle or, if the scan actually completes before - the cancel request is processed, to one of AfStateFocused or - AfStateFailed. - - Alternatively the AF algorithm could be in continuous mode - (AfModeContinuous) at which point it may enter this state - spontaneously whenever it determines that a rescan is needed. - - name: AfStateFocused - value: 2 - description: | - The AF algorithm is in auto (AfModeAuto) or continuous - (AfModeContinuous) mode and a scan has completed with the result - that the algorithm believes the image is now in focus. - - name: AfStateFailed - value: 3 - description: | - The AF algorithm is in auto (AfModeAuto) or continuous - (AfModeContinuous) mode and a scan has completed with the result - that the algorithm did not find a good focus position. - - - AfPauseState: - type: int32_t - description: | - Only applicable in continuous (AfModeContinuous) mode, this reports - whether the algorithm is currently running, paused or pausing (that is, - will pause as soon as any in-progress scan completes). - - Any change to AfMode will cause AfPauseStateRunning to be reported. - - enum: - - name: AfPauseStateRunning - value: 0 - description: | - Continuous AF is running and the algorithm may restart a scan - spontaneously. - - name: AfPauseStatePausing - value: 1 - description: | - Continuous AF has been sent an AfPauseDeferred control, and will - pause as soon as any in-progress scan completes (and then report - AfPauseStatePaused). No new scans will be start spontaneously until - the AfPauseResume control is sent. - - name: AfPauseStatePaused - value: 2 - description: | - Continuous AF is paused. No further state changes or lens movements - will occur until the AfPauseResume control is sent. - - # ---------------------------------------------------------------------------- - # Draft controls section - - - AePrecaptureTrigger: - type: int32_t - draft: true - description: | - Control for AE metering trigger. Currently identical to - ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER. - - Whether the camera device will trigger a precapture metering sequence - when it processes this request. - enum: - - name: AePrecaptureTriggerIdle - value: 0 - description: The trigger is idle. - - name: AePrecaptureTriggerStart - value: 1 - description: The pre-capture AE metering is started by the camera. - - name: AePrecaptureTriggerCancel - value: 2 - description: | - The camera will cancel any active or completed metering sequence. - The AE algorithm is reset to its initial state. - - - NoiseReductionMode: - type: int32_t - draft: true - description: | - Control to select the noise reduction algorithm mode. Currently - identical to ANDROID_NOISE_REDUCTION_MODE. - - Mode of operation for the noise reduction algorithm. - enum: - - name: NoiseReductionModeOff - value: 0 - description: No noise reduction is applied - - name: NoiseReductionModeFast - value: 1 - description: | - Noise reduction is applied without reducing the frame rate. - - name: NoiseReductionModeHighQuality - value: 2 - description: | - High quality noise reduction at the expense of frame rate. - - name: NoiseReductionModeMinimal - value: 3 - description: | - Minimal noise reduction is applied without reducing the frame rate. - - name: NoiseReductionModeZSL - value: 4 - description: | - Noise reduction is applied at different levels to different streams. - - - ColorCorrectionAberrationMode: - type: int32_t - draft: true - description: | - Control to select the color correction aberration mode. Currently - identical to ANDROID_COLOR_CORRECTION_ABERRATION_MODE. - - Mode of operation for the chromatic aberration correction algorithm. - enum: - - name: ColorCorrectionAberrationOff - value: 0 - description: No aberration correction is applied. - - name: ColorCorrectionAberrationFast - value: 1 - description: Aberration correction will not slow down the frame rate. - - name: ColorCorrectionAberrationHighQuality - value: 2 - description: | - High quality aberration correction which might reduce the frame - rate. - - - AeState: - type: int32_t - draft: true - description: | - Control to report the current AE algorithm state. Currently identical to - ANDROID_CONTROL_AE_STATE. - - Current state of the AE algorithm. - enum: - - name: AeStateInactive - value: 0 - description: The AE algorithm is inactive. - - name: AeStateSearching - value: 1 - description: The AE algorithm has not converged yet. - - name: AeStateConverged - value: 2 - description: The AE algorithm has converged. - - name: AeStateLocked - value: 3 - description: The AE algorithm is locked. - - name: AeStateFlashRequired - value: 4 - description: The AE algorithm would need a flash for good results - - name: AeStatePrecapture - value: 5 - description: | - The AE algorithm has started a pre-capture metering session. - \sa AePrecaptureTrigger - - - AwbState: - type: int32_t - draft: true - description: | - Control to report the current AWB algorithm state. Currently identical - to ANDROID_CONTROL_AWB_STATE. - - Current state of the AWB algorithm. - enum: - - name: AwbStateInactive - value: 0 - description: The AWB algorithm is inactive. - - name: AwbStateSearching - value: 1 - description: The AWB algorithm has not converged yet. - - name: AwbConverged - value: 2 - description: The AWB algorithm has converged. - - name: AwbLocked - value: 3 - description: The AWB algorithm is locked. - - - SensorRollingShutterSkew: - type: int64_t - draft: true - description: | - Control to report the time between the start of exposure of the first - row and the start of exposure of the last row. Currently identical to - ANDROID_SENSOR_ROLLING_SHUTTER_SKEW - - - LensShadingMapMode: - type: int32_t - draft: true - description: | - Control to report if the lens shading map is available. Currently - identical to ANDROID_STATISTICS_LENS_SHADING_MAP_MODE. - enum: - - name: LensShadingMapModeOff - value: 0 - description: No lens shading map mode is available. - - name: LensShadingMapModeOn - value: 1 - description: The lens shading map mode is available. - - - PipelineDepth: - type: int32_t - draft: true - description: | - Specifies the number of pipeline stages the frame went through from when - it was exposed to when the final completed result was available to the - framework. Always less than or equal to PipelineMaxDepth. Currently - identical to ANDROID_REQUEST_PIPELINE_DEPTH. - - The typical value for this control is 3 as a frame is first exposed, - captured and then processed in a single pass through the ISP. Any - additional processing step performed after the ISP pass (in example face - detection, additional format conversions etc) count as an additional - pipeline stage. - - - MaxLatency: - type: int32_t - draft: true - description: | - The maximum number of frames that can occur after a request (different - than the previous) has been submitted, and before the result's state - becomes synchronized. A value of -1 indicates unknown latency, and 0 - indicates per-frame control. Currently identical to - ANDROID_SYNC_MAX_LATENCY. - - - TestPatternMode: - type: int32_t - draft: true - description: | - Control to select the test pattern mode. Currently identical to - ANDROID_SENSOR_TEST_PATTERN_MODE. - enum: - - name: TestPatternModeOff - value: 0 - description: | - No test pattern mode is used. The camera device returns frames from - the image sensor. - - name: TestPatternModeSolidColor - value: 1 - description: | - Each pixel in [R, G_even, G_odd, B] is replaced by its respective - color channel provided in test pattern data. - \todo Add control for test pattern data. - - name: TestPatternModeColorBars - value: 2 - description: | - All pixel data is replaced with an 8-bar color pattern. The vertical - bars (left-to-right) are as follows; white, yellow, cyan, green, - magenta, red, blue and black. Each bar should take up 1/8 of the - sensor pixel array width. When this is not possible, the bar size - should be rounded down to the nearest integer and the pattern can - repeat on the right side. Each bar's height must always take up the - full sensor pixel array height. - - name: TestPatternModeColorBarsFadeToGray - value: 3 - description: | - The test pattern is similar to TestPatternModeColorBars, - except that each bar should start at its specified color at the top - and fade to gray at the bottom. Furthermore each bar is further - subdevided into a left and right half. The left half should have a - smooth gradient, and the right half should have a quantized - gradient. In particular, the right half's should consist of blocks - of the same color for 1/16th active sensor pixel array width. The - least significant bits in the quantized gradient should be copied - from the most significant bits of the smooth gradient. The height of - each bar should always be a multiple of 128. When this is not the - case, the pattern should repeat at the bottom of the image. - - name: TestPatternModePn9 - value: 4 - description: | - All pixel data is replaced by a pseudo-random sequence generated - from a PN9 512-bit sequence (typically implemented in hardware with - a linear feedback shift register). The generator should be reset at - the beginning of each frame, and thus each subsequent raw frame with - this test pattern should be exactly the same as the last. - - name: TestPatternModeCustom1 - value: 256 - description: | - The first custom test pattern. All custom patterns that are - available only on this camera device are at least this numeric - value. All of the custom test patterns will be static (that is the - raw image must not vary from frame to frame). - -... diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml new file mode 100644 index 00000000..aa744864 --- /dev/null +++ b/src/libcamera/control_ids_core.yaml @@ -0,0 +1,1271 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2019, Google Inc. +# +%YAML 1.1 +--- +# Unless otherwise stated, all controls are bi-directional, i.e. they can be +# set through Request::controls() and returned out through Request::metadata(). +vendor: libcamera +controls: + - AeEnable: + type: bool + direction: in + description: | + Enable or disable the AEGC algorithm. When this control is set to true, + both ExposureTimeMode and AnalogueGainMode are set to auto, and if this + control is set to false then both are set to manual. + + If ExposureTimeMode or AnalogueGainMode are also set in the same + request as AeEnable, then the modes supplied by ExposureTimeMode or + AnalogueGainMode will take precedence. + + \sa ExposureTimeMode AnalogueGainMode + + - AeState: + type: int32_t + direction: out + description: | + Report the AEGC algorithm state. + + The AEGC algorithm computes the exposure time and the analogue gain + to be applied to the image sensor. + + The AEGC algorithm behaviour is controlled by the ExposureTimeMode and + AnalogueGainMode controls, which allow applications to decide how + the exposure time and gain are computed, in Auto or Manual mode, + independently from one another. + + The AeState control reports the AEGC algorithm state through a single + value and describes it as a single computation block which computes + both the exposure time and the analogue gain values. + + When both the exposure time and analogue gain values are configured to + be in Manual mode, the AEGC algorithm is quiescent and does not actively + compute any value and the AeState control will report AeStateIdle. + + When at least the exposure time or analogue gain are configured to be + computed by the AEGC algorithm, the AeState control will report if the + algorithm has converged to stable values for all of the controls set + to be computed in Auto mode. + + \sa AnalogueGainMode + \sa ExposureTimeMode + + enum: + - name: AeStateIdle + value: 0 + description: | + The AEGC algorithm is inactive. + + This state is returned when both AnalogueGainMode and + ExposureTimeMode are set to Manual and the algorithm is not + actively computing any value. + - name: AeStateSearching + value: 1 + description: | + The AEGC algorithm is actively computing new values, for either the + exposure time or the analogue gain, but has not converged to a + stable result yet. + + This state is returned if at least one of AnalogueGainMode or + ExposureTimeMode is auto and the algorithm hasn't converged yet. + + The AEGC algorithm converges once stable values are computed for + all of the controls set to be computed in Auto mode. Once the + algorithm converges the state is moved to AeStateConverged. + - name: AeStateConverged + value: 2 + description: | + The AEGC algorithm has converged. + + This state is returned if at least one of AnalogueGainMode or + ExposureTimeMode is Auto, and the AEGC algorithm has converged to a + stable value. + + If the measurements move too far away from the convergence point + then the AEGC algorithm might start adjusting again, in which case + the state is moved to AeStateSearching. + + # AeMeteringMode needs further attention: + # - Auto-generate max enum value. + # - Better handling of custom types. + - AeMeteringMode: + type: int32_t + direction: inout + description: | + Specify a metering mode for the AE algorithm to use. + + The metering modes determine which parts of the image are used to + determine the scene brightness. Metering modes may be platform specific + and not all metering modes may be supported. + enum: + - name: MeteringCentreWeighted + value: 0 + description: Centre-weighted metering mode. + - name: MeteringSpot + value: 1 + description: Spot metering mode. + - name: MeteringMatrix + value: 2 + description: Matrix metering mode. + - name: MeteringCustom + value: 3 + description: Custom metering mode. + + # AeConstraintMode needs further attention: + # - Auto-generate max enum value. + # - Better handling of custom types. + - AeConstraintMode: + type: int32_t + direction: inout + description: | + Specify a constraint mode for the AE algorithm to use. + + The constraint modes determine how the measured scene brightness is + adjusted to reach the desired target exposure. Constraint modes may be + platform specific, and not all constraint modes may be supported. + enum: + - name: ConstraintNormal + value: 0 + description: | + Default constraint mode. + + This mode aims to balance the exposure of different parts of the + image so as to reach a reasonable average level. However, highlights + in the image may appear over-exposed and lowlights may appear + under-exposed. + - name: ConstraintHighlight + value: 1 + description: | + Highlight constraint mode. + + This mode adjusts the exposure levels in order to try and avoid + over-exposing the brightest parts (highlights) of an image. + Other non-highlight parts of the image may appear under-exposed. + - name: ConstraintShadows + value: 2 + description: | + Shadows constraint mode. + + This mode adjusts the exposure levels in order to try and avoid + under-exposing the dark parts (shadows) of an image. Other normally + exposed parts of the image may appear over-exposed. + - name: ConstraintCustom + value: 3 + description: | + Custom constraint mode. + + # AeExposureMode needs further attention: + # - Auto-generate max enum value. + # - Better handling of custom types. + - AeExposureMode: + type: int32_t + direction: inout + description: | + Specify an exposure mode for the AE algorithm to use. + + The exposure modes specify how the desired total exposure is divided + between the exposure time and the sensor's analogue gain. They are + platform specific, and not all exposure modes may be supported. + + When one of AnalogueGainMode or ExposureTimeMode is set to Manual, + the fixed values will override any choices made by AeExposureMode. + + \sa AnalogueGainMode + \sa ExposureTimeMode + + enum: + - name: ExposureNormal + value: 0 + description: Default exposure mode. + - name: ExposureShort + value: 1 + description: Exposure mode allowing only short exposure times. + - name: ExposureLong + value: 2 + description: Exposure mode allowing long exposure times. + - name: ExposureCustom + value: 3 + description: Custom exposure mode. + + - ExposureValue: + type: float + direction: inout + description: | + Specify an Exposure Value (EV) parameter. + + The EV parameter will only be applied if the AE algorithm is currently + enabled, that is, at least one of AnalogueGainMode and ExposureTimeMode + are in Auto mode. + + By convention EV adjusts the exposure as log2. For example + EV = [-2, -1, -0.5, 0, 0.5, 1, 2] results in an exposure adjustment + of [1/4x, 1/2x, 1/sqrt(2)x, 1x, sqrt(2)x, 2x, 4x]. + + \sa AnalogueGainMode + \sa ExposureTimeMode + + - ExposureTime: + type: int32_t + direction: inout + description: | + Exposure time for the frame applied in the sensor device. + + This value is specified in micro-seconds. + + This control will only take effect if ExposureTimeMode is Manual. If + this control is set when ExposureTimeMode is Auto, the value will be + ignored and will not be retained. + + When reported in metadata, this control indicates what exposure time + was used for the current frame, regardless of ExposureTimeMode. + ExposureTimeMode will indicate the source of the exposure time value, + whether it came from the AE algorithm or not. + + \sa AnalogueGain + \sa ExposureTimeMode + + - ExposureTimeMode: + type: int32_t + direction: inout + description: | + Controls the source of the exposure time that is applied to the image + sensor. + + When set to Auto, the AE algorithm computes the exposure time and + configures the image sensor accordingly. When set to Manual, the value + of the ExposureTime control is used. + + When transitioning from Auto to Manual mode and no ExposureTime control + is provided by the application, the last value computed by the AE + algorithm when the mode was Auto will be used. If the ExposureTimeMode + was never set to Auto (either because the camera started in Manual mode, + or Auto is not supported by the camera), the camera should use a + best-effort default value. + + If ExposureTimeModeManual is supported, the ExposureTime control must + also be supported. + + Cameras that support manual control of the sensor shall support manual + mode for both ExposureTimeMode and AnalogueGainMode, and shall expose + the ExposureTime and AnalogueGain controls. If the camera also has an + AEGC implementation, both ExposureTimeMode and AnalogueGainMode shall + support both manual and auto mode. If auto mode is available, it shall + be the default mode. These rules do not apply to black box cameras + such as UVC cameras, where the available gain and exposure modes are + completely dependent on what the device exposes. + + \par Flickerless exposure mode transitions + + Applications that wish to transition from ExposureTimeModeAuto to direct + control of the exposure time without causing extra flicker can do so by + selecting an ExposureTime value as close as possible to the last value + computed by the auto exposure algorithm in order to avoid any visible + flickering. + + To select the correct value to use as ExposureTime value, applications + should accommodate the natural delay in applying controls caused by the + capture pipeline frame depth. + + When switching to manual exposure mode, applications should not + immediately specify an ExposureTime value in the same request where + ExposureTimeMode is set to Manual. They should instead wait for the + first Request where ExposureTimeMode is reported as + ExposureTimeModeManual in the Request metadata, and use the reported + ExposureTime to populate the control value in the next Request to be + queued to the Camera. + + The implementation of the auto-exposure algorithm should equally try to + minimize flickering and when transitioning from manual exposure mode to + auto exposure use the last value provided by the application as starting + point. + + 1. Start with ExposureTimeMode set to Auto + + 2. Set ExposureTimeMode to Manual + + 3. Wait for the first completed request that has ExposureTimeMode + set to Manual + + 4. Copy the value reported in ExposureTime into a new request, and + submit it + + 5. Proceed to run manual exposure time as desired + + \sa ExposureTime + enum: + - name: ExposureTimeModeAuto + value: 0 + description: | + The exposure time will be calculated automatically and set by the + AE algorithm. + + If ExposureTime is set while this mode is active, it will be + ignored, and its value will not be retained. + + When transitioning from Manual to Auto mode, the AEGC should start + its adjustments based on the last set manual ExposureTime value. + - name: ExposureTimeModeManual + value: 1 + description: | + The exposure time will not be updated by the AE algorithm. + + When transitioning from Auto to Manual mode, the last computed + exposure value is used until a new value is specified through the + ExposureTime control. If an ExposureTime value is specified in the + same request where the ExposureTimeMode is changed from Auto to + Manual, the provided ExposureTime is applied immediately. + + - AnalogueGain: + type: float + direction: inout + description: | + Analogue gain value applied in the sensor device. + + The value of the control specifies the gain multiplier applied to all + colour channels. This value cannot be lower than 1.0. + + This control will only take effect if AnalogueGainMode is Manual. If + this control is set when AnalogueGainMode is Auto, the value will be + ignored and will not be retained. + + When reported in metadata, this control indicates what analogue gain + was used for the current request, regardless of AnalogueGainMode. + AnalogueGainMode will indicate the source of the analogue gain value, + whether it came from the AEGC algorithm or not. + + \sa ExposureTime + \sa AnalogueGainMode + + - AnalogueGainMode: + type: int32_t + direction: inout + description: | + Controls the source of the analogue gain that is applied to the image + sensor. + + When set to Auto, the AEGC algorithm computes the analogue gain and + configures the image sensor accordingly. When set to Manual, the value + of the AnalogueGain control is used. + + When transitioning from Auto to Manual mode and no AnalogueGain control + is provided by the application, the last value computed by the AEGC + algorithm when the mode was Auto will be used. If the AnalogueGainMode + was never set to Auto (either because the camera started in Manual mode, + or Auto is not supported by the camera), the camera should use a + best-effort default value. + + If AnalogueGainModeManual is supported, the AnalogueGain control must + also be supported. + + For cameras where we have control over the ISP, both ExposureTimeMode + and AnalogueGainMode are expected to support manual mode, and both + controls (as well as ExposureTimeMode and AnalogueGain) are expected to + be present. If the camera also has an AEGC implementation, both + ExposureTimeMode and AnalogueGainMode shall support both manual and + auto mode. If auto mode is available, it shall be the default mode. + These rules do not apply to black box cameras such as UVC cameras, + where the available gain and exposure modes are completely dependent on + what the hardware exposes. + + The same procedure described for performing flickerless transitions in + the ExposureTimeMode control documentation can be applied to analogue + gain. + + \sa ExposureTimeMode + \sa AnalogueGain + enum: + - name: AnalogueGainModeAuto + value: 0 + description: | + The analogue gain will be calculated automatically and set by the + AEGC algorithm. + + If AnalogueGain is set while this mode is active, it will be + ignored, and it will also not be retained. + + When transitioning from Manual to Auto mode, the AEGC should start + its adjustments based on the last set manual AnalogueGain value. + - name: AnalogueGainModeManual + value: 1 + description: | + The analogue gain will not be updated by the AEGC algorithm. + + When transitioning from Auto to Manual mode, the last computed + gain value is used until a new value is specified through the + AnalogueGain control. If an AnalogueGain value is specified in the + same request where the AnalogueGainMode is changed from Auto to + Manual, the provided AnalogueGain is applied immediately. + + - AeFlickerMode: + type: int32_t + direction: inout + description: | + Set the flicker avoidance mode for AGC/AEC. + + The flicker mode determines whether, and how, the AGC/AEC algorithm + attempts to hide flicker effects caused by the duty cycle of artificial + lighting. + + Although implementation dependent, many algorithms for "flicker + avoidance" work by restricting this exposure time to integer multiples + of the cycle period, wherever possible. + + Implementations may not support all of the flicker modes listed below. + + By default the system will start in FlickerAuto mode if this is + supported, otherwise the flicker mode will be set to FlickerOff. + + enum: + - name: FlickerOff + value: 0 + description: | + No flicker avoidance is performed. + - name: FlickerManual + value: 1 + description: | + Manual flicker avoidance. + + Suppress flicker effects caused by lighting running with a period + specified by the AeFlickerPeriod control. + \sa AeFlickerPeriod + - name: FlickerAuto + value: 2 + description: | + Automatic flicker period detection and avoidance. + + The system will automatically determine the most likely value of + flicker period, and avoid flicker of this frequency. Once flicker + is being corrected, it is implementation dependent whether the + system is still able to detect a change in the flicker period. + \sa AeFlickerDetected + + - AeFlickerPeriod: + type: int32_t + direction: inout + description: | + Manual flicker period in microseconds. + + This value sets the current flicker period to avoid. It is used when + AeFlickerMode is set to FlickerManual. + + To cancel 50Hz mains flicker, this should be set to 10000 (corresponding + to 100Hz), or 8333 (120Hz) for 60Hz mains. + + Setting the mode to FlickerManual when no AeFlickerPeriod has ever been + set means that no flicker cancellation occurs (until the value of this + control is updated). + + Switching to modes other than FlickerManual has no effect on the + value of the AeFlickerPeriod control. + + \sa AeFlickerMode + + - AeFlickerDetected: + type: int32_t + direction: out + description: | + Flicker period detected in microseconds. + + The value reported here indicates the currently detected flicker + period, or zero if no flicker at all is detected. + + When AeFlickerMode is set to FlickerAuto, there may be a period during + which the value reported here remains zero. Once a non-zero value is + reported, then this is the flicker period that has been detected and is + now being cancelled. + + In the case of 50Hz mains flicker, the value would be 10000 + (corresponding to 100Hz), or 8333 (120Hz) for 60Hz mains flicker. + + It is implementation dependent whether the system can continue to detect + flicker of different periods when another frequency is already being + cancelled. + + \sa AeFlickerMode + + - Brightness: + type: float + direction: inout + description: | + Specify a fixed brightness parameter. + + Positive values (up to 1.0) produce brighter images; negative values + (up to -1.0) produce darker images and 0.0 leaves pixels unchanged. + + - Contrast: + type: float + direction: inout + description: | + Specify a fixed contrast parameter. + + Normal contrast is given by the value 1.0; larger values produce images + with more contrast. + + - Lux: + type: float + direction: out + description: | + Report an estimate of the current illuminance level in lux. + + The Lux control can only be returned in metadata. + + - AwbEnable: + type: bool + direction: inout + description: | + Enable or disable the AWB. + + When AWB is enabled, the algorithm estimates the colour temperature of + the scene and computes colour gains and the colour correction matrix + automatically. The computed colour temperature, gains and correction + matrix are reported in metadata. The corresponding controls are ignored + if set in a request. + + When AWB is disabled, the colour temperature, gains and correction + matrix are not updated automatically and can be set manually in + requests. + + \sa ColourCorrectionMatrix + \sa ColourGains + \sa ColourTemperature + + # AwbMode needs further attention: + # - Auto-generate max enum value. + # - Better handling of custom types. + - AwbMode: + type: int32_t + direction: inout + description: | + Specify the range of illuminants to use for the AWB algorithm. + + The modes supported are platform specific, and not all modes may be + supported. + enum: + - name: AwbAuto + value: 0 + description: Search over the whole colour temperature range. + - name: AwbIncandescent + value: 1 + description: Incandescent AWB lamp mode. + - name: AwbTungsten + value: 2 + description: Tungsten AWB lamp mode. + - name: AwbFluorescent + value: 3 + description: Fluorescent AWB lamp mode. + - name: AwbIndoor + value: 4 + description: Indoor AWB lighting mode. + - name: AwbDaylight + value: 5 + description: Daylight AWB lighting mode. + - name: AwbCloudy + value: 6 + description: Cloudy AWB lighting mode. + - name: AwbCustom + value: 7 + description: Custom AWB mode. + + - AwbLocked: + type: bool + direction: out + description: | + Report the lock status of a running AWB algorithm. + + If the AWB algorithm is locked the value shall be set to true, if it's + converging it shall be set to false. If the AWB algorithm is not + running the control shall not be present in the metadata control list. + + \sa AwbEnable + + - ColourGains: + type: float + direction: inout + description: | + Pair of gain values for the Red and Blue colour channels, in that + order. + + ColourGains can only be applied in a Request when the AWB is disabled. + If ColourGains is set in a request but ColourTemperature is not, the + implementation shall calculate and set the ColourTemperature based on + the ColourGains. + + \sa AwbEnable + \sa ColourTemperature + size: [2] + + - ColourTemperature: + type: int32_t + direction: out + description: | + ColourTemperature of the frame, in kelvin. + + ColourTemperature can only be applied in a Request when the AWB is + disabled. + + If ColourTemperature is set in a request but ColourGains is not, the + implementation shall calculate and set the ColourGains based on the + given ColourTemperature. If ColourTemperature is set (either directly, + or indirectly by setting ColourGains) but ColourCorrectionMatrix is not, + the ColourCorrectionMatrix is updated based on the ColourTemperature. + + The ColourTemperature used to process the frame is reported in metadata. + + \sa AwbEnable + \sa ColourCorrectionMatrix + \sa ColourGains + + - Saturation: + type: float + direction: inout + description: | + Specify a fixed saturation parameter. + + Normal saturation is given by the value 1.0; larger values produce more + saturated colours; 0.0 produces a greyscale image. + + - SensorBlackLevels: + type: int32_t + direction: out + description: | + Reports the sensor black levels used for processing a frame. + + The values are in the order R, Gr, Gb, B. They are returned as numbers + out of a 16-bit pixel range (as if pixels ranged from 0 to 65535). The + SensorBlackLevels control can only be returned in metadata. + size: [4] + + - Sharpness: + type: float + direction: inout + description: | + Intensity of the sharpening applied to the image. + + A value of 0.0 means no sharpening. The minimum value means + minimal sharpening, and shall be 0.0 unless the camera can't + disable sharpening completely. The default value shall give a + "reasonable" level of sharpening, suitable for most use cases. + The maximum value may apply extremely high levels of sharpening, + higher than anyone could reasonably want. Negative values are + not allowed. Note also that sharpening is not applied to raw + streams. + + - FocusFoM: + type: int32_t + direction: out + description: | + Reports a Figure of Merit (FoM) to indicate how in-focus the frame is. + + A larger FocusFoM value indicates a more in-focus frame. This singular + value may be based on a combination of statistics gathered from + multiple focus regions within an image. The number of focus regions and + method of combination is platform dependent. In this respect, it is not + necessarily aimed at providing a way to implement a focus algorithm by + the application, rather an indication of how in-focus a frame is. + + - ColourCorrectionMatrix: + type: float + direction: inout + description: | + The 3x3 matrix that converts camera RGB to sRGB within the imaging + pipeline. + + This should describe the matrix that is used after pixels have been + white-balanced, but before any gamma transformation. The 3x3 matrix is + stored in conventional reading order in an array of 9 floating point + values. + + ColourCorrectionMatrix can only be applied in a Request when the AWB is + disabled. + + \sa AwbEnable + \sa ColourTemperature + size: [3,3] + + - ScalerCrop: + type: Rectangle + direction: inout + description: | + Sets the image portion that will be scaled to form the whole of + the final output image. + + The (x,y) location of this rectangle is relative to the + PixelArrayActiveAreas that is being used. The units remain native + sensor pixels, even if the sensor is being used in a binning or + skipping mode. + + This control is only present when the pipeline supports scaling. Its + maximum valid value is given by the properties::ScalerCropMaximum + property, and the two can be used to implement digital zoom. + + - DigitalGain: + type: float + direction: inout + description: | + Digital gain value applied during the processing steps applied + to the image as captured from the sensor. + + The global digital gain factor is applied to all the colour channels + of the RAW image. Different pipeline models are free to + specify how the global gain factor applies to each separate + channel. + + If an imaging pipeline applies digital gain in distinct + processing steps, this value indicates their total sum. + Pipelines are free to decide how to adjust each processing + step to respect the received gain factor and shall report + their total value in the request metadata. + + - FrameDuration: + type: int64_t + direction: out + description: | + The instantaneous frame duration from start of frame exposure to start + of next exposure, expressed in microseconds. + + This control is meant to be returned in metadata. + + - FrameDurationLimits: + type: int64_t + direction: inout + description: | + The minimum and maximum (in that order) frame duration, expressed in + microseconds. + + When provided by applications, the control specifies the sensor frame + duration interval the pipeline has to use. This limits the largest + exposure time the sensor can use. For example, if a maximum frame + duration of 33ms is requested (corresponding to 30 frames per second), + the sensor will not be able to raise the exposure time above 33ms. + A fixed frame duration is achieved by setting the minimum and maximum + values to be the same. Setting both values to 0 reverts to using the + camera defaults. + + The maximum frame duration provides the absolute limit to the exposure + time computed by the AE algorithm and it overrides any exposure mode + setting specified with controls::AeExposureMode. Similarly, when a + manual exposure time is set through controls::ExposureTime, it also + gets clipped to the limits set by this control. When reported in + metadata, the control expresses the minimum and maximum frame durations + used after being clipped to the sensor provided frame duration limits. + + \sa AeExposureMode + \sa ExposureTime + + \todo Define how to calculate the capture frame rate by + defining controls to report additional delays introduced by + the capture pipeline or post-processing stages (ie JPEG + conversion, frame scaling). + + \todo Provide an explicit definition of default control values, for + this and all other controls. + + size: [2] + + - SensorTemperature: + type: float + direction: out + description: | + Temperature measure from the camera sensor in Celsius. + + This value is typically obtained by a thermal sensor present on-die or + in the camera module. The range of reported temperatures is device + dependent. + + The SensorTemperature control will only be returned in metadata if a + thermal sensor is present. + + - SensorTimestamp: + type: int64_t + direction: out + description: | + The time when the first row of the image sensor active array is exposed. + + The timestamp, expressed in nanoseconds, represents a monotonically + increasing counter since the system boot time, as defined by the + Linux-specific CLOCK_BOOTTIME clock id. + + The SensorTimestamp control can only be returned in metadata. + + \todo Define how the sensor timestamp has to be used in the reprocessing + use case. + + - AfMode: + type: int32_t + direction: inout + description: | + The mode of the AF (autofocus) algorithm. + + An implementation may choose not to implement all the modes. + + enum: + - name: AfModeManual + value: 0 + description: | + The AF algorithm is in manual mode. + + In this mode it will never perform any action nor move the lens of + its own accord, but an application can specify the desired lens + position using the LensPosition control. The AfState will always + report AfStateIdle. + + If the camera is started in AfModeManual, it will move the focus + lens to the position specified by the LensPosition control. + + This mode is the recommended default value for the AfMode control. + External cameras (as reported by the Location property set to + CameraLocationExternal) may use a different default value. + - name: AfModeAuto + value: 1 + description: | + The AF algorithm is in auto mode. + + In this mode the algorithm will never move the lens or change state + unless the AfTrigger control is used. The AfTrigger control can be + used to initiate a focus scan, the results of which will be + reported by AfState. + + If the autofocus algorithm is moved from AfModeAuto to another mode + while a scan is in progress, the scan is cancelled immediately, + without waiting for the scan to finish. + + When first entering this mode the AfState will report AfStateIdle. + When a trigger control is sent, AfState will report AfStateScanning + for a period before spontaneously changing to AfStateFocused or + AfStateFailed, depending on the outcome of the scan. It will remain + in this state until another scan is initiated by the AfTrigger + control. If a scan is cancelled (without changing to another mode), + AfState will return to AfStateIdle. + - name: AfModeContinuous + value: 2 + description: | + The AF algorithm is in continuous mode. + + In this mode the lens can re-start a scan spontaneously at any + moment, without any user intervention. The AfState still reports + whether the algorithm is currently scanning or not, though the + application has no ability to initiate or cancel scans, nor to move + the lens for itself. + + However, applications can pause the AF algorithm from continuously + scanning by using the AfPause control. This allows video or still + images to be captured whilst guaranteeing that the focus is fixed. + + When set to AfModeContinuous, the system will immediately initiate a + scan so AfState will report AfStateScanning, and will settle on one + of AfStateFocused or AfStateFailed, depending on the scan result. + + - AfRange: + type: int32_t + direction: inout + description: | + The range of focus distances that is scanned. + + An implementation may choose not to implement all the options here. + enum: + - name: AfRangeNormal + value: 0 + description: | + A wide range of focus distances is scanned. + + Scanned distances cover all the way from infinity down to close + distances, though depending on the implementation, possibly not + including the very closest macro positions. + - name: AfRangeMacro + value: 1 + description: | + Only close distances are scanned. + - name: AfRangeFull + value: 2 + description: | + The full range of focus distances is scanned. + + This range is similar to AfRangeNormal but includes the very + closest macro positions. + + - AfSpeed: + type: int32_t + direction: inout + description: | + Determine whether the AF is to move the lens as quickly as possible or + more steadily. + + For example, during video recording it may be desirable not to move the + lens too abruptly, but when in a preview mode (waiting for a still + capture) it may be helpful to move the lens as quickly as is reasonably + possible. + enum: + - name: AfSpeedNormal + value: 0 + description: Move the lens at its usual speed. + - name: AfSpeedFast + value: 1 + description: Move the lens more quickly. + + - AfMetering: + type: int32_t + direction: inout + description: | + The parts of the image used by the AF algorithm to measure focus. + enum: + - name: AfMeteringAuto + value: 0 + description: | + Let the AF algorithm decide for itself where it will measure focus. + - name: AfMeteringWindows + value: 1 + description: | + Use the rectangles defined by the AfWindows control to measure focus. + + If no windows are specified the behaviour is platform dependent. + + - AfWindows: + type: Rectangle + direction: inout + description: | + The focus windows used by the AF algorithm when AfMetering is set to + AfMeteringWindows. + + The units used are pixels within the rectangle returned by the + ScalerCropMaximum property. + + In order to be activated, a rectangle must be programmed with non-zero + width and height. Internally, these rectangles are intersected with the + ScalerCropMaximum rectangle. If the window becomes empty after this + operation, then the window is ignored. If all the windows end up being + ignored, then the behaviour is platform dependent. + + On platforms that support the ScalerCrop control (for implementing + digital zoom, for example), no automatic recalculation or adjustment of + AF windows is performed internally if the ScalerCrop is changed. If any + window lies outside the output image after the scaler crop has been + applied, it is up to the application to recalculate them. + + The details of how the windows are used are platform dependent. We note + that when there is more than one AF window, a typical implementation + might find the optimal focus position for each one and finally select + the window where the focal distance for the objects shown in that part + of the image are closest to the camera. + + size: [n] + + - AfTrigger: + type: int32_t + direction: in + description: | + Start an autofocus scan. + + This control starts an autofocus scan when AfMode is set to AfModeAuto, + and is ignored if AfMode is set to AfModeManual or AfModeContinuous. It + can also be used to terminate a scan early. + + enum: + - name: AfTriggerStart + value: 0 + description: | + Start an AF scan. + + Setting the control to AfTriggerStart is ignored if a scan is in + progress. + - name: AfTriggerCancel + value: 1 + description: | + Cancel an AF scan. + + This does not cause the lens to move anywhere else. Ignored if no + scan is in progress. + + - AfPause: + type: int32_t + direction: in + description: | + Pause lens movements when in continuous autofocus mode. + + This control has no effect except when in continuous autofocus mode + (AfModeContinuous). It can be used to pause any lens movements while + (for example) images are captured. The algorithm remains inactive + until it is instructed to resume. + + enum: + - name: AfPauseImmediate + value: 0 + description: | + Pause the continuous autofocus algorithm immediately. + + The autofocus algorithm is paused whether or not any kind of scan + is underway. AfPauseState will subsequently report + AfPauseStatePaused. AfState may report any of AfStateScanning, + AfStateFocused or AfStateFailed, depending on the algorithm's state + when it received this control. + - name: AfPauseDeferred + value: 1 + description: | + Pause the continuous autofocus algorithm at the end of the scan. + + This is similar to AfPauseImmediate, and if the AfState is + currently reporting AfStateFocused or AfStateFailed it will remain + in that state and AfPauseState will report AfPauseStatePaused. + + However, if the algorithm is scanning (AfStateScanning), + AfPauseState will report AfPauseStatePausing until the scan is + finished, at which point AfState will report one of AfStateFocused + or AfStateFailed, and AfPauseState will change to + AfPauseStatePaused. + + - name: AfPauseResume + value: 2 + description: | + Resume continuous autofocus operation. + + The algorithm starts again from exactly where it left off, and + AfPauseState will report AfPauseStateRunning. + + - LensPosition: + type: float + direction: inout + description: | + Set and report the focus lens position. + + This control instructs the lens to move to a particular position and + also reports back the position of the lens for each frame. + + The LensPosition control is ignored unless the AfMode is set to + AfModeManual, though the value is reported back unconditionally in all + modes. + + This value, which is generally a non-integer, is the reciprocal of the + focal distance in metres, also known as dioptres. That is, to set a + focal distance D, the lens position LP is given by + + \f$LP = \frac{1\mathrm{m}}{D}\f$ + + For example: + + - 0 moves the lens to infinity. + - 0.5 moves the lens to focus on objects 2m away. + - 2 moves the lens to focus on objects 50cm away. + - And larger values will focus the lens closer. + + The default value of the control should indicate a good general + position for the lens, often corresponding to the hyperfocal distance + (the closest position for which objects at infinity are still + acceptably sharp). The minimum will often be zero (meaning infinity), + and the maximum value defines the closest focus position. + + \todo Define a property to report the Hyperfocal distance of calibrated + lenses. + + - AfState: + type: int32_t + direction: out + description: | + The current state of the AF algorithm. + + This control reports the current state of the AF algorithm in + conjunction with the reported AfMode value and (in continuous AF mode) + the AfPauseState value. The possible state changes are described below, + though we note the following state transitions that occur when the + AfMode is changed. + + If the AfMode is set to AfModeManual, then the AfState will always + report AfStateIdle (even if the lens is subsequently moved). Changing + to the AfModeManual state does not initiate any lens movement. + + If the AfMode is set to AfModeAuto then the AfState will report + AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent + together then AfState will omit AfStateIdle and move straight to + AfStateScanning (and start a scan). + + If the AfMode is set to AfModeContinuous then the AfState will + initially report AfStateScanning. + + enum: + - name: AfStateIdle + value: 0 + description: | + The AF algorithm is in manual mode (AfModeManual) or in auto mode + (AfModeAuto) and a scan has not yet been triggered, or an + in-progress scan was cancelled. + - name: AfStateScanning + value: 1 + description: | + The AF algorithm is in auto mode (AfModeAuto), and a scan has been + started using the AfTrigger control. + + The scan can be cancelled by sending AfTriggerCancel at which point + the algorithm will either move back to AfStateIdle or, if the scan + actually completes before the cancel request is processed, to one + of AfStateFocused or AfStateFailed. + + Alternatively the AF algorithm could be in continuous mode + (AfModeContinuous) at which point it may enter this state + spontaneously whenever it determines that a rescan is needed. + - name: AfStateFocused + value: 2 + description: | + The AF algorithm is in auto (AfModeAuto) or continuous + (AfModeContinuous) mode and a scan has completed with the result + that the algorithm believes the image is now in focus. + - name: AfStateFailed + value: 3 + description: | + The AF algorithm is in auto (AfModeAuto) or continuous + (AfModeContinuous) mode and a scan has completed with the result + that the algorithm did not find a good focus position. + + - AfPauseState: + type: int32_t + direction: out + description: | + Report whether the autofocus is currently running, paused or pausing. + + This control is only applicable in continuous (AfModeContinuous) mode, + and reports whether the algorithm is currently running, paused or + pausing (that is, will pause as soon as any in-progress scan + completes). + + Any change to AfMode will cause AfPauseStateRunning to be reported. + + enum: + - name: AfPauseStateRunning + value: 0 + description: | + Continuous AF is running and the algorithm may restart a scan + spontaneously. + - name: AfPauseStatePausing + value: 1 + description: | + Continuous AF has been sent an AfPauseDeferred control, and will + pause as soon as any in-progress scan completes. + + When the scan completes, the AfPauseState control will report + AfPauseStatePaused. No new scans will be start spontaneously until + the AfPauseResume control is sent. + - name: AfPauseStatePaused + value: 2 + description: | + Continuous AF is paused. + + No further state changes or lens movements will occur until the + AfPauseResume control is sent. + + - HdrMode: + type: int32_t + direction: inout + description: | + Set the mode to be used for High Dynamic Range (HDR) imaging. + + HDR techniques typically include multiple exposure, image fusion and + tone mapping techniques to improve the dynamic range of the resulting + images. + + When using an HDR mode, images are captured with different sets of AGC + settings called HDR channels. Channels indicate in particular the type + of exposure (short, medium or long) used to capture the raw image, + before fusion. Each HDR image is tagged with the corresponding channel + using the HdrChannel control. + + \sa HdrChannel + + enum: + - name: HdrModeOff + value: 0 + description: | + HDR is disabled. + + Metadata for this frame will not include the HdrChannel control. + - name: HdrModeMultiExposureUnmerged + value: 1 + description: | + Multiple exposures will be generated in an alternating fashion. + + The multiple exposures will not be merged together and will be + returned to the application as they are. Each image will be tagged + with the correct HDR channel, indicating what kind of exposure it + is. The tag should be the same as in the HdrModeMultiExposure case. + + The expectation is that an application using this mode would merge + the frames to create HDR images for itself if it requires them. + - name: HdrModeMultiExposure + value: 2 + description: | + Multiple exposures will be generated and merged to create HDR + images. + + Each image will be tagged with the HDR channel (long, medium or + short) that arrived and which caused this image to be output. + + Systems that use two channels for HDR will return images tagged + alternately as the short and long channel. Systems that use three + channels for HDR will cycle through the short, medium and long + channel before repeating. + - name: HdrModeSingleExposure + value: 3 + description: | + Multiple frames all at a single exposure will be used to create HDR + images. + + These images should be reported as all corresponding to the HDR + short channel. + - name: HdrModeNight + value: 4 + description: | + Multiple frames will be combined to produce "night mode" images. + + It is up to the implementation exactly which HDR channels it uses, + and the images will all be tagged accordingly with the correct HDR + channel information. + + - HdrChannel: + type: int32_t + direction: out + description: | + The HDR channel used to capture the frame. + + This value is reported back to the application so that it can discover + whether this capture corresponds to the short or long exposure image + (or any other image used by the HDR procedure). An application can + monitor the HDR channel to discover when the differently exposed images + have arrived. + + This metadata is only available when an HDR mode has been enabled. + + \sa HdrMode + + enum: + - name: HdrChannelNone + value: 0 + description: | + This image does not correspond to any of the captures used to create + an HDR image. + - name: HdrChannelShort + value: 1 + description: | + This is a short exposure image. + - name: HdrChannelMedium + value: 2 + description: | + This is a medium exposure image. + - name: HdrChannelLong + value: 3 + description: | + This is a long exposure image. + + - Gamma: + type: float + direction: inout + description: | + Specify a fixed gamma value. + + The default gamma value must be 2.2 which closely mimics sRGB gamma. + Note that this is camera gamma, so it is applied as 1.0/gamma. + + - DebugMetadataEnable: + type: bool + direction: inout + description: | + Enable or disable the debug metadata. + +... diff --git a/src/libcamera/control_ids_debug.yaml b/src/libcamera/control_ids_debug.yaml new file mode 100644 index 00000000..79753271 --- /dev/null +++ b/src/libcamera/control_ids_debug.yaml @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +%YAML 1.1 +--- +vendor: debug +controls: [] diff --git a/src/libcamera/control_ids_draft.yaml b/src/libcamera/control_ids_draft.yaml new file mode 100644 index 00000000..03309eea --- /dev/null +++ b/src/libcamera/control_ids_draft.yaml @@ -0,0 +1,297 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2019, Google Inc. +# +%YAML 1.1 +--- +# Unless otherwise stated, all controls are bi-directional, i.e. they can be +# set through Request::controls() and returned out through Request::metadata(). +vendor: draft +controls: + - AePrecaptureTrigger: + type: int32_t + direction: inout + description: | + Control for AE metering trigger. Currently identical to + ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER. + + Whether the camera device will trigger a precapture metering sequence + when it processes this request. + enum: + - name: AePrecaptureTriggerIdle + value: 0 + description: The trigger is idle. + - name: AePrecaptureTriggerStart + value: 1 + description: The pre-capture AE metering is started by the camera. + - name: AePrecaptureTriggerCancel + value: 2 + description: | + The camera will cancel any active or completed metering sequence. + The AE algorithm is reset to its initial state. + + - NoiseReductionMode: + type: int32_t + direction: inout + description: | + Control to select the noise reduction algorithm mode. Currently + identical to ANDROID_NOISE_REDUCTION_MODE. + + Mode of operation for the noise reduction algorithm. + enum: + - name: NoiseReductionModeOff + value: 0 + description: No noise reduction is applied + - name: NoiseReductionModeFast + value: 1 + description: | + Noise reduction is applied without reducing the frame rate. + - name: NoiseReductionModeHighQuality + value: 2 + description: | + High quality noise reduction at the expense of frame rate. + - name: NoiseReductionModeMinimal + value: 3 + description: | + Minimal noise reduction is applied without reducing the frame rate. + - name: NoiseReductionModeZSL + value: 4 + description: | + Noise reduction is applied at different levels to different streams. + + - ColorCorrectionAberrationMode: + type: int32_t + direction: inout + description: | + Control to select the color correction aberration mode. Currently + identical to ANDROID_COLOR_CORRECTION_ABERRATION_MODE. + + Mode of operation for the chromatic aberration correction algorithm. + enum: + - name: ColorCorrectionAberrationOff + value: 0 + description: No aberration correction is applied. + - name: ColorCorrectionAberrationFast + value: 1 + description: Aberration correction will not slow down the frame rate. + - name: ColorCorrectionAberrationHighQuality + value: 2 + description: | + High quality aberration correction which might reduce the frame + rate. + + - AwbState: + type: int32_t + direction: out + description: | + Control to report the current AWB algorithm state. Currently identical + to ANDROID_CONTROL_AWB_STATE. + + Current state of the AWB algorithm. + enum: + - name: AwbStateInactive + value: 0 + description: The AWB algorithm is inactive. + - name: AwbStateSearching + value: 1 + description: The AWB algorithm has not converged yet. + - name: AwbConverged + value: 2 + description: The AWB algorithm has converged. + - name: AwbLocked + value: 3 + description: The AWB algorithm is locked. + + - SensorRollingShutterSkew: + type: int64_t + direction: out + description: | + Control to report the time between the start of exposure of the first + row and the start of exposure of the last row. Currently identical to + ANDROID_SENSOR_ROLLING_SHUTTER_SKEW + + - LensShadingMapMode: + type: int32_t + direction: inout + description: | + Control to report if the lens shading map is available. Currently + identical to ANDROID_STATISTICS_LENS_SHADING_MAP_MODE. + enum: + - name: LensShadingMapModeOff + value: 0 + description: No lens shading map mode is available. + - name: LensShadingMapModeOn + value: 1 + description: The lens shading map mode is available. + + - PipelineDepth: + type: int32_t + direction: out + description: | + Specifies the number of pipeline stages the frame went through from when + it was exposed to when the final completed result was available to the + framework. Always less than or equal to PipelineMaxDepth. Currently + identical to ANDROID_REQUEST_PIPELINE_DEPTH. + + The typical value for this control is 3 as a frame is first exposed, + captured and then processed in a single pass through the ISP. Any + additional processing step performed after the ISP pass (in example face + detection, additional format conversions etc) count as an additional + pipeline stage. + + - MaxLatency: + type: int32_t + direction: out + description: | + The maximum number of frames that can occur after a request (different + than the previous) has been submitted, and before the result's state + becomes synchronized. A value of -1 indicates unknown latency, and 0 + indicates per-frame control. Currently identical to + ANDROID_SYNC_MAX_LATENCY. + + - TestPatternMode: + type: int32_t + direction: inout + description: | + Control to select the test pattern mode. Currently identical to + ANDROID_SENSOR_TEST_PATTERN_MODE. + enum: + - name: TestPatternModeOff + value: 0 + description: | + No test pattern mode is used. The camera device returns frames from + the image sensor. + - name: TestPatternModeSolidColor + value: 1 + description: | + Each pixel in [R, G_even, G_odd, B] is replaced by its respective + color channel provided in test pattern data. + \todo Add control for test pattern data. + - name: TestPatternModeColorBars + value: 2 + description: | + All pixel data is replaced with an 8-bar color pattern. The vertical + bars (left-to-right) are as follows; white, yellow, cyan, green, + magenta, red, blue and black. Each bar should take up 1/8 of the + sensor pixel array width. When this is not possible, the bar size + should be rounded down to the nearest integer and the pattern can + repeat on the right side. Each bar's height must always take up the + full sensor pixel array height. + - name: TestPatternModeColorBarsFadeToGray + value: 3 + description: | + The test pattern is similar to TestPatternModeColorBars, + except that each bar should start at its specified color at the top + and fade to gray at the bottom. Furthermore each bar is further + subdevided into a left and right half. The left half should have a + smooth gradient, and the right half should have a quantized + gradient. In particular, the right half's should consist of blocks + of the same color for 1/16th active sensor pixel array width. The + least significant bits in the quantized gradient should be copied + from the most significant bits of the smooth gradient. The height of + each bar should always be a multiple of 128. When this is not the + case, the pattern should repeat at the bottom of the image. + - name: TestPatternModePn9 + value: 4 + description: | + All pixel data is replaced by a pseudo-random sequence generated + from a PN9 512-bit sequence (typically implemented in hardware with + a linear feedback shift register). The generator should be reset at + the beginning of each frame, and thus each subsequent raw frame with + this test pattern should be exactly the same as the last. + - name: TestPatternModeCustom1 + value: 256 + description: | + The first custom test pattern. All custom patterns that are + available only on this camera device are at least this numeric + value. All of the custom test patterns will be static (that is the + raw image must not vary from frame to frame). + + - FaceDetectMode: + type: int32_t + direction: inout + description: | + Control to select the face detection mode used by the pipeline. + + Currently identical to ANDROID_STATISTICS_FACE_DETECT_MODE. + + \sa FaceDetectFaceRectangles + \sa FaceDetectFaceScores + \sa FaceDetectFaceLandmarks + \sa FaceDetectFaceIds + + enum: + - name: FaceDetectModeOff + value: 0 + description: | + Pipeline doesn't perform face detection and doesn't report any + control related to face detection. + - name: FaceDetectModeSimple + value: 1 + description: | + Pipeline performs face detection and reports the + FaceDetectFaceRectangles and FaceDetectFaceScores controls for each + detected face. FaceDetectFaceLandmarks and FaceDetectFaceIds are + optional. + - name: FaceDetectModeFull + value: 2 + description: | + Pipeline performs face detection and reports all the controls + related to face detection including FaceDetectFaceRectangles, + FaceDetectFaceScores, FaceDetectFaceLandmarks, and + FaceDeteceFaceIds for each detected face. + + - FaceDetectFaceRectangles: + type: Rectangle + direction: out + description: | + Boundary rectangles of the detected faces. The number of values is + the number of detected faces. + + The FaceDetectFaceRectangles control can only be returned in metadata. + + Currently identical to ANDROID_STATISTICS_FACE_RECTANGLES. + size: [n] + + - FaceDetectFaceScores: + type: uint8_t + direction: out + description: | + Confidence score of each of the detected faces. The range of score is + [0, 100]. The number of values should be the number of faces reported + in FaceDetectFaceRectangles. + + The FaceDetectFaceScores control can only be returned in metadata. + + Currently identical to ANDROID_STATISTICS_FACE_SCORES. + size: [n] + + - FaceDetectFaceLandmarks: + type: Point + direction: out + description: | + Array of human face landmark coordinates in format [..., left_eye_i, + right_eye_i, mouth_i, left_eye_i+1, ...], with i = index of face. The + number of values should be 3 * the number of faces reported in + FaceDetectFaceRectangles. + + The FaceDetectFaceLandmarks control can only be returned in metadata. + + Currently identical to ANDROID_STATISTICS_FACE_LANDMARKS. + size: [n] + + - FaceDetectFaceIds: + type: int32_t + direction: out + description: | + Each detected face is given a unique ID that is valid for as long as the + face is visible to the camera device. A face that leaves the field of + view and later returns may be assigned a new ID. The number of values + should be the number of faces reported in FaceDetectFaceRectangles. + + The FaceDetectFaceIds control can only be returned in metadata. + + Currently identical to ANDROID_STATISTICS_FACE_IDS. + size: [n] + +... diff --git a/src/libcamera/control_ids_rpi.yaml b/src/libcamera/control_ids_rpi.yaml new file mode 100644 index 00000000..8d1e8b47 --- /dev/null +++ b/src/libcamera/control_ids_rpi.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2023, Raspberry Pi Ltd +# +%YAML 1.1 +--- +# Raspberry Pi (VC4 and PiSP) specific vendor controls +vendor: rpi +controls: + - StatsOutputEnable: + type: bool + direction: inout + description: | + Toggles the Raspberry Pi IPA to output the hardware generated statistics. + + When this control is set to true, the IPA outputs a binary dump of the + hardware generated statistics through the Request metadata in the + Bcm2835StatsOutput control. + + \sa Bcm2835StatsOutput + + - Bcm2835StatsOutput: + type: uint8_t + size: [n] + direction: out + description: | + Span of the BCM2835 ISP generated statistics for the current frame. + + This is sent in the Request metadata if the StatsOutputEnable is set to + true. The statistics struct definition can be found in + include/linux/bcm2835-isp.h. + + \sa StatsOutputEnable + + - ScalerCrops: + type: Rectangle + size: [n] + direction: out + description: | + An array of rectangles, where each singular value has identical + functionality to the ScalerCrop control. This control allows the + Raspberry Pi pipeline handler to control individual scaler crops per + output stream. + + The order of rectangles passed into the control must match the order of + streams configured by the application. The pipeline handler will only + configure crop retangles up-to the number of output streams configured. + All subsequent rectangles passed into this control are ignored by the + pipeline handler. + + If both rpi::ScalerCrops and ScalerCrop controls are present in a + ControlList, the latter is discarded, and crops are obtained from this + control. + + Note that using different crop rectangles for each output stream with + this control is only applicable on the Pi5/PiSP platform. This control + should also be considered temporary/draft and will be replaced with + official libcamera API support for per-stream controls in the future. + + \sa ScalerCrop + + - PispStatsOutput: + type: uint8_t + direction: out + size: [n] + description: | + Span of the PiSP Frontend ISP generated statistics for the current + frame. This is sent in the Request metadata if the StatsOutputEnable is + set to true. The statistics struct definition can be found in + https://github.com/raspberrypi/libpisp/blob/main/src/libpisp/frontend/pisp_statistics.h + + \sa StatsOutputEnable + +... diff --git a/src/libcamera/control_ranges.yaml b/src/libcamera/control_ranges.yaml new file mode 100644 index 00000000..6752eb98 --- /dev/null +++ b/src/libcamera/control_ranges.yaml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2023, Raspberry Pi Ltd +# +%YAML 1.1 +--- +# Specifies the control id ranges/offsets for core/draft libcamera and vendor +# controls and properties. +ranges: + # Core libcamera controls + libcamera: 0 + # Draft designated libcamera controls + draft: 10000 + # Raspberry Pi vendor controls + rpi: 20000 + # Controls for debug metadata + debug: 30000 + # Next range starts at 40000 + +... diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp index 0cf719bd..17834648 100644 --- a/src/libcamera/control_serializer.cpp +++ b/src/libcamera/control_serializer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_serializer.cpp - Control (de)serializer + * Control (de)serializer */ #include "libcamera/internal/control_serializer.h" @@ -281,6 +281,7 @@ int ControlSerializer::serialize(const ControlInfoMap &infoMap, entry.id = id->id(); entry.type = id->type(); entry.offset = values.offset(); + entry.direction = static_cast<ControlId::DirectionFlags::Type>(id->direction()); entries.write(&entry); store(info, values); @@ -493,12 +494,17 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer & /* If we're using a local id map, populate it. */ if (localIdMap) { + ControlId::DirectionFlags flags{ + static_cast<ControlId::Direction>(entry->direction) + }; + /** * \todo Find a way to preserve the control name for * debugging purpose. */ controlIds_.emplace_back(std::make_unique<ControlId>(entry->id, - "", type)); + "", "local", type, + flags)); (*localIdMap)[entry->id] = controlIds_.back().get(); } diff --git a/src/libcamera/control_validator.cpp b/src/libcamera/control_validator.cpp index cf08b34a..93982cff 100644 --- a/src/libcamera/control_validator.cpp +++ b/src/libcamera/control_validator.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_validator.cpp - Control validator + * Control validator */ #include "libcamera/internal/control_validator.h" diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index b808116c..98fa7583 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -2,15 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * controls.cpp - Control handling + * Control handling */ #include <libcamera/controls.h> -#include <iomanip> #include <sstream> -#include <string> #include <string.h> +#include <string> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> @@ -55,12 +54,15 @@ static constexpr size_t ControlValueSize[] = { [ControlTypeNone] = 0, [ControlTypeBool] = sizeof(bool), [ControlTypeByte] = sizeof(uint8_t), + [ControlTypeUnsigned16] = sizeof(uint16_t), + [ControlTypeUnsigned32] = sizeof(uint32_t), [ControlTypeInteger32] = sizeof(int32_t), [ControlTypeInteger64] = sizeof(int64_t), [ControlTypeFloat] = sizeof(float), [ControlTypeString] = sizeof(char), [ControlTypeRectangle] = sizeof(Rectangle), [ControlTypeSize] = sizeof(Size), + [ControlTypePoint] = sizeof(Point), }; } /* namespace */ @@ -74,10 +76,14 @@ static constexpr size_t ControlValueSize[] = { * The control stores a boolean value * \var ControlTypeByte * The control stores a byte value as an unsigned 8-bit integer + * \var ControlTypeUnsigned16 + * The control stores an unsigned 16-bit integer value + * \var ControlTypeUnsigned32 + * The control stores an unsigned 32-bit integer value * \var ControlTypeInteger32 - * The control stores a 32-bit integer value + * The control stores a signed 32-bit integer value * \var ControlTypeInteger64 - * The control stores a 64-bit integer value + * The control stores a signed 64-bit integer value * \var ControlTypeFloat * The control stores a 32-bit floating point value * \var ControlTypeString @@ -230,6 +236,16 @@ std::string ControlValue::toString() const str += std::to_string(*value); break; } + case ControlTypeUnsigned16: { + const uint16_t *value = reinterpret_cast<const uint16_t *>(data); + str += std::to_string(*value); + break; + } + case ControlTypeUnsigned32: { + const uint32_t *value = reinterpret_cast<const uint32_t *>(data); + str += std::to_string(*value); + break; + } case ControlTypeInteger32: { const int32_t *value = reinterpret_cast<const int32_t *>(data); str += std::to_string(*value); @@ -255,6 +271,11 @@ std::string ControlValue::toString() const str += value->toString(); break; } + case ControlTypePoint: { + const Point *value = reinterpret_cast<const Point *>(data); + str += value->toString(); + break; + } case ControlTypeNone: case ControlTypeString: break; @@ -389,8 +410,22 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen * \brief Construct a ControlId instance * \param[in] id The control numerical ID * \param[in] name The control name + * \param[in] vendor The vendor name * \param[in] type The control data type - */ + * \param[in] direction The direction of the control, if it can be used in Controls or Metadata + * \param[in] size The size of the array control, or 0 if scalar control + * \param[in] enumStrMap The map from enum names to values (optional) + */ +ControlId::ControlId(unsigned int id, const std::string &name, + const std::string &vendor, ControlType type, + DirectionFlags direction, std::size_t size, + const std::map<std::string, int32_t> &enumStrMap) + : id_(id), name_(name), vendor_(vendor), type_(type), + direction_(direction), size_(size), enumStrMap_(enumStrMap) +{ + for (const auto &pair : enumStrMap_) + reverseMap_[pair.second] = pair.first; +} /** * \fn unsigned int ControlId::id() const @@ -405,12 +440,68 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen */ /** + * \fn const std::string &ControlId::vendor() const + * \brief Retrieve the vendor name + * \return The vendor name, as a string + */ + +/** * \fn ControlType ControlId::type() const * \brief Retrieve the control data type * \return The control data type */ /** + * \fn DirectionFlags ControlId::direction() const + * \brief Return the direction that the control can be used in + * + * This is similar to \sa isInput() and \sa isOutput(), but returns the flags + * direction instead of booleans for each direction. + * + * \return The direction flags corresponding to if the control can be used as + * an input control or as output metadata + */ + +/** + * \fn bool ControlId::isInput() const + * \brief Determine if the control is available to be used as an input control + * + * Controls can be used either as input in controls, or as output in metadata. + * This function checks if the control is allowed to be used as the former. + * + * \return True if the control can be used as an input control, false otherwise + */ + +/** + * \fn bool ControlId::isOutput() const + * \brief Determine if the control is available to be used in output metadata + * + * Controls can be used either as input in controls, or as output in metadata. + * This function checks if the control is allowed to be used as the latter. + * + * \return True if the control can be returned in output metadata, false otherwise + */ + +/** + * \fn bool ControlId::isArray() const + * \brief Determine if the control is an array control + * \return True if the control is an array control, false otherwise + */ + +/** + * \fn std::size_t ControlId::size() const + * \brief Retrieve the size of the control if it is an array control + * \return The size of the array control, size_t::max for dynamic extent, or 0 + * for non-array + */ + +/** + * \fn const std::map<int32_t, std::string> &ControlId::enumerators() const + * \brief Retrieve the map of enum values to enum names + * \return The map of enum values to enum names + */ + +/** * \fn bool operator==(unsigned int lhs, const ControlId &rhs) * \brief Compare a ControlId with a control numerical ID * \param[in] lhs Left-hand side numerical ID @@ -429,6 +520,22 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen */ /** + * \enum ControlId::Direction + * \brief The direction the control is capable of being passed from/to + * + * \var ControlId::Direction::In + * \brief The control can be passed as input in controls + * + * \var ControlId::Direction::Out + * \brief The control can be returned as output in metadata + */ + +/** + * \typedef ControlId::DirectionFlags + * \brief A wrapper for ControlId::Direction so that it can be used as flags + */ + +/** * \class Control * \brief Describe a control and its intrinsic properties * @@ -456,10 +563,14 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen */ /** - * \fn Control::Control(unsigned int id, const char *name) + * \fn Control::Control(unsigned int id, const char *name, const char *vendor) * \brief Construct a Control instance * \param[in] id The control numerical ID * \param[in] name The control name + * \param[in] vendor The vendor name + * \param[in] direction The direction of the control, if it can be used in + * Controls or Metadata + * \param[in] enumStrMap The map from enum names to values (optional) * * The control data type is automatically deduced from the template type T. */ @@ -746,15 +857,7 @@ const ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id) const */ ControlInfoMap::size_type ControlInfoMap::count(unsigned int id) const { - if (!idmap_) - return 0; - - /* - * The ControlInfoMap and its idmap have a 1:1 mapping between their - * entries, we can thus just count the matching entries in idmap to - * avoid an additional lookup. - */ - return idmap_->count(id); + return find(id) != end(); } /** @@ -908,12 +1011,26 @@ ControlList::ControlList(const ControlInfoMap &infoMap, */ /** + * \enum ControlList::MergePolicy + * \brief The policy used by the merge function + * + * \var ControlList::MergePolicy::KeepExisting + * \brief Existing controls in the target list are kept + * + * \var ControlList::MergePolicy::OverwriteExisting + * \brief Existing controls in the target list are updated + */ + +/** * \brief Merge the \a source into the ControlList * \param[in] source The ControlList to merge into this object + * \param[in] policy Controls if existing elements in *this shall be + * overwritten * * Merging two control lists copies elements from the \a source and inserts * them in *this. If the \a source contains elements whose key is already - * present in *this, then those elements are not overwritten. + * present in *this, then those elements are only overwritten if + * \a policy is MergePolicy::OverwriteExisting. * * Only control lists created from the same ControlIdMap or ControlInfoMap may * be merged. Attempting to do otherwise results in undefined behaviour. @@ -921,7 +1038,7 @@ ControlList::ControlList(const ControlInfoMap &infoMap, * \todo Reimplement or implement an overloaded version which internally uses * std::unordered_map::merge() and accepts a non-const argument. */ -void ControlList::merge(const ControlList &source) +void ControlList::merge(const ControlList &source, MergePolicy policy) { /** * \todo ASSERT that the current and source ControlList are derived @@ -936,7 +1053,7 @@ void ControlList::merge(const ControlList &source) */ for (const auto &ctrl : source) { - if (contains(ctrl.first)) { + if (policy == MergePolicy::KeepExisting && contains(ctrl.first)) { const ControlId *id = idmap_->at(ctrl.first); LOG(Controls, Warning) << "Control " << id->name() << " not overwritten"; diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp index 9f64eb51..d551b908 100644 --- a/src/libcamera/converter.cpp +++ b/src/libcamera/converter.cpp @@ -2,7 +2,7 @@ /* * Copyright 2022 NXP * - * converter.cpp - Generic format converter interface + * Generic format converter interface */ #include "libcamera/internal/converter.h" @@ -11,10 +11,12 @@ #include <libcamera/base/log.h> +#include <libcamera/stream.h> + #include "libcamera/internal/media_device.h" /** - * \file internal/converter.h + * \file converter.h * \brief Abstract converter */ @@ -35,13 +37,38 @@ LOG_DEFINE_CATEGORY(Converter) */ /** + * \enum Converter::Feature + * \brief Specify the features supported by the converter + * \var Converter::Feature::None + * \brief No extra features supported by the converter + * \var Converter::Feature::InputCrop + * \brief Cropping capability at input is supported by the converter + */ + +/** + * \typedef Converter::Features + * \brief A bitwise combination of features supported by the converter + */ + +/** + * \enum Converter::Alignment + * \brief The alignment mode specified when adjusting the converter input or + * output sizes + * \var Converter::Alignment::Down + * \brief Adjust the Converter sizes to a smaller valid size + * \var Converter::Alignment::Up + * \brief Adjust the Converter sizes to a larger valid size + */ + +/** * \brief Construct a Converter instance * \param[in] media The media device implementing the converter + * \param[in] features Features flags representing supported features * * This searches for the entity implementing the data streaming function in the * media graph entities and use its device node as the converter device node. */ -Converter::Converter(MediaDevice *media) +Converter::Converter(MediaDevice *media, Features features) { const std::vector<MediaEntity *> &entities = media->entities(); auto it = std::find_if(entities.begin(), entities.end(), @@ -56,6 +83,7 @@ Converter::Converter(MediaDevice *media) } deviceNode_ = (*it)->deviceNode(); + features_ = features; } Converter::~Converter() @@ -93,6 +121,26 @@ Converter::~Converter() */ /** + * \fn Converter::adjustInputSize() + * \brief Adjust the converter input \a size to a valid value + * \param[in] pixFmt The pixel format of the converter input stream + * \param[in] size The converter input size to adjust to a valid value + * \param[in] align The desired alignment + * \return The adjusted converter input size or a null Size if \a size cannot + * be adjusted + */ + +/** + * \fn Converter::adjustOutputSize() + * \brief Adjust the converter output \a size to a valid value + * \param[in] pixFmt The pixel format of the converter output stream + * \param[in] size The converter output size to adjust to a valid value + * \param[in] align The desired alignment + * \return The adjusted converter output size or a null Size if \a size cannot + * be adjusted + */ + +/** * \fn Converter::strideAndFrameSize() * \brief Retrieve the output stride and frame size for an input configutation * \param[in] pixelFormat Input stream pixel format @@ -101,6 +149,16 @@ Converter::~Converter() */ /** + * \fn Converter::validateOutput() + * \brief Validate and possibily adjust \a cfg to a valid converter output + * \param[inout] cfg The StreamConfiguration to validate and adjust + * \param[out] adjusted Set to true if \a cfg has been adjusted + * \param[in] align The desired alignment + * \return 0 if \a cfg is valid or has been adjusted, a negative error code + * otherwise if \a cfg cannot be adjusted + */ + +/** * \fn Converter::configure() * \brief Configure a set of output stream conversion from an input stream * \param[in] inputCfg Input stream configuration @@ -109,14 +167,21 @@ Converter::~Converter() */ /** + * \fn Converter::isConfigured() + * \brief Check if a given stream is configured + * \param[in] stream The output stream + * \return True if the \a stream is configured or false otherwise + */ + +/** * \fn Converter::exportBuffers() * \brief Export buffers from the converter device - * \param[in] output Output stream index exporting the buffers + * \param[in] stream Output stream pointer exporting the buffers * \param[in] count Number of buffers to allocate * \param[out] buffers Vector to store allocated buffers * * This function operates similarly to V4L2VideoDevice::exportBuffers() on the - * output stream indicated by the \a output index. + * output stream indicated by the \a output. * * \return The number of allocated buffers on success or a negative error code * otherwise @@ -137,7 +202,7 @@ Converter::~Converter() * \fn Converter::queueBuffers() * \brief Queue buffers to converter device * \param[in] input The frame buffer to apply the conversion - * \param[out] outputs The container holding the output stream indexes and + * \param[out] outputs The container holding the output stream pointers and * their respective frame buffer outputs. * * This function queues the \a input frame buffer on the output streams of the @@ -148,6 +213,52 @@ Converter::~Converter() */ /** + * \fn Converter::setInputCrop() + * \brief Set the crop rectangle \a rect for \a stream + * \param[in] stream The output stream + * \param[inout] rect The crop rectangle to apply and return the rectangle + * that is actually applied + * + * Set the crop rectangle \a rect for \a stream provided the converter supports + * cropping. The converter has the Feature::InputCrop flag in this case. + * + * The underlying hardware can adjust the rectangle supplied by the user + * due to hardware constraints. The caller can inspect \a rect to determine the + * actual rectangle that has been applied by the converter, after this function + * returns. + * + * \return 0 on success or a negative error code otherwise + */ + +/** + * \fn Converter::inputCropBounds() + * \brief Retrieve the crop bounds of the converter + * + * Retrieve the minimum and maximum crop bounds of the converter. This can be + * used to query the crop bounds before configuring a stream. + * + * \return A pair containing the minimum and maximum crop bound in that order + */ + +/** + * \fn Converter::inputCropBounds(const Stream *stream) + * \brief Retrieve the crop bounds for \a stream + * \param[in] stream The output stream + * + * Retrieve the minimum and maximum crop bounds for \a stream. The converter + * should support cropping (Feature::InputCrop). + * + * The crop bounds depend on the configuration of the output stream and hence + * this function should be called after the \a stream has been configured using + * configure(). + * + * When called with an unconfigured \a stream, this function returns a pair of + * null rectangles. + * + * \return A pair containing the minimum and maximum crop bound in that order + */ + +/** * \var Converter::inputBufferReady * \brief A signal emitted when the input frame buffer completes */ @@ -158,12 +269,23 @@ Converter::~Converter() */ /** + * \var Converter::features_ + * \brief Stores the features supported by the converter + */ + +/** * \fn Converter::deviceNode() * \brief The converter device node attribute accessor * \return The converter device node string */ /** + * \fn Converter::features() + * \brief Retrieve the features supported by the converter + * \return The converter Features flags + */ + +/** * \class ConverterFactoryBase * \brief Base class for converter factories * @@ -263,8 +385,9 @@ std::vector<std::string> ConverterFactoryBase::names() for (ConverterFactoryBase *factory : factories) { list.push_back(factory->name_); - for (auto alias : factory->compatibles()) - list.push_back(alias); + + const auto &compatibles = factory->compatibles(); + list.insert(list.end(), compatibles.begin(), compatibles.end()); } return list; diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp index 2a4d1d99..ee05b798 100644 --- a/src/libcamera/converter/converter_v4l2_m2m.cpp +++ b/src/libcamera/converter/converter_v4l2_m2m.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2020, Laurent Pinchart * Copyright 2022 NXP * - * converter_v4l2_m2m.cpp - V4L2 M2M Format converter + * V4L2 M2M Format converter */ #include "libcamera/internal/converter/converter_v4l2_m2m.h" @@ -23,7 +23,7 @@ #include "libcamera/internal/v4l2_videodevice.h" /** - * \file internal/converter/converter_v4l2_m2m.h + * \file converter/converter_v4l2_m2m.h * \brief V4L2 M2M based converter */ @@ -31,25 +31,71 @@ namespace libcamera { LOG_DECLARE_CATEGORY(Converter) +namespace { + +int getCropBounds(V4L2VideoDevice *device, Rectangle &minCrop, + Rectangle &maxCrop) +{ + Rectangle minC; + Rectangle maxC; + + /* Find crop bounds */ + minC.width = 1; + minC.height = 1; + maxC.width = UINT_MAX; + maxC.height = UINT_MAX; + + int ret = device->setSelection(V4L2_SEL_TGT_CROP, &minC); + if (ret) { + LOG(Converter, Error) + << "Could not query minimum selection crop: " + << strerror(-ret); + return ret; + } + + ret = device->getSelection(V4L2_SEL_TGT_CROP_BOUNDS, &maxC); + if (ret) { + LOG(Converter, Error) + << "Could not query maximum selection crop: " + << strerror(-ret); + return ret; + } + + /* Reset the crop to its maximum */ + ret = device->setSelection(V4L2_SEL_TGT_CROP, &maxC); + if (ret) { + LOG(Converter, Error) + << "Could not reset selection crop: " + << strerror(-ret); + return ret; + } + + minCrop = minC; + maxCrop = maxC; + return 0; +} + +} /* namespace */ + /* ----------------------------------------------------------------------------- - * V4L2M2MConverter::Stream + * V4L2M2MConverter::V4L2M2MStream */ -V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index) - : converter_(converter), index_(index) +V4L2M2MConverter::V4L2M2MStream::V4L2M2MStream(V4L2M2MConverter *converter, const Stream *stream) + : converter_(converter), stream_(stream) { m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode()); - m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady); - m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady); + m2m_->output()->bufferReady.connect(this, &V4L2M2MStream::outputBufferReady); + m2m_->capture()->bufferReady.connect(this, &V4L2M2MStream::captureBufferReady); int ret = m2m_->open(); if (ret < 0) m2m_.reset(); } -int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg, - const StreamConfiguration &outputCfg) +int V4L2M2MConverter::V4L2M2MStream::configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg) { V4L2PixelFormat videoFormat = m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat); @@ -98,16 +144,23 @@ int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg, inputBufferCount_ = inputCfg.bufferCount; outputBufferCount_ = outputCfg.bufferCount; + if (converter_->features() & Feature::InputCrop) { + ret = getCropBounds(m2m_->output(), inputCropBounds_.first, + inputCropBounds_.second); + if (ret) + return ret; + } + return 0; } -int V4L2M2MConverter::Stream::exportBuffers(unsigned int count, - std::vector<std::unique_ptr<FrameBuffer>> *buffers) +int V4L2M2MConverter::V4L2M2MStream::exportBuffers(unsigned int count, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) { return m2m_->capture()->exportBuffers(count, buffers); } -int V4L2M2MConverter::Stream::start() +int V4L2M2MConverter::V4L2M2MStream::start() { int ret = m2m_->output()->importBuffers(inputBufferCount_); if (ret < 0) @@ -134,7 +187,7 @@ int V4L2M2MConverter::Stream::start() return 0; } -void V4L2M2MConverter::Stream::stop() +void V4L2M2MConverter::V4L2M2MStream::stop() { m2m_->capture()->streamOff(); m2m_->output()->streamOff(); @@ -142,7 +195,7 @@ void V4L2M2MConverter::Stream::stop() m2m_->output()->releaseBuffers(); } -int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output) +int V4L2M2MConverter::V4L2M2MStream::queueBuffers(FrameBuffer *input, FrameBuffer *output) { int ret = m2m_->output()->queueBuffer(input); if (ret < 0) @@ -155,12 +208,27 @@ int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *outp return 0; } -std::string V4L2M2MConverter::Stream::logPrefix() const +int V4L2M2MConverter::V4L2M2MStream::getInputSelection(unsigned int target, Rectangle *rect) +{ + return m2m_->output()->getSelection(target, rect); +} + +int V4L2M2MConverter::V4L2M2MStream::setInputSelection(unsigned int target, Rectangle *rect) +{ + return m2m_->output()->setSelection(target, rect); +} + +std::pair<Rectangle, Rectangle> V4L2M2MConverter::V4L2M2MStream::inputCropBounds() { - return "stream" + std::to_string(index_); + return inputCropBounds_; } -void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer) +std::string V4L2M2MConverter::V4L2M2MStream::logPrefix() const +{ + return stream_->configuration().toString(); +} + +void V4L2M2MConverter::V4L2M2MStream::outputBufferReady(FrameBuffer *buffer) { auto it = converter_->queue_.find(buffer); if (it == converter_->queue_.end()) @@ -172,7 +240,7 @@ void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer) } } -void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer) +void V4L2M2MConverter::V4L2M2MStream::captureBufferReady(FrameBuffer *buffer) { converter_->outputBufferReady.emit(buffer); } @@ -205,6 +273,15 @@ V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media) m2m_.reset(); return; } + + ret = getCropBounds(m2m_->output(), inputCropBounds_.first, + inputCropBounds_.second); + if (!ret && inputCropBounds_.first != inputCropBounds_.second) { + features_ |= Feature::InputCrop; + + LOG(Converter, Info) + << "Converter supports cropping on its input"; + } } /** @@ -325,6 +402,127 @@ V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat, } /** + * \copydoc libcamera::Converter::adjustInputSize + */ +Size V4L2M2MConverter::adjustInputSize(const PixelFormat &pixFmt, + const Size &size, Alignment align) +{ + auto formats = m2m_->output()->formats(); + V4L2PixelFormat v4l2PixFmt = m2m_->output()->toV4L2PixelFormat(pixFmt); + + auto it = formats.find(v4l2PixFmt); + if (it == formats.end()) { + LOG(Converter, Info) + << "Unsupported pixel format " << pixFmt; + return {}; + } + + return adjustSizes(size, it->second, align); +} + +/** + * \copydoc libcamera::Converter::adjustOutputSize + */ +Size V4L2M2MConverter::adjustOutputSize(const PixelFormat &pixFmt, + const Size &size, Alignment align) +{ + auto formats = m2m_->capture()->formats(); + V4L2PixelFormat v4l2PixFmt = m2m_->capture()->toV4L2PixelFormat(pixFmt); + + auto it = formats.find(v4l2PixFmt); + if (it == formats.end()) { + LOG(Converter, Info) + << "Unsupported pixel format " << pixFmt; + return {}; + } + + return adjustSizes(size, it->second, align); +} + +Size V4L2M2MConverter::adjustSizes(const Size &cfgSize, + const std::vector<SizeRange> &ranges, + Alignment align) +{ + Size size = cfgSize; + + if (ranges.size() == 1) { + /* + * The device supports either V4L2_FRMSIZE_TYPE_CONTINUOUS or + * V4L2_FRMSIZE_TYPE_STEPWISE. + */ + const SizeRange &range = *ranges.begin(); + + size.width = std::clamp(size.width, range.min.width, + range.max.width); + size.height = std::clamp(size.height, range.min.height, + range.max.height); + + /* + * Check if any alignment is needed. If the sizes are already + * aligned, or the device supports V4L2_FRMSIZE_TYPE_CONTINUOUS + * with hStep and vStep equal to 1, we're done here. + */ + int widthR = size.width % range.hStep; + int heightR = size.height % range.vStep; + + /* Align up or down according to the caller request. */ + + if (widthR != 0) + size.width = size.width - widthR + + ((align == Alignment::Up) ? range.hStep : 0); + + if (heightR != 0) + size.height = size.height - heightR + + ((align == Alignment::Up) ? range.vStep : 0); + } else { + /* + * The device supports V4L2_FRMSIZE_TYPE_DISCRETE, find the + * size closer to the requested output configuration. + * + * The size ranges vector is not ordered, so we sort it first. + * If we align up, start from the larger element. + */ + std::vector<Size> sizes(ranges.size()); + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes), + [](const SizeRange &range) { return range.max; }); + std::sort(sizes.begin(), sizes.end()); + + if (align == Alignment::Up) + std::reverse(sizes.begin(), sizes.end()); + + /* + * Return true if s2 is valid according to the desired + * alignment: smaller than s1 if we align down, larger than s1 + * if we align up. + */ + auto nextSizeValid = [](const Size &s1, const Size &s2, Alignment a) { + return a == Alignment::Down + ? (s1.width > s2.width && s1.height > s2.height) + : (s1.width < s2.width && s1.height < s2.height); + }; + + Size newSize; + for (const Size &sz : sizes) { + if (!nextSizeValid(size, sz, align)) + break; + + newSize = sz; + } + + if (newSize.isNull()) { + LOG(Converter, Error) + << "Cannot adjust " << cfgSize + << " to a supported converter size"; + return {}; + } + + size = newSize; + } + + return size; +} + +/** * \copydoc libcamera::Converter::configure */ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg, @@ -333,21 +531,24 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg, int ret = 0; streams_.clear(); - streams_.reserve(outputCfgs.size()); for (unsigned int i = 0; i < outputCfgs.size(); ++i) { - Stream &stream = streams_.emplace_back(this, i); + const StreamConfiguration &cfg = outputCfgs[i]; + std::unique_ptr<V4L2M2MStream> stream = + std::make_unique<V4L2M2MStream>(this, cfg.stream()); - if (!stream.isValid()) { + if (!stream->isValid()) { LOG(Converter, Error) << "Failed to create stream " << i; ret = -EINVAL; break; } - ret = stream.configure(inputCfg, outputCfgs[i]); + ret = stream->configure(inputCfg, cfg); if (ret < 0) break; + + streams_.emplace(cfg.stream(), std::move(stream)); } if (ret < 0) { @@ -359,15 +560,61 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg, } /** + * \copydoc libcamera::Converter::isConfigured + */ +bool V4L2M2MConverter::isConfigured(const Stream *stream) const +{ + return streams_.find(stream) != streams_.end(); +} + +/** * \copydoc libcamera::Converter::exportBuffers */ -int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count, +int V4L2M2MConverter::exportBuffers(const Stream *stream, unsigned int count, std::vector<std::unique_ptr<FrameBuffer>> *buffers) { - if (output >= streams_.size()) + auto iter = streams_.find(stream); + if (iter == streams_.end()) + return -EINVAL; + + return iter->second->exportBuffers(count, buffers); +} + +/** + * \copydoc libcamera::Converter::setInputCrop + */ +int V4L2M2MConverter::setInputCrop(const Stream *stream, Rectangle *rect) +{ + if (!(features_ & Feature::InputCrop)) + return -ENOTSUP; + + auto iter = streams_.find(stream); + if (iter == streams_.end()) { + LOG(Converter, Error) << "Invalid output stream"; return -EINVAL; + } + + return iter->second->setInputSelection(V4L2_SEL_TGT_CROP, rect); +} + +/** + * \fn libcamera::V4L2M2MConverter::inputCropBounds() + * \copydoc libcamera::Converter::inputCropBounds() + */ + +/** + * \copydoc libcamera::Converter::inputCropBounds(const Stream *stream) + */ +std::pair<Rectangle, Rectangle> +V4L2M2MConverter::inputCropBounds(const Stream *stream) +{ + auto iter = streams_.find(stream); + if (iter == streams_.end()) { + LOG(Converter, Error) << "Invalid output stream"; + return {}; + } - return streams_[output].exportBuffers(count, buffers); + return iter->second->inputCropBounds(); } /** @@ -377,8 +624,8 @@ int V4L2M2MConverter::start() { int ret; - for (Stream &stream : streams_) { - ret = stream.start(); + for (auto &iter : streams_) { + ret = iter.second->start(); if (ret < 0) { stop(); return ret; @@ -393,41 +640,87 @@ int V4L2M2MConverter::start() */ void V4L2M2MConverter::stop() { - for (Stream &stream : utils::reverse(streams_)) - stream.stop(); + for (auto &iter : streams_) + iter.second->stop(); +} + +/** + * \copydoc libcamera::Converter::validateOutput + */ +int V4L2M2MConverter::validateOutput(StreamConfiguration *cfg, bool *adjusted, + Alignment align) +{ + V4L2VideoDevice *capture = m2m_->capture(); + V4L2VideoDevice::Formats fmts = capture->formats(); + + if (adjusted) + *adjusted = false; + + PixelFormat fmt = cfg->pixelFormat; + V4L2PixelFormat v4l2PixFmt = capture->toV4L2PixelFormat(fmt); + + auto it = fmts.find(v4l2PixFmt); + if (it == fmts.end()) { + it = fmts.begin(); + v4l2PixFmt = it->first; + cfg->pixelFormat = v4l2PixFmt.toPixelFormat(); + + if (adjusted) + *adjusted = true; + + LOG(Converter, Info) + << "Converter output pixel format adjusted to " + << cfg->pixelFormat; + } + + const Size cfgSize = cfg->size; + cfg->size = adjustSizes(cfgSize, it->second, align); + + if (cfg->size.isNull()) + return -EINVAL; + + if (cfg->size.width != cfgSize.width || + cfg->size.height != cfgSize.height) { + LOG(Converter, Info) + << "Converter size adjusted to " + << cfg->size; + if (adjusted) + *adjusted = true; + } + + return 0; } /** * \copydoc libcamera::Converter::queueBuffers */ int V4L2M2MConverter::queueBuffers(FrameBuffer *input, - const std::map<unsigned int, FrameBuffer *> &outputs) + const std::map<const Stream *, FrameBuffer *> &outputs) { - unsigned int mask = 0; + std::set<FrameBuffer *> outputBufs; int ret; /* * Validate the outputs as a sanity check: at least one output is * required, all outputs must reference a valid stream and no two - * outputs can reference the same stream. + * streams can reference same output framebuffers. */ if (outputs.empty()) return -EINVAL; - for (auto [index, buffer] : outputs) { + for (auto [stream, buffer] : outputs) { if (!buffer) return -EINVAL; - if (index >= streams_.size()) - return -EINVAL; - if (mask & (1 << index)) - return -EINVAL; - mask |= 1 << index; + outputBufs.insert(buffer); } + if (outputBufs.size() != streams_.size()) + return -EINVAL; + /* Queue the input and output buffers to all the streams. */ - for (auto [index, buffer] : outputs) { - ret = streams_[index].queueBuffers(input, buffer); + for (auto [stream, buffer] : outputs) { + ret = streams_.at(stream)->queueBuffers(input, buffer); if (ret < 0) return ret; } @@ -444,7 +737,12 @@ int V4L2M2MConverter::queueBuffers(FrameBuffer *input, return 0; } +/* + * \todo This should be extended to include Feature::Flag to denote + * what each converter supports feature-wise. + */ static std::initializer_list<std::string> compatibles = { + "mtk-mdp", "pxp", }; diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build index 2aa72fe4..af1a80fe 100644 --- a/src/libcamera/converter/meson.build +++ b/src/libcamera/converter/meson.build @@ -1,5 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'converter_v4l2_m2m.cpp' ]) diff --git a/src/libcamera/debug_controls.cpp b/src/libcamera/debug_controls.cpp new file mode 100644 index 00000000..33960231 --- /dev/null +++ b/src/libcamera/debug_controls.cpp @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Helper to easily record debug metadata inside libcamera. + */ + +#include "libcamera/internal/debug_controls.h" + +#include <libcamera/base/log.h> + +namespace libcamera { + +LOG_DEFINE_CATEGORY(DebugControls) + +/** + * \file debug_controls.h + * \brief Helper to easily record debug metadata inside libcamera + */ + +/** + * \class DebugMetadata + * \brief Helper to record metadata for later use + * + * Metadata is a useful tool for debugging the internal state of libcamera. It + * has the benefit that it is easy to use and related tooling is readily + * available. The difficulty is that the metadata control list is often not + * directly available (either because the variable to debug lives inside + * process() of an IPA or inside a closed algorithm class with no direct access + * to the IPA and therefore the metadata list). + * + * This class helps in both cases. It allows to forward the data to a parent or + * alternatively record the data and at a later point in time copy it to the + * metadata list when it becomes available. Both mechanisms allow easy reuse and + * loose coupling. + * + * Typical usage is to instantiate a DebugMetadata object in every + * class/algorithm where debug metadata shall be recorded (the inner object). If + * the IPA doesn't support debug metadata, the object is still usable, but the + * debug data gets dropped. If the IPA supports debug metadata it will either + * register a parent DebugMetadata object on the inner object or manually + * retrieve the data using enable()/moveToList(). + * + * The concepts of forwarding to a parent and recording for later retrieval are + * mutually exclusive and the parent takes precedence. E.g. it is not allowed to + * enable a DebugMetadata object, log entries to it and later set the parent. + * + * This is done to keep the path open for using other means of data transport + * (like tracing). For every tracing event a corresponding context needs to be + * available on set() time. The parent can be treated as such, the top level + * object (the one where enable() get's called) also lives in a place where that + * information is also available. + */ + +/** + * \fn DebugMetadata::enableByControl() + * \brief Enable based on controls::DebugMetadataEnable in the supplied + * ControlList + * \param[in] controls The supplied ControlList + * + * This function looks for controls::DebugMetadataEnable and enables or disables + * debug metadata handling accordingly. + */ +void DebugMetadata::enableByControl(const ControlList &controls) +{ + const auto &ctrl = controls.get(controls::DebugMetadataEnable); + if (ctrl) + enable(*ctrl); +} + +/** + * \fn DebugMetadata::enable() + * \brief Enable or disable metadata handling + * \param[in] enable The enable state + * + * When \a enable is true, all calls to set() get cached and can later be + * retrieved using moveEntries(). When \a enable is false, the cache gets + * cleared and no further metadata is recorded. + * + * Forwarding to a parent is independent of the enabled state. + */ +void DebugMetadata::enable(bool enable) +{ + enabled_ = enable; + if (!enabled_) + cache_.clear(); +} + +/** + * \fn DebugMetadata::setParent() + * \brief Set the parent metadata handler to \a parent + * \param[in] parent The parent handler + * + * When a \a parent is set, all further calls to set() are unconditionally + * forwarded to that instance. + * + * The parent can be reset by passing a nullptr. + */ +void DebugMetadata::setParent(DebugMetadata *parent) +{ + parent_ = parent; + + if (!parent_) + return; + + if (!cache_.empty()) + LOG(DebugControls, Error) + << "Controls were recorded before setting a parent." + << " These are dropped."; + + cache_.clear(); +} + +/** + * \fn DebugMetadata::moveEntries() + * \brief Move all cached entries into control list \a list + * \param[in] list The control list + * + * This function moves all entries into the list specified by \a list. Duplicate + * entries in \a list get overwritten. + */ +void DebugMetadata::moveEntries(ControlList &list) +{ + list.merge(std::move(cache_), ControlList::MergePolicy::OverwriteExisting); + cache_.clear(); +} + +/** + * \fn DebugMetadata::set(const Control<T> &ctrl, const V &value) + * \brief Set the value of \a ctrl to \a value + * \param[in] ctrl The control to set + * \param[in] value The control value + * + * If a parent is set, the value gets passed there unconditionally. Otherwise it + * gets cached if the instance is enabled or dropped silently when disabled. + * + * \sa enable() + */ + +/** + * \fn DebugMetadata::set(unsigned int id, const ControlValue &value) + * \brief Set the value of control \a id to \a value + * \param[in] id The id of the control + * \param[in] value The control value + * + * If a parent is set, the value gets passed there unconditionally. Otherwise it + * gets cached if the instance is enabled or dropped silently when disabled. + * + * \sa enable() + */ +void DebugMetadata::set(unsigned int id, const ControlValue &value) +{ + if (parent_) { + parent_->set(id, value); + return; + } + + if (!enabled_) + return; + + cache_.set(id, value); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/delayed_controls.cpp b/src/libcamera/delayed_controls.cpp index 777441e8..94d0a575 100644 --- a/src/libcamera/delayed_controls.cpp +++ b/src/libcamera/delayed_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * delayed_controls.h - Helper to deal with controls that take effect with a delay + * Helper to deal with controls that take effect with a delay */ #include "libcamera/internal/delayed_controls.h" diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp index f2e055de..ae17862f 100644 --- a/src/libcamera/device_enumerator.cpp +++ b/src/libcamera/device_enumerator.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * device_enumerator.cpp - Enumeration and matching + * Enumeration and matching */ #include "libcamera/internal/device_enumerator.h" @@ -56,7 +56,7 @@ LOG_DEFINE_CATEGORY(DeviceEnumerator) * names can be added as match criteria. * * Pipeline handlers are recommended to add entities to DeviceMatch as - * appropriare to ensure that the media device they need can be uniquely + * appropriate to ensure that the media device they need can be uniquely * identified. This is useful when the corresponding kernel driver can produce * different graphs, for instance as a result of different driver versions or * hardware configurations, and not all those graphs are suitable for a pipeline @@ -101,8 +101,14 @@ bool DeviceMatch::match(const MediaDevice *device) const for (const MediaEntity *entity : device->entities()) { if (name == entity->name()) { - found = true; - break; + if (!entity->deviceNode().empty()) { + found = true; + break; + } else { + LOG(DeviceEnumerator, Debug) + << "Skip " << entity->name() + << ": no device node"; + } } } diff --git a/src/libcamera/device_enumerator_sysfs.cpp b/src/libcamera/device_enumerator_sysfs.cpp index 686bb809..7866885c 100644 --- a/src/libcamera/device_enumerator_sysfs.cpp +++ b/src/libcamera/device_enumerator_sysfs.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * device_enumerator_sysfs.cpp - sysfs-based device enumerator + * sysfs-based device enumerator */ #include "libcamera/internal/device_enumerator_sysfs.h" @@ -33,7 +33,7 @@ int DeviceEnumeratorSysfs::init() int DeviceEnumeratorSysfs::enumerate() { struct dirent *ent; - DIR *dir; + DIR *dir = nullptr; static const char * const sysfs_dirs[] = { "/sys/subsystem/media/devices", diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp index 0abc1248..4e20a3cc 100644 --- a/src/libcamera/device_enumerator_udev.cpp +++ b/src/libcamera/device_enumerator_udev.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018-2019, Google Inc. * - * device_enumerator_udev.cpp - udev-based device enumerator + * udev-based device enumerator */ #include "libcamera/internal/device_enumerator_udev.h" @@ -332,6 +332,14 @@ int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum) void DeviceEnumeratorUdev::udevNotify() { struct udev_device *dev = udev_monitor_receive_device(monitor_); + if (!dev) { + int err = errno; + LOG(DeviceEnumerator, Warning) + << "Ignoring notfication received without a device: " + << strerror(err); + return; + } + std::string_view action(udev_device_get_action(dev)); std::string_view deviceNode(udev_device_get_devnode(dev)); diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp new file mode 100644 index 00000000..d8c62dd6 --- /dev/null +++ b/src/libcamera/dma_buf_allocator.cpp @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * Copyright (C) 2020, Raspberry Pi Ltd + * + * Helper class for dma-buf allocations. + */ + +#include "libcamera/internal/dma_buf_allocator.h" + +#include <array> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <linux/dma-buf.h> +#include <linux/dma-heap.h> +#include <linux/udmabuf.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/memfd.h> +#include <libcamera/base/shared_fd.h> + +#include <libcamera/framebuffer.h> + +/** + * \file dma_buf_allocator.cpp + * \brief dma-buf allocator + */ + +namespace libcamera { + +#ifndef __DOXYGEN__ +struct DmaBufAllocatorInfo { + DmaBufAllocator::DmaBufAllocatorFlag type; + const char *deviceNodeName; +}; +#endif + +static constexpr std::array<DmaBufAllocatorInfo, 4> providerInfos = { { + /* + * /dev/dma_heap/linux,cma is the CMA dma-heap. When the cma heap size is + * specified on the kernel command line, this gets renamed to "reserved". + */ + { DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap, "/dev/dma_heap/linux,cma" }, + { DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap, "/dev/dma_heap/reserved" }, + { DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap, "/dev/dma_heap/system" }, + { DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf, "/dev/udmabuf" }, +} }; + +LOG_DEFINE_CATEGORY(DmaBufAllocator) + +/** + * \class DmaBufAllocator + * \brief Helper class for dma-buf allocations + * + * This class wraps a userspace dma-buf provider selected at construction time, + * and exposes functions to allocate dma-buffers from this provider. + * + * Different providers may provide dma-buffers with different properties for + * the underlying memory. Which providers are acceptable is specified through + * the type argument passed to the DmaBufAllocator() constructor. + */ + +/** + * \enum DmaBufAllocator::DmaBufAllocatorFlag + * \brief Type of the dma-buf provider + * \var DmaBufAllocator::CmaHeap + * \brief Allocate from a CMA dma-heap, providing physically-contiguous memory + * \var DmaBufAllocator::SystemHeap + * \brief Allocate from the system dma-heap, using the page allocator + * \var DmaBufAllocator::UDmaBuf + * \brief Allocate using a memfd + /dev/udmabuf + */ + +/** + * \typedef DmaBufAllocator::DmaBufAllocatorFlags + * \brief A bitwise combination of DmaBufAllocator::DmaBufAllocatorFlag values + */ + +/** + * \brief Construct a DmaBufAllocator of a given type + * \param[in] type The type(s) of the dma-buf providers to allocate from + * + * The dma-buf provider type is selected with the \a type parameter, which + * defaults to the CMA heap. If no provider of the given type can be accessed, + * the constructed DmaBufAllocator instance is invalid as indicated by + * the isValid() function. + * + * Multiple types can be selected by combining type flags, in which case + * the constructed DmaBufAllocator will match one of the types. If multiple + * requested types can work on the system, which provider is used is undefined. + */ +DmaBufAllocator::DmaBufAllocator(DmaBufAllocatorFlags type) +{ + for (const auto &info : providerInfos) { + if (!(type & info.type)) + continue; + + int ret = ::open(info.deviceNodeName, O_RDWR | O_CLOEXEC, 0); + if (ret < 0) { + ret = errno; + LOG(DmaBufAllocator, Debug) + << "Failed to open " << info.deviceNodeName << ": " + << strerror(ret); + continue; + } + + LOG(DmaBufAllocator, Debug) << "Using " << info.deviceNodeName; + providerHandle_ = UniqueFD(ret); + type_ = info.type; + break; + } + + if (!providerHandle_.isValid()) + LOG(DmaBufAllocator, Error) << "Could not open any dma-buf provider"; +} + +/** + * \brief Destroy the DmaBufAllocator instance + */ +DmaBufAllocator::~DmaBufAllocator() = default; + +/** + * \fn DmaBufAllocator::isValid() + * \brief Check if the DmaBufAllocator instance is valid + * \return True if the DmaBufAllocator is valid, false otherwise + */ +UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size) +{ + /* Size must be a multiple of the page size. Round it up. */ + std::size_t pageMask = sysconf(_SC_PAGESIZE) - 1; + size = (size + pageMask) & ~pageMask; + + /* udmabuf dma-buffers *must* have the F_SEAL_SHRINK seal. */ + UniqueFD memfd = MemFd::create(name, size, MemFd::Seal::Shrink); + if (!memfd.isValid()) + return {}; + + struct udmabuf_create create; + + create.memfd = memfd.get(); + create.flags = UDMABUF_FLAGS_CLOEXEC; + create.offset = 0; + create.size = size; + + int ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create); + if (ret < 0) { + ret = errno; + LOG(DmaBufAllocator, Error) + << "Failed to create dma buf for " << name + << ": " << strerror(ret); + return {}; + } + + /* The underlying memfd is kept as as a reference in the kernel. */ + return UniqueFD(ret); +} + +UniqueFD DmaBufAllocator::allocFromHeap(const char *name, std::size_t size) +{ + struct dma_heap_allocation_data alloc = {}; + int ret; + + alloc.len = size; + alloc.fd_flags = O_CLOEXEC | O_RDWR; + + ret = ::ioctl(providerHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc); + if (ret < 0) { + LOG(DmaBufAllocator, Error) + << "dma-heap allocation failure for " << name; + return {}; + } + + UniqueFD allocFd(alloc.fd); + ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name); + if (ret < 0) { + LOG(DmaBufAllocator, Error) + << "dma-heap naming failure for " << name; + return {}; + } + + return allocFd; +} + +/** + * \brief Allocate a dma-buf from the DmaBufAllocator + * \param [in] name The name to set for the allocated buffer + * \param [in] size The size of the buffer to allocate + * + * Allocates a dma-buf with read/write access. + * + * If the allocation fails, return an invalid UniqueFD. + * + * \return The UniqueFD of the allocated buffer + */ +UniqueFD DmaBufAllocator::alloc(const char *name, std::size_t size) +{ + if (!name) + return {}; + + if (type_ == DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) + return allocFromUDmaBuf(name, size); + else + return allocFromHeap(name, size); +} + +/** + * \brief Allocate and export buffers from the DmaBufAllocator + * \param[in] count The number of requested FrameBuffers + * \param[in] planeSizes The sizes of planes in each FrameBuffer + * \param[out] buffers Array of buffers successfully allocated + * + * Planes in a FrameBuffer are allocated with a single dma buf. + * \todo Add the option to allocate each plane with a dma buf respectively. + * + * \return The number of allocated buffers on success or a negative error code + * otherwise + */ +int DmaBufAllocator::exportBuffers(unsigned int count, + const std::vector<unsigned int> &planeSizes, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) +{ + for (unsigned int i = 0; i < count; ++i) { + std::unique_ptr<FrameBuffer> buffer = + createBuffer("frame-" + std::to_string(i), planeSizes); + if (!buffer) { + LOG(DmaBufAllocator, Error) << "Unable to create buffer"; + + buffers->clear(); + return -EINVAL; + } + + buffers->push_back(std::move(buffer)); + } + + return count; +} + +std::unique_ptr<FrameBuffer> +DmaBufAllocator::createBuffer(std::string name, + const std::vector<unsigned int> &planeSizes) +{ + std::vector<FrameBuffer::Plane> planes; + + unsigned int frameSize = 0, offset = 0; + for (auto planeSize : planeSizes) + frameSize += planeSize; + + SharedFD fd(alloc(name.c_str(), frameSize)); + if (!fd.isValid()) + return nullptr; + + for (auto planeSize : planeSizes) { + planes.emplace_back(FrameBuffer::Plane{ fd, offset, planeSize }); + offset += planeSize; + } + + return std::make_unique<FrameBuffer>(planes); +} + +/** + * \class DmaSyncer + * \brief Helper class for dma-buf's synchronization + * + * This class wraps a userspace dma-buf's synchronization process with an + * object's lifetime. + * + * It's used when the user needs to access a dma-buf with CPU, mostly mapped + * with MappedFrameBuffer, so that the buffer is synchronized between CPU and + * ISP. + */ + +/** + * \enum DmaSyncer::SyncType + * \brief Read and/or write access via the CPU map + * \var DmaSyncer::Read + * \brief Indicates that the mapped dma-buf will be read by the client via the + * CPU map + * \var DmaSyncer::Write + * \brief Indicates that the mapped dm-buf will be written by the client via the + * CPU map + * \var DmaSyncer::ReadWrite + * \brief Indicates that the mapped dma-buf will be read and written by the + * client via the CPU map + */ + +/** + * \brief Construct a DmaSyncer with a dma-buf's fd and the access type + * \param[in] fd The dma-buf's file descriptor to synchronize + * \param[in] type Read and/or write access via the CPU map + */ +DmaSyncer::DmaSyncer(SharedFD fd, SyncType type) + : fd_(fd) +{ + switch (type) { + case SyncType::Read: + flags_ = DMA_BUF_SYNC_READ; + break; + case SyncType::Write: + flags_ = DMA_BUF_SYNC_WRITE; + break; + case SyncType::ReadWrite: + flags_ = DMA_BUF_SYNC_RW; + break; + } + + sync(DMA_BUF_SYNC_START); +} + +/** + * \fn DmaSyncer::DmaSyncer(DmaSyncer &&other); + * \param[in] other The other instance + * \brief Enable move on class DmaSyncer + */ + +/** + * \fn DmaSyncer::operator=(DmaSyncer &&other); + * \param[in] other The other instance + * \brief Enable move on class DmaSyncer + */ + +DmaSyncer::~DmaSyncer() +{ + /* + * DmaSyncer might be moved and left with an empty SharedFD. + * Avoid syncing with an invalid file descriptor in this case. + */ + if (fd_.isValid()) + sync(DMA_BUF_SYNC_END); +} + +void DmaSyncer::sync(uint64_t step) +{ + struct dma_buf_sync sync = { + .flags = flags_ | step + }; + + int ret; + do { + ret = ioctl(fd_.get(), DMA_BUF_IOCTL_SYNC, &sync); + } while (ret && (errno == EINTR || errno == EAGAIN)); + + if (ret) { + ret = errno; + LOG(DmaBufAllocator, Error) + << "Unable to sync dma fd: " << fd_.get() + << ", err: " << strerror(ret) + << ", flags: " << sync.flags; + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/fence.cpp b/src/libcamera/fence.cpp index 7b784778..73299b40 100644 --- a/src/libcamera/fence.cpp +++ b/src/libcamera/fence.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * fence.cpp - Synchronization fence + * Synchronization fence */ #include "libcamera/fence.h" @@ -11,7 +11,7 @@ namespace libcamera { /** * - * \file libcamera/fence.h + * \file fence.h * \brief Definition of the Fence class */ diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp index 447e6238..bfcdfc08 100644 --- a/src/libcamera/formats.cpp +++ b/src/libcamera/formats.cpp @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * formats.cpp - libcamera image formats + * libcamera image formats */ #include "libcamera/internal/formats.h" -#include <algorithm> -#include <errno.h> +#include <map> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> @@ -16,7 +15,7 @@ #include <libcamera/formats.h> /** - * \file internal/formats.h + * \file libcamera/internal/formats.h * \brief Types and helper functions to handle libcamera image formats */ @@ -33,7 +32,7 @@ LOG_DEFINE_CATEGORY(Formats) * used in pipeline handlers. * * \var PixelFormatInfo::name - * \brief The format name as a human-readable string, used as the test + * \brief The format name as a human-readable string, used as the text * representation of the PixelFormat * * \var PixelFormatInfo::format @@ -49,9 +48,9 @@ LOG_DEFINE_CATEGORY(Formats) * \var PixelFormatInfo::bitsPerPixel * \brief The average number of bits per pixel * - * The number per pixel averages the total number of bits for all colour - * components over the whole image, excluding any padding bits or padding - * pixels. + * The number of bits per pixel averages the total number of bits for all + * colour components over the whole image, excluding any padding bits or + * padding pixels. * * For formats that store pixels with bit padding within words, only the * effective bits are taken into account. For instance, 12-bit Bayer data @@ -158,7 +157,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .colourEncoding = PixelFormatInfo::ColourEncodingRGB, .packed = false, .pixelsPerGroup = 1, - .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }}, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, } }, { formats::RGB565_BE, { .name = "RGB565_BE", @@ -168,7 +167,7 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .colourEncoding = PixelFormatInfo::ColourEncodingRGB, .packed = false, .pixelsPerGroup = 1, - .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }}, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, } }, { formats::BGR888, { .name = "BGR888", @@ -270,6 +269,26 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .pixelsPerGroup = 1, .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }}, } }, + { formats::BGR161616, { + .name = "BGR161616", + .format = formats::BGR161616, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_RGB48), }, + .bitsPerPixel = 48, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + .packed = false, + .pixelsPerGroup = 1, + .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::RGB161616, { + .name = "RGB161616", + .format = formats::RGB161616, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_BGR48), }, + .bitsPerPixel = 48, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + .packed = false, + .pixelsPerGroup = 1, + .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, /* YUV packed formats. */ { formats::YUYV, { @@ -497,6 +516,26 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .pixelsPerGroup = 1, .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, } }, + { formats::R10_CSI2P, { + .name = "R10_CSI2P", + .format = formats::R10_CSI2P, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y10P), }, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + .packed = true, + .pixelsPerGroup = 4, + .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::R12_CSI2P, { + .name = "R12_CSI2P", + .format = formats::R12_CSI2P, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y12P), }, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + .packed = true, + .pixelsPerGroup = 2, + .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, { formats::R12, { .name = "R12", .format = formats::R12, @@ -507,15 +546,25 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .pixelsPerGroup = 1, .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, } }, - { formats::R10_CSI2P, { - .name = "R10_CSI2P", - .format = formats::R10_CSI2P, - .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y10P), }, - .bitsPerPixel = 10, + { formats::R16, { + .name = "R16", + .format = formats::R16, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y16), }, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + .packed = false, + .pixelsPerGroup = 1, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::MONO_PISP_COMP1, { + .name = "MONO_PISP_COMP1", + .format = formats::MONO_PISP_COMP1, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_MONO), }, + .bitsPerPixel = 8, .colourEncoding = PixelFormatInfo::ColourEncodingYUV, .packed = true, - .pixelsPerGroup = 4, - .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }}, + .pixelsPerGroup = 1, + .planes = {{ { 1, 1 }, { 0, 0 }, { 0, 0 } }}, } }, /* Bayer formats. */ @@ -880,7 +929,46 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{ .pixelsPerGroup = 25, .planes = {{ { 32, 1 }, { 0, 0 }, { 0, 0 } }}, } }, - + { formats::BGGR_PISP_COMP1, { + .name = "BGGR_PISP_COMP1", + .format = formats::BGGR_PISP_COMP1, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_BGGR), }, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + .packed = true, + .pixelsPerGroup = 2, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::GBRG_PISP_COMP1, { + .name = "GBRG_PISP_COMP1", + .format = formats::GBRG_PISP_COMP1, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GBRG), }, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + .packed = true, + .pixelsPerGroup = 2, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::GRBG_PISP_COMP1, { + .name = "GRBG_PISP_COMP1", + .format = formats::GRBG_PISP_COMP1, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GRBG), }, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + .packed = true, + .pixelsPerGroup = 2, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, + { formats::RGGB_PISP_COMP1, { + .name = "RGGB_PISP_COMP1", + .format = formats::RGGB_PISP_COMP1, + .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_RGGB), }, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + .packed = true, + .pixelsPerGroup = 2, + .planes = {{ { 2, 1 }, { 0, 0 }, { 0, 0 } }}, + } }, /* Compressed formats. */ { formats::MJPEG, { .name = "MJPEG", @@ -987,7 +1075,7 @@ unsigned int PixelFormatInfo::stride(unsigned int width, unsigned int plane, return 0; } - if (plane > planes.size() || !planes[plane].bytesPerGroup) { + if (plane >= planes.size() || !planes[plane].bytesPerGroup) { LOG(Formats, Warning) << "Invalid plane index, stride is zero"; return 0; } diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml index 539ac0b3..2d54d391 100644 --- a/src/libcamera/formats.yaml +++ b/src/libcamera/formats.yaml @@ -11,6 +11,8 @@ formats: fourcc: DRM_FORMAT_R10 - R12: fourcc: DRM_FORMAT_R12 + - R16: + fourcc: DRM_FORMAT_R16 - RGB565: fourcc: DRM_FORMAT_RGB565 @@ -41,6 +43,11 @@ formats: - BGRA8888: fourcc: DRM_FORMAT_BGRA8888 + - RGB161616: + fourcc: DRM_FORMAT_RGB161616 + - BGR161616: + fourcc: DRM_FORMAT_BGR161616 + - YUYV: fourcc: DRM_FORMAT_YUYV - YVYU: @@ -131,6 +138,9 @@ formats: - R10_CSI2P: fourcc: DRM_FORMAT_R10 mod: MIPI_FORMAT_MOD_CSI2_PACKED + - R12_CSI2P: + fourcc: DRM_FORMAT_R12 + mod: MIPI_FORMAT_MOD_CSI2_PACKED - SRGGB10_CSI2P: fourcc: DRM_FORMAT_SRGGB10 @@ -183,4 +193,20 @@ formats: - SBGGR10_IPU3: fourcc: DRM_FORMAT_SBGGR10 mod: IPU3_FORMAT_MOD_PACKED + + - RGGB_PISP_COMP1: + fourcc: DRM_FORMAT_SRGGB16 + mod: PISP_FORMAT_MOD_COMPRESS_MODE1 + - GRBG_PISP_COMP1: + fourcc: DRM_FORMAT_SGRBG16 + mod: PISP_FORMAT_MOD_COMPRESS_MODE1 + - GBRG_PISP_COMP1: + fourcc: DRM_FORMAT_SGBRG16 + mod: PISP_FORMAT_MOD_COMPRESS_MODE1 + - BGGR_PISP_COMP1: + fourcc: DRM_FORMAT_SBGGR16 + mod: PISP_FORMAT_MOD_COMPRESS_MODE1 + - MONO_PISP_COMP1: + fourcc: DRM_FORMAT_R16 + mod: PISP_FORMAT_MOD_COMPRESS_MODE1 ... diff --git a/src/libcamera/framebuffer.cpp b/src/libcamera/framebuffer.cpp index 5a7f3c0b..826848f7 100644 --- a/src/libcamera/framebuffer.cpp +++ b/src/libcamera/framebuffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * framebuffer.cpp - Frame buffer handling + * Frame buffer handling */ #include <libcamera/framebuffer.h> @@ -16,7 +16,10 @@ /** * \file libcamera/framebuffer.h * \brief Frame buffer handling - * + */ + +/** + * \internal * \file libcamera/internal/framebuffer.h * \brief Internal frame buffer handling support */ @@ -104,6 +107,7 @@ LOG_DEFINE_CATEGORY(Buffer) * \return The array of per-plane metadata */ +#ifndef __DOXYGEN_PUBLIC__ /** * \class FrameBuffer::Private * \brief Base class for FrameBuffer private data @@ -206,6 +210,7 @@ FrameBuffer::Private::~Private() * \brief Retrieve the dynamic metadata * \return Dynamic metadata for the frame contained in the buffer */ +#endif /* __DOXYGEN_PUBLIC__ */ /** * \class FrameBuffer diff --git a/src/libcamera/framebuffer_allocator.cpp b/src/libcamera/framebuffer_allocator.cpp index dabd9219..3d53bde2 100644 --- a/src/libcamera/framebuffer_allocator.cpp +++ b/src/libcamera/framebuffer_allocator.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * framebuffer_allocator.cpp - FrameBuffer allocator + * FrameBuffer allocator */ #include <libcamera/framebuffer_allocator.h> @@ -59,14 +59,11 @@ LOG_DEFINE_CATEGORY(Allocator) * \param[in] camera The camera */ FrameBufferAllocator::FrameBufferAllocator(std::shared_ptr<Camera> camera) - : camera_(camera) + : camera_(std::move(camera)) { } -FrameBufferAllocator::~FrameBufferAllocator() -{ - buffers_.clear(); -} +FrameBufferAllocator::~FrameBufferAllocator() = default; /** * \brief Allocate buffers for a configured stream @@ -100,6 +97,10 @@ int FrameBufferAllocator::allocate(Stream *stream) LOG(Allocator, Error) << "Stream is not part of " << camera_->id() << " active configuration"; + + if (ret < 0) + buffers_.erase(it); + return ret; } @@ -121,8 +122,6 @@ int FrameBufferAllocator::free(Stream *stream) if (iter == buffers_.end()) return -EINVAL; - std::vector<std::unique_ptr<FrameBuffer>> &buffers = iter->second; - buffers.clear(); buffers_.erase(iter); return 0; diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp index e50b46c5..81cc8cd5 100644 --- a/src/libcamera/geometry.cpp +++ b/src/libcamera/geometry.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * geometry.cpp - Geometry-related structures + * Geometry-related structures */ #include <libcamera/geometry.h> @@ -95,10 +95,10 @@ std::ostream &operator<<(std::ostream &out, const Point &p) } /** - * \struct Size + * \class Size * \brief Describe a two-dimensional size * - * The Size structure defines a two-dimensional size with integer precision. + * The Size class defines a two-dimensional size with integer precision. */ /** @@ -455,7 +455,7 @@ std::ostream &operator<<(std::ostream &out, const Size &s) } /** - * \struct SizeRange + * \class SizeRange * \brief Describe a range of sizes * * A SizeRange describes a range of sizes included in the [min, max] interval @@ -589,11 +589,13 @@ std::ostream &operator<<(std::ostream &out, const SizeRange &sr) } /** - * \struct Rectangle + * \class Rectangle * \brief Describe a rectangle's position and dimensions * * Rectangles are used to identify an area of an image. They are specified by * the coordinates of top-left corner and their horizontal and vertical size. + * By convention, the top-left corner is defined as the corner with the lowest + * x and y coordinates, regardless of the origin and direction of the axes. * * The measure unit of the rectangle coordinates and size, as well as the * reference point from which the Rectangle::x and Rectangle::y displacements @@ -611,6 +613,8 @@ std::ostream &operator<<(std::ostream &out, const SizeRange &sr) * \param[in] x The horizontal coordinate of the top-left corner * \param[in] y The vertical coordinate of the top-left corner * \param[in] size The size + * + * The rectangle's top-left corner is the point with the smaller x and y values. */ /** @@ -620,6 +624,8 @@ std::ostream &operator<<(std::ostream &out, const SizeRange &sr) * \param[in] y The vertical coordinate of the top-left corner * \param[in] width The width * \param[in] height The height + * + * The rectangle's top-left corner is the point with the smaller x and y values. */ /** @@ -630,13 +636,24 @@ std::ostream &operator<<(std::ostream &out, const SizeRange &sr) */ /** + * \fn Rectangle::Rectangle(const Point &point1, const Point &point2) + * \brief Construct a Rectangle from two opposite corners + * \param[in] point1 One of corners of the rectangle + * \param[in] point2 The opposite corner of \a point1 + */ + +/** * \var Rectangle::x * \brief The horizontal coordinate of the rectangle's top-left corner + * + * The rectangle's top-left corner is the point with the smaller x and y values. */ /** * \var Rectangle::y * \brief The vertical coordinate of the rectangle's top-left corner + * + * The rectangle's top-left corner is the point with the smaller x and y values. */ /** @@ -685,6 +702,9 @@ Point Rectangle::center() const /** * \fn Point Rectangle::topLeft() const * \brief Retrieve the coordinates of the top left corner of this Rectangle + * + * The rectangle's top-left corner is the point with the smaller x and y values. + * * \return The Rectangle's top left corner */ @@ -818,6 +838,55 @@ Rectangle Rectangle::translatedBy(const Point &point) const } /** + * \brief Transform a Rectangle from one reference rectangle to another + * \param[in] source The \a source reference rectangle + * \param[in] destination The \a destination reference rectangle + * + * The \a source and \a destination parameters describe two rectangles defined + * in different reference systems. The Rectangle is translated from the source + * reference system into the destination reference system. + * + * The typical use case for this function is to translate a selection rectangle + * specified in a reference system, in example the sensor's pixel array, into + * the same rectangle re-scaled and translated into a different reference + * system, in example the output frame on which the selection rectangle is + * applied to. + * + * For example, consider a sensor with a resolution of 4040x2360 pixels and a + * assume a rectangle of (100, 100)/3840x2160 (sensorFrame) in sensor + * coordinates is mapped to a rectangle (0,0)/(1920,1080) (displayFrame) in + * display coordinates. This function can be used to transform an arbitrary + * rectangle from display coordinates to sensor coordinates or vice versa: + * + * \code{.cpp} + * Rectangle sensorReference(100, 100, 3840, 2160); + * Rectangle displayReference(0, 0, 1920, 1080); + * + * // Bottom right quarter in sensor coordinates + * Rectangle sensorRect(2020, 100, 1920, 1080); + * displayRect = sensorRect.transformedBetween(sensorReference, displayReference); + * // displayRect is now (960, 540)/960x540 + * + * // Transformation back to sensor coordinates + * sensorRect = displayRect.transformedBetween(displayReference, sensorReference); + * \endcode + */ +Rectangle Rectangle::transformedBetween(const Rectangle &source, + const Rectangle &destination) const +{ + Rectangle r; + double sx = static_cast<double>(destination.width) / source.width; + double sy = static_cast<double>(destination.height) / source.height; + + r.x = static_cast<int>((x - source.x) * sx) + destination.x; + r.y = static_cast<int>((y - source.y) * sy) + destination.y; + r.width = static_cast<int>(width * sx); + r.height = static_cast<int>(height * sy); + + return r; +} + +/** * \brief Compare rectangles for equality * \return True if the two rectangles are equal, false otherwise */ diff --git a/src/libcamera/ipa/meson.build b/src/libcamera/ipa/meson.build index 44695240..ef73b3f9 100644 --- a/src/libcamera/ipa/meson.build +++ b/src/libcamera/ipa/meson.build @@ -3,13 +3,10 @@ libcamera_ipa_interfaces = [] foreach file : ipa_mojom_files - name = '@0@'.format(file).split('/')[-1].split('.')[0] - # {pipeline}_ipa_interface.cpp libcamera_ipa_interfaces += \ - custom_target(name + '_ipa_interface_cpp', - input : file, - output : name + '_ipa_interface.cpp', + custom_target(input : file, + output : '@BASENAME@_ipa_interface.cpp', command : [ mojom_docs_extractor, '-o', '@OUTPUT@', '@INPUT@' diff --git a/src/libcamera/ipa_controls.cpp b/src/libcamera/ipa_controls.cpp index 870a443b..12d92ebe 100644 --- a/src/libcamera/ipa_controls.cpp +++ b/src/libcamera/ipa_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_controls.cpp - IPA control handling + * IPA control handling */ #include <libcamera/ipa/ipa_controls.h> @@ -220,6 +220,10 @@ static_assert(sizeof(ipa_control_value_entry) == 16, * \var ipa_control_info_entry::offset * The offset in bytes from the beginning of the data section to the control * info data (shall be a multiple of 8 bytes) + * \var ipa_control_info_entry::direction + * The directions in which the control is allowed to be sent. This is a flags + * value, where 0x1 signifies input (as controls), and 0x2 signifies output (as + * metadata). \sa ControlId::Direction * \var ipa_control_info_entry::padding * Padding bytes (shall be set to 0) */ diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp index 0a259305..0537f785 100644 --- a/src/libcamera/ipa_data_serializer.cpp +++ b/src/libcamera/ipa_data_serializer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipa_data_serializer.cpp - Image Processing Algorithm data serializer + * Image Processing Algorithm data serializer */ #include "libcamera/internal/ipa_data_serializer.h" @@ -11,6 +11,8 @@ #include <libcamera/base/log.h> +#include "libcamera/internal/byte_stream_buffer.h" + /** * \file ipa_data_serializer.h * \brief IPA Data Serializer @@ -194,7 +196,6 @@ IPADataSerializer<type>::serialize(const type &data, \ [[maybe_unused]] ControlSerializer *cs) \ { \ std::vector<uint8_t> dataVec; \ - dataVec.reserve(sizeof(type)); \ appendPOD<type>(dataVec, data); \ \ return { dataVec, {} }; \ @@ -537,7 +538,6 @@ IPADataSerializer<SharedFD>::serialize(const SharedFD &data, if (data.isValid()) fdVec.push_back(data); - return { dataVec, fdVec }; } @@ -604,7 +604,7 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(std::vector<uint8_t>::const_i FrameBuffer::Plane ret; ret.fd = IPADataSerializer<SharedFD>::deserialize(dataBegin, dataBegin + 4, - fdsBegin, fdsBegin + 1); + fdsBegin, fdsBegin + 1); ret.offset = readPOD<uint32_t>(dataBegin, 4, dataEnd); ret.length = readPOD<uint32_t>(dataBegin, 8, dataEnd); diff --git a/src/libcamera/ipa_interface.cpp b/src/libcamera/ipa_interface.cpp index 8ea6cbee..a9dc54ad 100644 --- a/src/libcamera/ipa_interface.cpp +++ b/src/libcamera/ipa_interface.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_interface.cpp - Image Processing Algorithm interface + * Image Processing Algorithm interface */ #include <libcamera/ipa/ipa_interface.h> diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index 7a4515d9..830750dc 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_manager.cpp - Image Processing Algorithm module manager + * Image Processing Algorithm module manager */ #include "libcamera/internal/ipa_manager.h" @@ -95,8 +95,6 @@ LOG_DEFINE_CATEGORY(IPAManager) * IPC. */ -IPAManager *IPAManager::self_ = nullptr; - /** * \brief Construct an IPAManager instance * @@ -105,10 +103,6 @@ IPAManager *IPAManager::self_ = nullptr; */ IPAManager::IPAManager() { - if (self_) - LOG(IPAManager, Fatal) - << "Multiple IPAManager objects are not allowed"; - #if HAVE_IPA_PUBKEY if (!pubKey_.isValid()) LOG(IPAManager, Warning) << "Public key not valid"; @@ -153,17 +147,9 @@ IPAManager::IPAManager() if (!ipaCount) LOG(IPAManager, Warning) << "No IPA found in '" IPA_MODULE_DIR "'"; - - self_ = this; } -IPAManager::~IPAManager() -{ - for (IPAModule *module : modules_) - delete module; - - self_ = nullptr; -} +IPAManager::~IPAManager() = default; /** * \brief Identify shared library objects within a directory @@ -236,15 +222,13 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) unsigned int count = 0; for (const std::string &file : files) { - IPAModule *ipaModule = new IPAModule(file); - if (!ipaModule->isValid()) { - delete ipaModule; + auto ipaModule = std::make_unique<IPAModule>(file); + if (!ipaModule->isValid()) continue; - } LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'"; - modules_.push_back(ipaModule); + modules_.push_back(std::move(ipaModule)); count++; } @@ -260,9 +244,9 @@ unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth) IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion, uint32_t maxVersion) { - for (IPAModule *module : modules_) { + for (const auto &module : modules_) { if (module->match(pipe, minVersion, maxVersion)) - return module; + return module.get(); } return nullptr; diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp index f2dd87e5..e6ea61e4 100644 --- a/src/libcamera/ipa_module.cpp +++ b/src/libcamera/ipa_module.cpp @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_module.cpp - Image Processing Algorithm module + * Image Processing Algorithm module */ #include "libcamera/internal/ipa_module.h" #include <algorithm> -#include <array> #include <ctype.h> #include <dlfcn.h> #include <elf.h> @@ -51,8 +50,8 @@ typename std::remove_extent_t<T> *elfPointer(Span<const uint8_t> elf, if (size > elf.size() || size < objSize) return nullptr; - return reinterpret_cast<typename std::remove_extent_t<T> *> - (reinterpret_cast<const char *>(elf.data()) + offset); + return reinterpret_cast<typename std::remove_extent_t<T> *>( + reinterpret_cast<const char *>(elf.data()) + offset); } template<typename T> @@ -374,7 +373,7 @@ const struct IPAModuleInfo &IPAModule::info() const * * \return The IPA module signature */ -const std::vector<uint8_t> IPAModule::signature() const +const std::vector<uint8_t> &IPAModule::signature() const { return signature_; } diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp index 3f2cc6b8..9907b961 100644 --- a/src/libcamera/ipa_proxy.cpp +++ b/src/libcamera/ipa_proxy.cpp @@ -2,12 +2,11 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_proxy.cpp - Image Processing Algorithm proxy + * Image Processing Algorithm proxy */ #include "libcamera/internal/ipa_proxy.h" -#include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -72,6 +71,7 @@ IPAProxy::~IPAProxy() /** * \brief Retrieve the absolute path to an IPA configuration file * \param[in] name The configuration file name + * \param[in] fallbackName The name of a fallback configuration file * * This function locates the configuration file for an IPA and returns its * absolute path. It searches the following directories, in order: @@ -89,21 +89,42 @@ IPAProxy::~IPAProxy() * named after the IPA module name, as reported in IPAModuleInfo::name, and for * a file named \a name within that directory. The \a name is IPA-specific. * + * If the file named \a name is not found and \a fallbackName is non-empty then + * the whole search is repeated for \a fallbackName. + * * \return The full path to the IPA configuration file, or an empty string if * no configuration file can be found */ -std::string IPAProxy::configurationFile(const std::string &name) const +std::string IPAProxy::configurationFile(const std::string &name, + const std::string &fallbackName) const { - struct stat statbuf; - int ret; - /* * The IPA module name can be used as-is to build directory names as it * has been validated when loading the module. */ - std::string ipaName = ipam_->info().name; + const std::string ipaName = ipam_->info().name; + + /* + * Start with any user override through the module-specific environment + * variable. Use the name of the IPA module up to the first '/' to + * construct the variable name. + */ + std::string ipaEnvName = ipaName.substr(0, ipaName.find('/')); + std::transform(ipaEnvName.begin(), ipaEnvName.end(), ipaEnvName.begin(), + [](unsigned char c) { return std::toupper(c); }); + ipaEnvName = "LIBCAMERA_" + ipaEnvName + "_TUNING_FILE"; + + char const *configFromEnv = utils::secure_getenv(ipaEnvName.c_str()); + if (configFromEnv && *configFromEnv != '\0') + return { configFromEnv }; + + struct stat statbuf; + int ret; - /* Check the environment variable first. */ + /* + * Check the directory pointed to by the IPA config path environment + * variable next. + */ const char *confPaths = utils::secure_getenv("LIBCAMERA_IPA_CONFIG_PATH"); if (confPaths) { for (const auto &dir : utils::split(confPaths, ":")) { @@ -146,11 +167,18 @@ std::string IPAProxy::configurationFile(const std::string &name) const } } - LOG(IPAProxy, Error) - << "Configuration file '" << name - << "' not found for IPA module '" << ipaName << "'"; + if (fallbackName.empty()) { + LOG(IPAProxy, Error) + << "Configuration file '" << name + << "' not found for IPA module '" << ipaName << "'"; + return std::string(); + } - return std::string(); + LOG(IPAProxy, Warning) + << "Configuration file '" << name + << "' not found for IPA module '" << ipaName + << "', falling back to '" << fallbackName << "'"; + return configurationFile(fallbackName); } /** diff --git a/src/libcamera/ipa_pub_key.cpp.in b/src/libcamera/ipa_pub_key.cpp.in index 01e5333b..5d8c92c2 100644 --- a/src/libcamera/ipa_pub_key.cpp.in +++ b/src/libcamera/ipa_pub_key.cpp.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Laurent Pinchart <laurent.pinchart@ideasonboard.com> * - * ipa_pub_key.cpp - IPA module signing public key + * IPA module signing public key * * This file is auto-generated. Do not edit. */ diff --git a/src/libcamera/ipc_pipe.cpp b/src/libcamera/ipc_pipe.cpp index 31a0ca09..548299d0 100644 --- a/src/libcamera/ipc_pipe.cpp +++ b/src/libcamera/ipc_pipe.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipc_pipe.cpp - Image Processing Algorithm IPC module for IPA proxies + * Image Processing Algorithm IPC module for IPA proxies */ #include "libcamera/internal/ipc_pipe.h" diff --git a/src/libcamera/ipc_pipe_unixsocket.cpp b/src/libcamera/ipc_pipe_unixsocket.cpp index da2cffc3..668ec73b 100644 --- a/src/libcamera/ipc_pipe_unixsocket.cpp +++ b/src/libcamera/ipc_pipe_unixsocket.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipc_pipe_unixsocket.cpp - Image Processing Algorithm IPC module using unix socket + * Image Processing Algorithm IPC module using unix socket */ #include "libcamera/internal/ipc_pipe_unixsocket.h" diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp index 1980d374..002053e3 100644 --- a/src/libcamera/ipc_unixsocket.cpp +++ b/src/libcamera/ipc_unixsocket.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipc_unixsocket.cpp - IPC mechanism based on Unix sockets + * IPC mechanism based on Unix sockets */ #include "libcamera/internal/ipc_unixsocket.h" @@ -12,6 +12,7 @@ #include <string.h> #include <sys/socket.h> #include <unistd.h> +#include <vector> #include <libcamera/base/event_notifier.h> #include <libcamera/base/log.h> @@ -247,10 +248,9 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length, iov[0].iov_base = const_cast<void *>(buffer); iov[0].iov_len = length; - char buf[CMSG_SPACE(num * sizeof(uint32_t))]; - memset(buf, 0, sizeof(buf)); + std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t))); - struct cmsghdr *cmsg = (struct cmsghdr *)buf; + struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data()); cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; @@ -283,10 +283,9 @@ int IPCUnixSocket::recvData(void *buffer, size_t length, iov[0].iov_base = buffer; iov[0].iov_len = length; - char buf[CMSG_SPACE(num * sizeof(uint32_t))]; - memset(buf, 0, sizeof(buf)); + std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t))); - struct cmsghdr *cmsg = (struct cmsghdr *)buf; + struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data()); cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp index 6860069b..f54bbf21 100644 --- a/src/libcamera/mapped_framebuffer.cpp +++ b/src/libcamera/mapped_framebuffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * mapped_framebuffer.cpp - Mapped Framebuffer support + * Mapped Framebuffer support */ #include "libcamera/internal/mapped_framebuffer.h" @@ -16,7 +16,7 @@ #include <libcamera/base/log.h> /** - * \file libcamera/internal/mapped_framebuffer.h + * \file mapped_framebuffer.h * \brief Frame buffer memory mapping support */ @@ -72,7 +72,7 @@ MappedBuffer::MappedBuffer(MappedBuffer &&other) /** * \brief Move assignment operator, replace the mappings with those of \a other -* \param[in] other The other MappedBuffer + * \param[in] other The other MappedBuffer * * Moving a MappedBuffer moves the mappings contained in the \a other to the new * MappedBuffer and invalidates the \a other. diff --git a/src/libcamera/matrix.cpp b/src/libcamera/matrix.cpp new file mode 100644 index 00000000..b7c07e89 --- /dev/null +++ b/src/libcamera/matrix.cpp @@ -0,0 +1,333 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Matrix and related operations + */ + +#include "libcamera/internal/matrix.h" + +#include <algorithm> +#include <assert.h> +#include <cmath> +#include <numeric> +#include <vector> + +#include <libcamera/base/log.h> + +/** + * \file matrix.h + * \brief Matrix class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Matrix) + +/** + * \class Matrix + * \brief Matrix class + * \tparam T Type of numerical values to be stored in the matrix + * \tparam Rows Number of rows in the matrix + * \tparam Cols Number of columns in the matrix + */ + +/** + * \fn Matrix::Matrix() + * \brief Construct a zero matrix + */ + +/** + * \fn Matrix::Matrix(const std::array<T, Rows * Cols> &data) + * \brief Construct a matrix from supplied data + * \param[in] data Data from which to construct a matrix + * + * \a data is a one-dimensional vector and will be turned into a matrix in + * row-major order. The size of \a data must be equal to the product of the + * number of rows and columns of the matrix (Rows x Cols). + */ + +/** + * \fn Matrix::Matrix(const Span<const T, Rows * Cols> data) + * \brief Construct a matrix from supplied data + * \param[in] data Data from which to construct a matrix + * + * \a data is a one-dimensional Span and will be turned into a matrix in + * row-major order. The size of \a data must be equal to the product of the + * number of rows and columns of the matrix (Rows x Cols). + */ + +/** + * \fn Matrix::identity() + * \brief Construct an identity matrix + */ + +/** + * \fn Matrix::toString() + * \brief Assemble and return a string describing the matrix + * \return A string describing the matrix + */ + +/** + * \fn Matrix::data() + * \brief Access the matrix data as a linear array + * + * Access the contents of the matrix as a one-dimensional linear array of + * values in row-major order. The size of the array is equal to the product of + * the number of rows and columns of the matrix (Rows x Cols). + * + * \return A span referencing the matrix data as a linear array + */ + +/** + * \fn Span<const T, Cols> Matrix::operator[](size_t i) const + * \brief Index to a row in the matrix + * \param[in] i Index of row to retrieve + * + * This operator[] returns a Span, which can then be indexed into again with + * another operator[], allowing a convenient m[i][j] to access elements of the + * matrix. Note that the lifetime of the Span returned by this first-level + * operator[] is bound to that of the Matrix itself, so it is not recommended + * to save the Span that is the result of this operator[]. + * + * \return Row \a i from the matrix, as a Span + */ + +/** + * \fn Matrix::inverse(bool *ok) const + * \param[out] ok Indicate if the matrix was successfully inverted + * \brief Compute the inverse of the matrix + * + * This function computes the inverse of the matrix. It is only implemented for + * matrices of float and double types. If \a ok is provided it will be set to a + * boolean value to indicate of the inversion was successful. This can be used + * to check if the matrix is singular, in which case the function will return + * an identity matrix. + * + * \return The inverse of the matrix + */ + +/** + * \fn Matrix::operator[](size_t i) + * \copydoc Matrix::operator[](size_t i) const + */ + +/** + * \fn Matrix<T, Rows, Cols> &Matrix::operator*=(U d) + * \brief Multiply the matrix by a scalar in-place + * \tparam U Type of the numerical scalar value + * \param d The scalar multiplier + * \return Product of this matrix and scalar \a d + */ + +/** + * \fn Matrix::Matrix<U, Rows, Cols> operator*(T d, const Matrix<U, Rows, Cols> &m) + * \brief Multiply the matrix by a scalar + * \tparam T Type of the numerical scalar value + * \tparam U Type of numerical values in the matrix + * \tparam Rows Number of rows in the matrix + * \tparam Cols Number of columns in the matrix + * \param d The scalar multiplier + * \param m The matrix + * \return Product of scalar \a d and matrix \a m + */ + +/** + * \fn Matrix::Matrix<U, Rows, Cols> operator*(const Matrix<U, Rows, Cols> &m, T d) + * \copydoc operator*(T d, const Matrix<U, Rows, Cols> &m) + */ + +/** + * \fn operator*(const Matrix<T1, R1, C1> &m1, const Matrix<T2, R2, C2> &m2) + * \brief Matrix multiplication + * \tparam T1 Type of numerical values in the first matrix + * \tparam R1 Number of rows in the first matrix + * \tparam C1 Number of columns in the first matrix + * \tparam T2 Type of numerical values in the secont matrix + * \tparam R2 Number of rows in the second matrix + * \tparam C2 Number of columns in the second matrix + * \param m1 Multiplicand matrix + * \param m2 Multiplier matrix + * \return Matrix product of matrices \a m1 and \a m2 + */ + +/** + * \fn Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const Matrix<T, Rows, Cols> &m2) + * \brief Matrix addition + * \tparam T Type of numerical values in the matrices + * \tparam Rows Number of rows in the matrices + * \tparam Cols Number of columns in the matrices + * \param m1 Summand matrix + * \param m2 Summand matrix + * \return Matrix sum of matrices \a m1 and \a m2 + */ + +#ifndef __DOXYGEN__ +template<typename T> +bool matrixInvert(Span<const T> dataIn, Span<T> dataOut, unsigned int dim, + Span<T> scratchBuffer, Span<unsigned int> swapBuffer) +{ + /* + * Convenience class to access matrix data, providing a row-major (i,j) + * element accessor through the call operator, and the ability to swap + * rows without modifying the backing storage. + */ + class MatrixAccessor + { + public: + MatrixAccessor(Span<T> data, Span<unsigned int> swapBuffer, unsigned int rows, unsigned int cols) + : data_(data), swap_(swapBuffer), rows_(rows), cols_(cols) + { + ASSERT(swap_.size() == rows); + std::iota(swap_.begin(), swap_.end(), T{ 0 }); + } + + T &operator()(unsigned int row, unsigned int col) + { + assert(row < rows_ && col < cols_); + return data_[index(row, col)]; + } + + void swap(unsigned int a, unsigned int b) + { + assert(a < rows_ && a < cols_); + std::swap(swap_[a], swap_[b]); + } + + private: + unsigned int index(unsigned int row, unsigned int col) const + { + return swap_[row] * cols_ + col; + } + + Span<T> data_; + Span<unsigned int> swap_; + unsigned int rows_; + unsigned int cols_; + }; + + /* + * Matrix inversion using Gaussian elimination. + * + * Start by augmenting the original matrix with an identiy matrix of + * the same size. + */ + ASSERT(scratchBuffer.size() == dim * dim * 2); + MatrixAccessor matrix(scratchBuffer, swapBuffer, dim, dim * 2); + + for (unsigned int i = 0; i < dim; ++i) { + for (unsigned int j = 0; j < dim; ++j) { + matrix(i, j) = dataIn[i * dim + j]; + matrix(i, j + dim) = T{ 0 }; + } + matrix(i, i + dim) = T{ 1 }; + } + + /* Start by triangularizing the input . */ + for (unsigned int pivot = 0; pivot < dim; ++pivot) { + /* + * Locate the next pivot. To improve numerical stability, use + * the row with the largest value in the pivot's column. + */ + unsigned int row = pivot; + T maxValue{ 0 }; + + for (unsigned int i = pivot; i < dim; ++i) { + T value = std::abs(matrix(i, pivot)); + if (maxValue < value) { + maxValue = value; + row = i; + } + } + + /* + * If no pivot is found in the column, the matrix is not + * invertible. Return an identity matrix. + */ + if (maxValue == 0) { + std::fill(dataOut.begin(), dataOut.end(), T{ 0 }); + for (unsigned int i = 0; i < dim; ++i) + dataOut[i * dim + i] = T{ 1 }; + return false; + } + + /* Swap rows to bring the pivot in the right location. */ + matrix.swap(pivot, row); + + /* Process all rows below the pivot to zero the pivot column. */ + const T pivotValue = matrix(pivot, pivot); + + for (unsigned int i = pivot + 1; i < dim; ++i) { + const T factor = matrix(i, pivot) / pivotValue; + + /* + * We know the element in the pivot column will be 0, + * hardcode it instead of computing it. + */ + matrix(i, pivot) = T{ 0 }; + + for (unsigned int j = pivot + 1; j < dim * 2; ++j) + matrix(i, j) -= matrix(pivot, j) * factor; + } + } + + /* + * Then diagonalize the input, walking the diagonal backwards. There's + * no need to update the input matrix, as all the values we would write + * in the top-right triangle aren't used in further calculations (and + * would all by definition be zero). + */ + for (unsigned int pivot = dim - 1; pivot > 0; --pivot) { + const T pivotValue = matrix(pivot, pivot); + + for (unsigned int i = 0; i < pivot; ++i) { + const T factor = matrix(i, pivot) / pivotValue; + + for (unsigned int j = dim; j < dim * 2; ++j) + matrix(i, j) -= matrix(pivot, j) * factor; + } + } + + /* + * Finally, normalize the diagonal and store the result in the output + * data. + */ + for (unsigned int i = 0; i < dim; ++i) { + const T factor = matrix(i, i); + + for (unsigned int j = 0; j < dim; ++j) + dataOut[i * dim + j] = matrix(i, j + dim) / factor; + } + + return true; +} + +template bool matrixInvert<float>(Span<const float> dataIn, Span<float> dataOut, + unsigned int dim, Span<float> scratchBuffer, + Span<unsigned int> swapBuffer); +template bool matrixInvert<double>(Span<const double> data, Span<double> dataOut, + unsigned int dim, Span<double> scratchBuffer, + Span<unsigned int> swapBuffer); + +/* + * The YAML data shall be a list of numerical values. Its size shall be equal + * to the product of the number of rows and columns of the matrix (Rows x + * Cols). The values shall be stored in row-major order. + */ +bool matrixValidateYaml(const YamlObject &obj, unsigned int size) +{ + if (!obj.isList()) + return false; + + if (obj.size() != size) { + LOG(Matrix, Error) + << "Wrong number of values in matrix: expected " + << size << ", got " << obj.size(); + return false; + } + + return true; +} +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp index 2949816b..353f34a8 100644 --- a/src/libcamera/media_device.cpp +++ b/src/libcamera/media_device.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * media_device.cpp - Media device handler + * Media device handler */ #include "libcamera/internal/media_device.h" @@ -13,6 +13,7 @@ #include <string> #include <string.h> #include <sys/ioctl.h> +#include <tuple> #include <unistd.h> #include <vector> @@ -164,7 +165,7 @@ void MediaDevice::unlock() if (!fd_.isValid()) return; - lockf(fd_.get(), F_ULOCK, 0); + std::ignore = lockf(fd_.get(), F_ULOCK, 0); } /** @@ -793,7 +794,7 @@ void MediaDevice::fixupEntityFlags(struct media_v2_entity *entity) * low-level link setup as it performs no checks on the validity of the \a * flags, and assumes that the supplied \a flags are valid for the link (e.g. * immutable links cannot be disabled). -* + * * \sa MediaLink::setEnabled(bool enable) * * \return 0 on success or a negative error code otherwise @@ -818,22 +819,36 @@ int MediaDevice::setupLink(const MediaLink *link, unsigned int flags) if (ret) { ret = -errno; LOG(MediaDevice, Error) - << "Failed to setup link " - << source->entity()->name() << "[" - << source->index() << "] -> " - << sink->entity()->name() << "[" - << sink->index() << "]: " + << "Failed to setup link " << *link << ": " << strerror(-ret); return ret; } - LOG(MediaDevice, Debug) - << source->entity()->name() << "[" - << source->index() << "] -> " - << sink->entity()->name() << "[" - << sink->index() << "]: " << flags; + LOG(MediaDevice, Debug) << *link << ": " << flags; return 0; } +/** + * \brief Identify all entities of a common function in the MediaDevice + * \param[in] function The entity function to search for + * + * Search all entities within the graph of the MediaDevice and return + * a vector of those which match the given function. + * + * \return A vector of matching entities + */ +std::vector<MediaEntity *> MediaDevice::locateEntities(unsigned int function) +{ + std::vector<MediaEntity *> found; + + /* Gather all the entities matching the function they expose. */ + for (MediaEntity *entity : entities()) { + if (entity->function() == function) + found.push_back(entity); + } + + return found; +} + } /* namespace libcamera */ diff --git a/src/libcamera/media_object.cpp b/src/libcamera/media_object.cpp index c78f4758..3e3772a6 100644 --- a/src/libcamera/media_object.cpp +++ b/src/libcamera/media_object.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * media_object.cpp - Media device objects: entities, pads and links + * Media device objects: entities, pads and links */ #include "libcamera/internal/media_object.h" @@ -147,6 +147,31 @@ MediaLink::MediaLink(const struct media_v2_link *link, MediaPad *source, } /** + * \brief Generate a string representation of the MediaLink + * \return A string representing the MediaLink + */ +std::string MediaLink::toString() const +{ + std::stringstream ss; + ss << *this; + + return ss.str(); +} + +/** + * \brief Insert a text representation of a Link into an output stream + * \param[in] out The output stream + * \param[in] link The MediaLink + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const MediaLink &link) +{ + out << *link.source() << " -> " << *link.sink(); + + return out; +} + +/** * \fn MediaLink::source() * \brief Retrieve the link's source pad * \return The source pad at the origin of the link @@ -236,6 +261,31 @@ void MediaPad::addLink(MediaLink *link) } /** + * \brief Generate a string representation of the MediaPad + * \return A string representing the MediaPad + */ +std::string MediaPad::toString() const +{ + std::stringstream ss; + ss << *this; + + return ss.str(); +} + +/** + * \brief Insert a text representation of a MediaPad into an output stream + * \param[in] out The output stream + * \param[in] pad The MediaPad + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const MediaPad &pad) +{ + out << "'" << pad.entity()->name() << "'[" << pad.index() << "]"; + + return out; +} + +/** * \class MediaEntity * \brief The MediaEntity represents an entity in the media graph * diff --git a/src/libcamera/media_pipeline.cpp b/src/libcamera/media_pipeline.cpp new file mode 100644 index 00000000..c4e9f69b --- /dev/null +++ b/src/libcamera/media_pipeline.cpp @@ -0,0 +1,304 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Media pipeline support + */ + +#include "libcamera/internal/media_pipeline.h" + +#include <algorithm> +#include <errno.h> +#include <queue> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include <linux/media.h> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/media_object.h" +#include "libcamera/internal/v4l2_subdevice.h" + +/** + * \file media_pipeline.h + * \brief Provide a representation of a pipeline of devices using the Media + * Controller. + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(MediaPipeline) + +/** + * \class MediaPipeline + * \brief The MediaPipeline represents a set of entities that together form a + * data path for stream data. + * + * A MediaPipeline instance is constructed from a sink and a source between + * two entities in a media graph. + */ + +/** + * \brief Retrieve all source pads connected to a sink pad through active routes + * + * Examine the entity using the V4L2 Subdevice Routing API to collect all the + * source pads which are connected with an active route to the sink pad. + * + * \return A vector of source MediaPads + */ +static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink) +{ + MediaEntity *entity = sink->entity(); + std::unique_ptr<V4L2Subdevice> subdev = + std::make_unique<V4L2Subdevice>(entity); + + int ret = subdev->open(); + if (ret < 0) + return {}; + + V4L2Subdevice::Routing routing = {}; + ret = subdev->getRouting(&routing, V4L2Subdevice::ActiveFormat); + if (ret < 0) + return {}; + + std::vector<const MediaPad *> pads; + + for (const V4L2Subdevice::Route &route : routing) { + if (sink->index() != route.sink.pad || + !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + const MediaPad *pad = entity->getPadByIndex(route.source.pad); + if (!pad) { + LOG(MediaPipeline, Error) + << "Entity " << entity->name() + << " has invalid route source pad " + << route.source.pad; + return {}; + } + + pads.push_back(pad); + } + + return pads; +} + +/** + * \brief Find the path from source to sink + * + * Starting from a source entity, determine the shortest path to the target + * described by \a sink. + * + * If \a sink can not be found, or a route from source to sink can not be + * achieved an error of -ENOLINK will be returned. + * + * When successful, the MediaPipeline will internally store the representation + * of entities and links to describe the path between the two entities. + * + * \return 0 on success, a negative errno otherwise + */ +int MediaPipeline::init(MediaEntity *source, std::string_view sink) +{ + /* + * Find the shortest path between from the Camera Sensor and the + * target entity. + */ + std::unordered_set<MediaEntity *> visited; + std::queue<std::tuple<MediaEntity *, MediaPad *>> queue; + + /* Remember at each entity where we came from. */ + std::unordered_map<MediaEntity *, Entity> parents; + MediaEntity *entity = nullptr; + MediaEntity *target = nullptr; + MediaPad *sinkPad; + + queue.push({ source, nullptr }); + + while (!queue.empty()) { + std::tie(entity, sinkPad) = queue.front(); + queue.pop(); + + /* Found the target device. */ + if (entity->name() == sink) { + LOG(MediaPipeline, Debug) + << "Found Pipeline target " << entity->name(); + target = entity; + break; + } + + visited.insert(entity); + + /* + * Add direct downstream entities to the search queue. If the + * current entity supports the subdev internal routing API, + * restrict the search to downstream entities reachable through + * active routes. + */ + + std::vector<const MediaPad *> pads; + bool supportsRouting = false; + + if (sinkPad) { + pads = routedSourcePads(sinkPad); + if (!pads.empty()) + supportsRouting = true; + } + + if (pads.empty()) { + for (const MediaPad *pad : entity->pads()) { + if (!(pad->flags() & MEDIA_PAD_FL_SOURCE)) + continue; + pads.push_back(pad); + } + } + + for (const MediaPad *pad : pads) { + for (MediaLink *link : pad->links()) { + MediaEntity *next = link->sink()->entity(); + if (visited.find(next) == visited.end()) { + queue.push({ next, link->sink() }); + + Entity e{ entity, supportsRouting, sinkPad, pad, link }; + parents.insert({ next, e }); + } + } + } + } + + if (!target) { + LOG(MediaPipeline, Error) + << "Failed to connect " << source->name() + << " to " << sink; + return -ENOLINK; + } + + /* + * With the parents, we can follow back our way from the capture device + * to the sensor. Store all the entities in the pipeline, from the + * camera sensor to the video node, in entities_. + */ + entities_.push_front({ entity, false, sinkPad, nullptr, nullptr }); + + for (auto it = parents.find(entity); it != parents.end(); + it = parents.find(entity)) { + const Entity &e = it->second; + entities_.push_front(e); + entity = e.entity; + } + + LOG(MediaPipeline, Info) + << "Found pipeline: " + << utils::join(entities_, " -> ", + [](const Entity &e) { + std::string s = "["; + if (e.sink) + s += std::to_string(e.sink->index()) + "|"; + s += e.entity->name(); + if (e.source) + s += "|" + std::to_string(e.source->index()); + s += "]"; + return s; + }); + + return 0; +} + +/** + * \brief Initialise and enable all links through the MediaPipeline + * \return 0 on success, or a negative errno otherwise + */ +int MediaPipeline::initLinks() +{ + int ret = 0; + + MediaLink *sinkLink = nullptr; + for (Entity &e : entities_) { + /* Sensor entities have no connected sink. */ + if (!sinkLink) { + sinkLink = e.sourceLink; + continue; + } + + LOG(MediaPipeline, Debug) << "Enabling : " << *sinkLink; + + if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) { + ret = sinkLink->setEnabled(true); + if (ret < 0) + return ret; + } + + sinkLink = e.sourceLink; + } + + return ret; +} + +/** + * \brief Configure the entities of this MediaPipeline + * + * Propagate formats through each of the entities of the Pipeline, validating + * that each one was not adjusted by the driver from the desired format. + * + * \return 0 on success or a negative errno otherwise + */ +int MediaPipeline::configure(CameraSensor *sensor, V4L2SubdeviceFormat *format) +{ + int ret; + + for (const Entity &e : entities_) { + /* The sensor is configured through the CameraSensor */ + if (!e.sourceLink) + break; + + MediaLink *link = e.sourceLink; + MediaPad *source = link->source(); + MediaPad *sink = link->sink(); + + /* 'format' already contains the sensor configuration */ + if (source->entity() != sensor->entity()) { + /* \todo Add MediaDevice cache to reduce FD pressure */ + V4L2Subdevice subdev(source->entity()); + ret = subdev.open(); + if (ret) + return ret; + + ret = subdev.getFormat(source->index(), format); + if (ret < 0) + return ret; + } + + V4L2SubdeviceFormat sourceFormat = *format; + /* \todo Add MediaDevice cache to reduce FD pressure */ + V4L2Subdevice subdev(sink->entity()); + ret = subdev.open(); + if (ret) + return ret; + + ret = subdev.setFormat(sink->index(), format); + if (ret < 0) + return ret; + + if (format->code != sourceFormat.code || + format->size != sourceFormat.size) { + LOG(MediaPipeline, Debug) + << "Source '" << *source + << " produces " << sourceFormat + << ", sink '" << *sink + << " requires " << *format; + return -EINVAL; + } + + LOG(MediaPipeline, Debug) + << "Link " << *link << " configured with format " + << *format; + } + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index d0e26f6b..202db1ef 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -1,27 +1,35 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources = files([ - 'bayer_format.cpp', - 'byte_stream_buffer.cpp', +libcamera_public_sources = files([ 'camera.cpp', - 'camera_controls.cpp', - 'camera_lens.cpp', 'camera_manager.cpp', - 'camera_sensor.cpp', - 'camera_sensor_properties.cpp', 'color_space.cpp', 'controls.cpp', + 'fence.cpp', + 'framebuffer.cpp', + 'framebuffer_allocator.cpp', + 'geometry.cpp', + 'orientation.cpp', + 'pixel_format.cpp', + 'request.cpp', + 'stream.cpp', + 'transform.cpp', +]) + +libcamera_internal_sources = files([ + 'bayer_format.cpp', + 'byte_stream_buffer.cpp', + 'camera_controls.cpp', + 'camera_lens.cpp', 'control_serializer.cpp', 'control_validator.cpp', 'converter.cpp', + 'debug_controls.cpp', 'delayed_controls.cpp', 'device_enumerator.cpp', 'device_enumerator_sysfs.cpp', - 'fence.cpp', + 'dma_buf_allocator.cpp', 'formats.cpp', - 'framebuffer.cpp', - 'framebuffer_allocator.cpp', - 'geometry.cpp', 'ipa_controls.cpp', 'ipa_data_serializer.cpp', 'ipa_interface.cpp', @@ -32,29 +40,24 @@ libcamera_sources = files([ 'ipc_pipe_unixsocket.cpp', 'ipc_unixsocket.cpp', 'mapped_framebuffer.cpp', + 'matrix.cpp', 'media_device.cpp', 'media_object.cpp', - 'orientation.cpp', + 'media_pipeline.cpp', 'pipeline_handler.cpp', - 'pixel_format.cpp', 'process.cpp', 'pub_key.cpp', - 'request.cpp', + 'shared_mem_object.cpp', 'source_paths.cpp', - 'stream.cpp', 'sysfs.cpp', - 'transform.cpp', 'v4l2_device.cpp', 'v4l2_pixelformat.cpp', 'v4l2_subdevice.cpp', 'v4l2_videodevice.cpp', + 'vector.cpp', 'yaml_parser.cpp', ]) -libcamera_sources += libcamera_public_headers -libcamera_sources += libcamera_generated_ipa_headers -libcamera_sources += libcamera_tracepoint_header - includes = [ libcamera_includes, ] @@ -69,6 +72,8 @@ subdir('converter') subdir('ipa') subdir('pipeline') subdir('proxy') +subdir('sensor') +subdir('software_isp') null_dep = dependency('', required : false) @@ -102,14 +107,14 @@ endif if liblttng.found() tracing_enabled = true config_h.set('HAVE_TRACING', 1) - libcamera_sources += files(['tracepoints.cpp']) + libcamera_internal_sources += files(['tracepoints.cpp']) else tracing_enabled = false endif if libudev.found() config_h.set('HAVE_LIBUDEV', 1) - libcamera_sources += files([ + libcamera_internal_sources += files([ 'device_enumerator_udev.cpp', ]) endif @@ -127,15 +132,35 @@ endif control_sources = [] -foreach source : control_source_files - input_files = files(source +'.yaml', source + '.cpp.in') - control_sources += custom_target(source + '_cpp', +controls_mode_files = { + 'controls': [ + controls_files, + 'control_ids.cpp', + ], + 'properties': [ + properties_files, + 'property_ids.cpp', + ], +} + +foreach mode, inout_files : controls_mode_files + input_files = inout_files[0] + output_file = inout_files[1] + + template_file = files('control_ids.cpp.in') + ranges_file = files('control_ranges.yaml') + + control_sources += custom_target(mode + '_ids_cpp', input : input_files, - output : source + '.cpp', - command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@']) + output : output_file, + command : [gen_controls, '-o', '@OUTPUT@', + '--mode', mode, '-t', template_file, + '-r', ranges_file, '@INPUT@'], + depend_files : [py_mod_controls], + env : py_build_env) endforeach -libcamera_sources += control_sources +libcamera_public_sources += control_sources gen_version = meson.project_source_root() / 'utils' / 'gen-version.sh' @@ -146,7 +171,7 @@ version_cpp = vcs_tag(command : [gen_version, meson.project_build_root(), meson. output : 'version.cpp', fallback : meson.project_version()) -libcamera_sources += version_cpp +libcamera_public_sources += version_cpp if ipa_sign_module ipa_pub_key_cpp = custom_target('ipa_pub_key_cpp', @@ -154,7 +179,7 @@ if ipa_sign_module output : 'ipa_pub_key.cpp', command : [gen_ipa_pub_key, '@INPUT@', '@OUTPUT@']) - libcamera_sources += ipa_pub_key_cpp + libcamera_internal_sources += ipa_pub_key_cpp endif libcamera_deps += [ @@ -174,7 +199,13 @@ libcamera_deps += [ # for the presence or abscence of the dynamic tag. libcamera = shared_library('libcamera', - libcamera_sources, + [ + libcamera_public_headers, + libcamera_public_sources, + libcamera_ipa_headers, + libcamera_internal_headers, + libcamera_internal_sources, + ], version : libcamera_version, soversion : libcamera_soversion, name_prefix : '', @@ -184,7 +215,6 @@ libcamera = shared_library('libcamera', dependencies : libcamera_deps) libcamera_public = declare_dependency(sources : [ - libcamera_ipa_headers, libcamera_public_headers, ], include_directories : libcamera_includes, @@ -193,7 +223,7 @@ libcamera_public = declare_dependency(sources : [ # Internal dependency for components and plugins which can use private APIs libcamera_private = declare_dependency(sources : [ - libcamera_generated_ipa_headers, + libcamera_ipa_headers, ], dependencies : [ libcamera_public, diff --git a/src/libcamera/orientation.cpp b/src/libcamera/orientation.cpp index 965f5a8b..7d7d21ae 100644 --- a/src/libcamera/orientation.cpp +++ b/src/libcamera/orientation.cpp @@ -2,16 +2,15 @@ /* * Copyright (C) 2023, Ideas On Board Oy * - * orientation.cpp - Image orientation + * Image orientation */ #include <libcamera/orientation.h> #include <array> -#include <string> /** - * \file libcamera/orientation.h + * \file orientation.h * \brief Image orientation definition */ @@ -102,10 +101,14 @@ std::ostream &operator<<(std::ostream &out, const Orientation &orientation) { constexpr std::array<const char *, 9> orientationNames = { "", /* Orientation starts counting from 1. */ - "Rotate0", "Rotate0Mirror", - "Rotate180", "Rotate180Mirror", - "Rotate90Mirror", "Rotate270", - "Rotate270Mirror", "Rotate90", + "Rotate0", + "Rotate0Mirror", + "Rotate180", + "Rotate180Mirror", + "Rotate90Mirror", + "Rotate270", + "Rotate270Mirror", + "Rotate90", }; out << orientationNames[static_cast<unsigned int>(orientation)]; diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp index 9bdfff0b..ecda426a 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022 - Jacopo Mondi <jacopo@jmondi.org> * - * imx8-isi.cpp - Pipeline handler for ISI interface found on NXP i.MX8 SoC + * Pipeline handler for ISI interface found on NXP i.MX8 SoC */ #include <algorithm> @@ -157,11 +157,10 @@ PipelineHandlerISI *ISICameraData::pipe() /* Open and initialize pipe components. */ int ISICameraData::init() { - int ret = sensor_->init(); - if (ret) - return ret; + if (!sensor_) + return -ENODEV; - ret = csis_->open(); + int ret = csis_->open(); if (ret) return ret; @@ -367,7 +366,6 @@ ISICameraConfiguration::validateRaw(std::set<Stream *> &availableStreams, * Make sure the requested RAW format is supported by the * pipeline, otherwise adjust it. */ - std::vector<unsigned int> mbusCodes = data_->sensor_->mbusCodes(); StreamConfiguration &rawConfig = config_[0]; PixelFormat rawFormat = rawConfig.pixelFormat; @@ -554,7 +552,7 @@ CameraConfiguration::Status ISICameraConfiguration::validate() PixelFormat pixelFormat = config_[0].pixelFormat; V4L2SubdeviceFormat sensorFormat{}; - sensorFormat.mbus_code = data_->getMediaBusFormat(&pixelFormat); + sensorFormat.code = data_->getMediaBusFormat(&pixelFormat); sensorFormat.size = maxSize; LOG(ISI, Debug) << "Computed sensor configuration: " << sensorFormat; @@ -569,7 +567,7 @@ CameraConfiguration::Status ISICameraConfiguration::validate() * the smallest larger format without considering the aspect ratio * as the ISI can freely scale. */ - auto sizes = sensor->sizes(sensorFormat.mbus_code); + auto sizes = sensor->sizes(sensorFormat.code); Size bestSize; for (const Size &s : sizes) { @@ -595,7 +593,7 @@ CameraConfiguration::Status ISICameraConfiguration::validate() return Invalid; } - sensorFormat_.mbus_code = sensorFormat.mbus_code; + sensorFormat_.code = sensorFormat.code; sensorFormat_.size = bestSize; LOG(ISI, Debug) << "Selected sensor format: " << sensorFormat_; @@ -632,7 +630,7 @@ StreamConfiguration PipelineHandlerISI::generateYUVConfiguration(Camera *camera, /* Adjust the requested size to the sensor's capabilities. */ V4L2SubdeviceFormat sensorFmt; - sensorFmt.mbus_code = mbusCode; + sensorFmt.code = mbusCode; sensorFmt.size = size; int ret = data->sensor_->tryFormat(&sensorFmt); @@ -827,16 +825,10 @@ int PipelineHandlerISI::configure(Camera *camera, CameraConfiguration *c) unsigned int xbarFirstSource = crossbar_->entity()->pads().size() / 2 + 1; for (const auto &[idx, config] : utils::enumerate(*c)) { - struct v4l2_subdev_route route = { - .sink_pad = data->xbarSink_, - .sink_stream = 0, - .source_pad = static_cast<uint32_t>(xbarFirstSource + idx), - .source_stream = 0, - .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, - .reserved = {} - }; - - routing.push_back(route); + uint32_t sourcePad = xbarFirstSource + idx; + routing.emplace_back(V4L2Subdevice::Stream{ data->xbarSink_, 0 }, + V4L2Subdevice::Stream{ sourcePad, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); } int ret = crossbar_->setRouting(&routing, V4L2Subdevice::ActiveFormat); @@ -891,7 +883,7 @@ int PipelineHandlerISI::configure(Camera *camera, CameraConfiguration *c) unsigned int isiCode = ISICameraConfiguration::formatsMap_.at(config.pixelFormat); V4L2SubdeviceFormat isiFormat{}; - isiFormat.mbus_code = isiCode; + isiFormat.code = isiCode; isiFormat.size = config.size; ret = pipe->isi->setFormat(1, &isiFormat); @@ -1063,7 +1055,7 @@ bool PipelineHandlerISI::match(DeviceEnumerator *enumerator) std::unique_ptr<ISICameraData> data = std::make_unique<ISICameraData>(this); - data->sensor_ = std::make_unique<CameraSensor>(sensor); + data->sensor_ = CameraSensorFactoryBase::create(sensor); data->csis_ = std::make_unique<V4L2Subdevice>(csi); data->xbarSink_ = sink; @@ -1118,6 +1110,6 @@ void PipelineHandlerISI::bufferReady(FrameBuffer *buffer) completeRequest(request); } -REGISTER_PIPELINE_HANDLER(PipelineHandlerISI) +REGISTER_PIPELINE_HANDLER(PipelineHandlerISI, "imx8-isi") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build index ffd0ce54..b369b031 100644 --- a/src/libcamera/pipeline/imx8-isi/meson.build +++ b/src/libcamera/pipeline/imx8-isi/meson.build @@ -1,5 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'imx8-isi.cpp' ]) diff --git a/src/libcamera/pipeline/ipu3/cio2.cpp b/src/libcamera/pipeline/ipu3/cio2.cpp index 7400cb0b..aa544d7b 100644 --- a/src/libcamera/pipeline/ipu3/cio2.cpp +++ b/src/libcamera/pipeline/ipu3/cio2.cpp @@ -2,13 +2,13 @@ /* * Copyright (C) 2019, Google Inc. * - * cio2.cpp - Intel IPU3 CIO2 + * Intel IPU3 CIO2 */ #include "cio2.h" +#include <cmath> #include <limits> -#include <math.h> #include <linux/media-bus-format.h> @@ -134,10 +134,9 @@ int CIO2Device::init(const MediaDevice *media, unsigned int index) MediaLink *link = links[0]; MediaEntity *sensorEntity = link->source()->entity(); - sensor_ = std::make_unique<CameraSensor>(sensorEntity); - ret = sensor_->init(); - if (ret) - return ret; + sensor_ = CameraSensorFactoryBase::create(sensorEntity); + if (!sensor_) + return -ENODEV; ret = link->setEnabled(true); if (ret) @@ -202,7 +201,7 @@ int CIO2Device::configure(const Size &size, const Transform &transform, if (ret) return ret; - const auto &itInfo = mbusCodesToPixelFormat.find(sensorFormat.mbus_code); + const auto &itInfo = mbusCodesToPixelFormat.find(sensorFormat.code); if (itInfo == mbusCodesToPixelFormat.end()) return -EINVAL; @@ -230,13 +229,13 @@ StreamConfiguration CIO2Device::generateConfiguration(Size size) const /* Query the sensor static information for closest match. */ std::vector<unsigned int> mbusCodes = utils::map_keys(mbusCodesToPixelFormat); V4L2SubdeviceFormat sensorFormat = getSensorFormat(mbusCodes, size); - if (!sensorFormat.mbus_code) { + if (!sensorFormat.code) { LOG(IPU3, Error) << "Sensor does not support mbus code"; return {}; } cfg.size = sensorFormat.size; - cfg.pixelFormat = mbusCodesToPixelFormat.at(sensorFormat.mbus_code); + cfg.pixelFormat = mbusCodesToPixelFormat.at(sensorFormat.code); cfg.bufferCount = kBufferCount; return cfg; @@ -304,7 +303,7 @@ V4L2SubdeviceFormat CIO2Device::getSensorFormat(const std::vector<unsigned int> * comparing it with a single precision digit is enough. */ ratio = static_cast<unsigned int>(ratio * 10) / 10.0; - float ratioDiff = fabsf(ratio - desiredRatio); + float ratioDiff = std::abs(ratio - desiredRatio); unsigned int area = sz.width * sz.height; unsigned int areaDiff = area - desiredArea; @@ -326,7 +325,7 @@ V4L2SubdeviceFormat CIO2Device::getSensorFormat(const std::vector<unsigned int> } V4L2SubdeviceFormat format{}; - format.mbus_code = bestCode; + format.code = bestCode; format.size = bestSize; return format; diff --git a/src/libcamera/pipeline/ipu3/cio2.h b/src/libcamera/pipeline/ipu3/cio2.h index bbd87eb8..963c2f6b 100644 --- a/src/libcamera/pipeline/ipu3/cio2.h +++ b/src/libcamera/pipeline/ipu3/cio2.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * cio2.h - Intel IPU3 CIO2 + * Intel IPU3 CIO2 */ #pragma once diff --git a/src/libcamera/pipeline/ipu3/frames.cpp b/src/libcamera/pipeline/ipu3/frames.cpp index a4c3477c..bc0526a7 100644 --- a/src/libcamera/pipeline/ipu3/frames.cpp +++ b/src/libcamera/pipeline/ipu3/frames.cpp @@ -2,17 +2,18 @@ /* * Copyright (C) 2020, Google Inc. * - * frames.cpp - Intel IPU3 Frames helper + * Intel IPU3 Frames helper */ #include "frames.h" +#include <libcamera/base/log.h> + #include <libcamera/framebuffer.h> #include <libcamera/request.h> #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/pipeline_handler.h" -#include "libcamera/internal/v4l2_videodevice.h" namespace libcamera { diff --git a/src/libcamera/pipeline/ipu3/frames.h b/src/libcamera/pipeline/ipu3/frames.h index 6e3cb915..a347b66f 100644 --- a/src/libcamera/pipeline/ipu3/frames.h +++ b/src/libcamera/pipeline/ipu3/frames.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * frames.h - Intel IPU3 Frames helper + * Intel IPU3 Frames helper */ #pragma once diff --git a/src/libcamera/pipeline/ipu3/imgu.cpp b/src/libcamera/pipeline/ipu3/imgu.cpp index 531879f1..7be78091 100644 --- a/src/libcamera/pipeline/ipu3/imgu.cpp +++ b/src/libcamera/pipeline/ipu3/imgu.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * imgu.cpp - Intel IPU3 ImgU + * Intel IPU3 ImgU */ #include "imgu.h" @@ -504,7 +504,7 @@ int ImgUDevice::configure(const PipeConfig &pipeConfig, V4L2DeviceFormat *inputF LOG(IPU3, Debug) << "ImgU BDS rectangle = " << bds; V4L2SubdeviceFormat gdcFormat = {}; - gdcFormat.mbus_code = MEDIA_BUS_FMT_FIXED; + gdcFormat.code = MEDIA_BUS_FMT_FIXED; gdcFormat.size = pipeConfig.gdc; ret = imgu_->setFormat(PAD_INPUT, &gdcFormat); @@ -543,7 +543,7 @@ int ImgUDevice::configureVideoDevice(V4L2VideoDevice *dev, unsigned int pad, V4L2DeviceFormat *outputFormat) { V4L2SubdeviceFormat imguFormat = {}; - imguFormat.mbus_code = MEDIA_BUS_FMT_FIXED; + imguFormat.code = MEDIA_BUS_FMT_FIXED; imguFormat.size = cfg.size; int ret = imgu_->setFormat(pad, &imguFormat); diff --git a/src/libcamera/pipeline/ipu3/imgu.h b/src/libcamera/pipeline/ipu3/imgu.h index 0af4dd8a..fa508316 100644 --- a/src/libcamera/pipeline/ipu3/imgu.h +++ b/src/libcamera/pipeline/ipu3/imgu.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * imgu.h - Intel IPU3 ImgU + * Intel IPU3 ImgU */ #pragma once diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index fa4bd0bb..e31e3879 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -2,11 +2,10 @@ /* * Copyright (C) 2019, Google Inc. * - * ipu3.cpp - Pipeline handler for Intel IPU3 + * Pipeline handler for Intel IPU3 */ #include <algorithm> -#include <iomanip> #include <memory> #include <queue> #include <vector> @@ -19,15 +18,17 @@ #include <libcamera/camera.h> #include <libcamera/control_ids.h> #include <libcamera/formats.h> -#include <libcamera/ipa/ipu3_ipa_interface.h> -#include <libcamera/ipa/ipu3_ipa_proxy.h> #include <libcamera/property_ids.h> #include <libcamera/request.h> #include <libcamera/stream.h> +#include <libcamera/ipa/ipu3_ipa_interface.h> +#include <libcamera/ipa/ipu3_ipa_proxy.h> + #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_lens.h" #include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/delayed_controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/framebuffer.h" @@ -88,7 +89,7 @@ public: private: void metadataReady(unsigned int id, const ControlList &metadata); - void paramsBufferReady(unsigned int id); + void paramsComputed(unsigned int id); void setSensorControls(unsigned int id, const ControlList &sensorControls, const ControlList &lensControls); }; @@ -958,7 +959,7 @@ int PipelineHandlerIPU3::updateControls(IPU3CameraData *data) values.reserve(testPatternModes.size()); for (auto pattern : testPatternModes) - values.emplace_back(static_cast<int32_t>(pattern)); + values.emplace_back(pattern); controls[&controls::draft::TestPatternMode] = ControlInfo(values); } @@ -1077,14 +1078,10 @@ int PipelineHandlerIPU3::registerCameras() if (ret) continue; - /* - * \todo Read delay values from the sensor itself or from a - * a sensor database. For now use generic values taken from - * the Raspberry Pi and listed as 'generic values'. - */ + const CameraSensorProperties::SensorDelays &delays = cio2->sensor()->sensorDelays(); std::unordered_map<uint32_t, DelayedControls::ControlParams> params = { - { V4L2_CID_ANALOGUE_GAIN, { 1, false } }, - { V4L2_CID_EXPOSURE, { 2, false } }, + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, }; data->delayedCtrls_ = @@ -1117,19 +1114,19 @@ int PipelineHandlerIPU3::registerCameras() * returned through the ImgU main and secondary outputs. */ data->cio2_.bufferReady().connect(data.get(), - &IPU3CameraData::cio2BufferReady); + &IPU3CameraData::cio2BufferReady); data->cio2_.bufferAvailable.connect( data.get(), &IPU3CameraData::queuePendingRequests); data->imgu_->input_->bufferReady.connect(&data->cio2_, - &CIO2Device::tryReturnBuffer); + &CIO2Device::tryReturnBuffer); data->imgu_->output_->bufferReady.connect(data.get(), - &IPU3CameraData::imguOutputBufferReady); + &IPU3CameraData::imguOutputBufferReady); data->imgu_->viewfinder_->bufferReady.connect(data.get(), - &IPU3CameraData::imguOutputBufferReady); + &IPU3CameraData::imguOutputBufferReady); data->imgu_->param_->bufferReady.connect(data.get(), - &IPU3CameraData::paramBufferReady); + &IPU3CameraData::paramBufferReady); data->imgu_->stat_->bufferReady.connect(data.get(), - &IPU3CameraData::statBufferReady); + &IPU3CameraData::statBufferReady); /* Create and register the Camera instance. */ const std::string &cameraId = cio2->sensor()->id(); @@ -1156,7 +1153,7 @@ int IPU3CameraData::loadIPA() return -ENOENT; ipa_->setSensorControls.connect(this, &IPU3CameraData::setSensorControls); - ipa_->paramsBufferReady.connect(this, &IPU3CameraData::paramsBufferReady); + ipa_->paramsComputed.connect(this, &IPU3CameraData::paramsComputed); ipa_->metadataReady.connect(this, &IPU3CameraData::metadataReady); /* @@ -1186,9 +1183,8 @@ int IPU3CameraData::loadIPA() * The API tuning file is made from the sensor name. If the tuning file * isn't found, fall back to the 'uncalibrated' file. */ - std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml"); - if (ipaTuningFile.empty()) - ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml"); + std::string ipaTuningFile = + ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml"); ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() }, sensorInfo, sensor->controls(), &ipaControls_); @@ -1218,7 +1214,7 @@ void IPU3CameraData::setSensorControls([[maybe_unused]] unsigned int id, focusLens->setFocusPosition(focusValue.get<int32_t>()); } -void IPU3CameraData::paramsBufferReady(unsigned int id) +void IPU3CameraData::paramsComputed(unsigned int id) { IPU3Frames::Info *info = frameInfos_.find(id); if (!info) @@ -1329,7 +1325,7 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer) if (request->findBuffer(&rawStream_)) pipe()->completeBuffer(request, buffer); - ipa_->fillParamsBuffer(info->id, info->paramBuffer->cookie()); + ipa_->computeParams(info->id, info->paramBuffer->cookie()); } void IPU3CameraData::paramBufferReady(FrameBuffer *buffer) @@ -1373,8 +1369,8 @@ void IPU3CameraData::statBufferReady(FrameBuffer *buffer) return; } - ipa_->processStatsBuffer(info->id, request->metadata().get(controls::SensorTimestamp).value_or(0), - info->statBuffer->cookie(), info->effectiveSensorControls); + ipa_->processStats(info->id, request->metadata().get(controls::SensorTimestamp).value_or(0), + info->statBuffer->cookie(), info->effectiveSensorControls); } /* @@ -1420,6 +1416,6 @@ void IPU3CameraData::frameStart(uint32_t sequence) *testPatternMode); } -REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3) +REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3, "ipu3") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/ipu3/meson.build b/src/libcamera/pipeline/ipu3/meson.build index a1b0b31a..f2904b4a 100644 --- a/src/libcamera/pipeline/ipu3/meson.build +++ b/src/libcamera/pipeline/ipu3/meson.build @@ -1,6 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'cio2.cpp', 'frames.cpp', 'imgu.cpp', diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp new file mode 100644 index 00000000..a05e11fc --- /dev/null +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -0,0 +1,1755 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Pipeline Handler for ARM's Mali-C55 ISP + */ + +#include <algorithm> +#include <array> +#include <map> +#include <memory> +#include <set> +#include <string> + +#include <linux/mali-c55-config.h> +#include <linux/media-bus-format.h> +#include <linux/media.h> + +#include <libcamera/base/log.h> + +#include <libcamera/camera.h> +#include <libcamera/formats.h> +#include <libcamera/geometry.h> +#include <libcamera/property_ids.h> +#include <libcamera/stream.h> + +#include <libcamera/ipa/core_ipa_interface.h> +#include <libcamera/ipa/mali-c55_ipa_interface.h> +#include <libcamera/ipa/mali-c55_ipa_proxy.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/camera.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" +#include "libcamera/internal/delayed_controls.h" +#include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" +#include "libcamera/internal/v4l2_subdevice.h" +#include "libcamera/internal/v4l2_videodevice.h" + +namespace { + +bool isFormatRaw(const libcamera::PixelFormat &pixFmt) +{ + return libcamera::PixelFormatInfo::info(pixFmt).colourEncoding == + libcamera::PixelFormatInfo::ColourEncodingRAW; +} + +} /* namespace */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(MaliC55) + +const std::map<libcamera::PixelFormat, unsigned int> maliC55FmtToCode = { + /* \todo Support all formats supported by the driver in libcamera. */ + + { formats::RGB565, MEDIA_BUS_FMT_RGB121212_1X36 }, + { formats::RGB888, MEDIA_BUS_FMT_RGB121212_1X36 }, + { formats::YUYV, MEDIA_BUS_FMT_YUV10_1X30 }, + { formats::UYVY, MEDIA_BUS_FMT_YUV10_1X30 }, + { formats::R8, MEDIA_BUS_FMT_YUV10_1X30 }, + { formats::NV12, MEDIA_BUS_FMT_YUV10_1X30 }, + { formats::NV21, MEDIA_BUS_FMT_YUV10_1X30 }, + + /* RAW formats, FR pipe only. */ + { formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 }, + { formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 }, + { formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 }, + { formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 }, +}; + +constexpr Size kMaliC55MinInputSize = { 640, 480 }; +constexpr Size kMaliC55MinSize = { 128, 128 }; +constexpr Size kMaliC55MaxSize = { 8192, 8192 }; +constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36; + +struct MaliC55FrameInfo { + Request *request; + + FrameBuffer *paramBuffer; + FrameBuffer *statBuffer; + + bool paramsDone; + bool statsDone; +}; + +class MaliC55CameraData : public Camera::Private +{ +public: + MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity) + : Camera::Private(pipe), entity_(entity) + { + } + + int init(); + int loadIPA(); + + /* Deflect these functionalities to either TPG or CameraSensor. */ + const std::vector<Size> sizes(unsigned int mbusCode) const; + const Size resolution() const; + + int pixfmtToMbusCode(const PixelFormat &pixFmt) const; + const PixelFormat &bestRawFormat() const; + + void updateControls(const ControlInfoMap &ipaControls); + + PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const; + Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const; + + std::unique_ptr<CameraSensor> sensor_; + + MediaEntity *entity_; + std::unique_ptr<V4L2Subdevice> csi_; + std::unique_ptr<V4L2Subdevice> sd_; + Stream frStream_; + Stream dsStream_; + + std::unique_ptr<ipa::mali_c55::IPAProxyMaliC55> ipa_; + std::vector<IPABuffer> ipaStatBuffers_; + std::vector<IPABuffer> ipaParamBuffers_; + + std::unique_ptr<DelayedControls> delayedCtrls_; + +private: + void initTPGData(); + void setSensorControls(const ControlList &sensorControls); + + std::string id_; + std::vector<unsigned int> tpgCodes_; + std::vector<Size> tpgSizes_; + Size tpgResolution_; +}; + +int MaliC55CameraData::init() +{ + int ret; + + sd_ = std::make_unique<V4L2Subdevice>(entity_); + ret = sd_->open(); + if (ret) { + LOG(MaliC55, Error) << "Failed to open sensor subdevice"; + return ret; + } + + /* If this camera is created from TPG, we return here. */ + if (entity_->name() == "mali-c55 tpg") { + initTPGData(); + return 0; + } + + /* + * Register a CameraSensor if we connect to a sensor and create + * an entity for the connected CSI-2 receiver. + */ + sensor_ = CameraSensorFactoryBase::create(entity_); + if (!sensor_) + return ret; + + const MediaPad *sourcePad = entity_->getPadByIndex(0); + MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity(); + + csi_ = std::make_unique<V4L2Subdevice>(csiEntity); + if (csi_->open()) { + LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice"; + return false; + } + + return 0; +} + +void MaliC55CameraData::initTPGData() +{ + /* Replicate the CameraSensor implementation for TPG. */ + V4L2Subdevice::Formats formats = sd_->formats(0); + if (formats.empty()) + return; + + tpgCodes_ = utils::map_keys(formats); + std::sort(tpgCodes_.begin(), tpgCodes_.end()); + + for (const auto &format : formats) { + const std::vector<SizeRange> &ranges = format.second; + std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes_), + [](const SizeRange &range) { return range.max; }); + } + + tpgResolution_ = tpgSizes_.back(); +} + +void MaliC55CameraData::setSensorControls(const ControlList &sensorControls) +{ + delayedCtrls_->push(sensorControls); +} + +const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const +{ + if (sensor_) + return sensor_->sizes(mbusCode); + + V4L2Subdevice::Formats formats = sd_->formats(0); + if (formats.empty()) + return {}; + + std::vector<Size> sizes; + const auto &format = formats.find(mbusCode); + if (format == formats.end()) + return {}; + + const std::vector<SizeRange> &ranges = format->second; + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes), + [](const SizeRange &range) { return range.max; }); + + std::sort(sizes.begin(), sizes.end()); + + return sizes; +} + +const Size MaliC55CameraData::resolution() const +{ + if (sensor_) + return sensor_->resolution(); + + return tpgResolution_; +} + +/* + * The Mali C55 ISP can only produce 16-bit RAW output in bypass modes, but the + * sensors connected to it might produce 8/10/12/16 bits. We simply search the + * sensor's supported formats for the one with a matching bayer order and the + * greatest bitdepth. + */ +int MaliC55CameraData::pixfmtToMbusCode(const PixelFormat &pixFmt) const +{ + auto it = maliC55FmtToCode.find(pixFmt); + if (it == maliC55FmtToCode.end()) + return -EINVAL; + + BayerFormat bayerFormat = BayerFormat::fromMbusCode(it->second); + if (!bayerFormat.isValid()) + return -EINVAL; + + V4L2Subdevice::Formats formats = sd_->formats(0); + unsigned int sensorMbusCode = 0; + unsigned int bitDepth = 0; + + for (const auto &[code, sizes] : formats) { + BayerFormat sdBayerFormat = BayerFormat::fromMbusCode(code); + if (!sdBayerFormat.isValid()) + continue; + + if (sdBayerFormat.order != bayerFormat.order) + continue; + + if (sdBayerFormat.bitDepth > bitDepth) { + bitDepth = sdBayerFormat.bitDepth; + sensorMbusCode = code; + } + } + + if (!sensorMbusCode) + return -EINVAL; + + return sensorMbusCode; +} + +/* + * Find a RAW PixelFormat supported by both the ISP and the sensor. + * + * The situation is mildly complicated by the fact that we expect the sensor to + * output something like RAW8/10/12/16, but the ISP can only accept as input + * RAW20 and can only produce as output RAW16. The one constant in that is the + * bayer order of the data, so we'll simply check that the sensor produces a + * format with a bayer order that matches that of one of the formats we support, + * and select that. + */ +const PixelFormat &MaliC55CameraData::bestRawFormat() const +{ + static const PixelFormat invalidPixFmt = {}; + + for (const auto &fmt : sd_->formats(0)) { + BayerFormat sensorBayer = BayerFormat::fromMbusCode(fmt.first); + + if (!sensorBayer.isValid()) + continue; + + for (const auto &[pixFmt, rawCode] : maliC55FmtToCode) { + if (!isFormatRaw(pixFmt)) + continue; + + BayerFormat bayer = BayerFormat::fromMbusCode(rawCode); + if (bayer.order == sensorBayer.order) + return pixFmt; + } + } + + LOG(MaliC55, Error) << "Sensor doesn't provide a compatible format"; + return invalidPixFmt; +} + +void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls) +{ + if (!sensor_) + return; + + IPACameraSensorInfo sensorInfo; + int ret = sensor_->sensorInfo(&sensorInfo); + if (ret) { + LOG(MaliC55, Error) << "Failed to retrieve sensor info"; + return; + } + + ControlInfoMap::Map controls; + Rectangle ispMinCrop{ 0, 0, 640, 480 }; + controls[&controls::ScalerCrop] = + ControlInfo(ispMinCrop, sensorInfo.analogCrop, + sensorInfo.analogCrop); + + for (auto const &c : ipaControls) + controls.emplace(c.first, c.second); + + controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); +} + +/* + * Make sure the provided raw pixel format is supported and adjust it to + * one of the supported ones if it's not. + */ +PixelFormat MaliC55CameraData::adjustRawFormat(const PixelFormat &rawFmt) const +{ + /* Make sure the RAW mbus code is supported by the image source. */ + int rawCode = pixfmtToMbusCode(rawFmt); + if (rawCode < 0) + return bestRawFormat(); + + const auto rawSizes = sizes(rawCode); + if (rawSizes.empty()) + return bestRawFormat(); + + return rawFmt; +} + +Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &size) const +{ + /* Expand the RAW size to the minimum ISP input size. */ + Size rawSize = size.expandedTo(kMaliC55MinInputSize); + + /* Check if the size is natively supported. */ + int rawCode = pixfmtToMbusCode(rawFmt); + if (rawCode < 0) + return {}; + + const auto rawSizes = sizes(rawCode); + auto sizeIt = std::find(rawSizes.begin(), rawSizes.end(), rawSize); + if (sizeIt != rawSizes.end()) + return rawSize; + + /* Or adjust it to the closest supported size. */ + uint16_t distance = std::numeric_limits<uint16_t>::max(); + Size bestSize; + for (const Size &sz : rawSizes) { + uint16_t dist = std::abs(static_cast<int>(rawSize.width) - + static_cast<int>(sz.width)) + + std::abs(static_cast<int>(rawSize.height) - + static_cast<int>(sz.height)); + if (dist < distance) { + dist = distance; + bestSize = sz; + } + } + + return bestSize; +} + +int MaliC55CameraData::loadIPA() +{ + int ret; + + /* Do not initialize IPA for TPG. */ + if (!sensor_) + return 0; + + ipa_ = IPAManager::createIPA<ipa::mali_c55::IPAProxyMaliC55>(pipe(), 1, 1); + if (!ipa_) + return -ENOENT; + + ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls); + + std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml", + "uncalibrated.yaml"); + + /* We need to inform the IPA of the sensor configuration */ + ipa::mali_c55::IPAConfigInfo ipaConfig{}; + + ret = sensor_->sensorInfo(&ipaConfig.sensorInfo); + if (ret) + return ret; + + ipaConfig.sensorControls = sensor_->controls(); + + ControlInfoMap ipaControls; + ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig, + &ipaControls); + if (ret) { + LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA"; + return ret; + } + + updateControls(ipaControls); + + return 0; +} + +class MaliC55CameraConfiguration : public CameraConfiguration +{ +public: + MaliC55CameraConfiguration(MaliC55CameraData *data) + : CameraConfiguration(), data_(data) + { + } + + Status validate() override; + const Transform &combinedTransform() { return combinedTransform_; } + + V4L2SubdeviceFormat sensorFormat_; + +private: + static constexpr unsigned int kMaxStreams = 2; + + const MaliC55CameraData *data_; + Transform combinedTransform_; +}; + +CameraConfiguration::Status MaliC55CameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) + return Invalid; + + /* + * The TPG doesn't support flips, so we only need to calculate a + * transform if we have a sensor. + */ + if (data_->sensor_) { + Orientation requestedOrientation = orientation; + combinedTransform_ = data_->sensor_->computeTransform(&orientation); + if (orientation != requestedOrientation) + status = Adjusted; + } else { + combinedTransform_ = Transform::Rot0; + } + + /* Only 2 streams available. */ + if (config_.size() > kMaxStreams) { + config_.resize(kMaxStreams); + status = Adjusted; + } + + bool frPipeAvailable = true; + StreamConfiguration *rawConfig = nullptr; + for (StreamConfiguration &config : config_) { + if (!isFormatRaw(config.pixelFormat)) + continue; + + if (rawConfig) { + LOG(MaliC55, Error) + << "Only a single RAW stream is supported"; + return Invalid; + } + + rawConfig = &config; + } + + /* + * The C55 can not upscale. Limit the configuration to the ISP + * capabilities and the sensor resolution. + */ + Size maxSize = kMaliC55MaxSize.boundedTo(data_->resolution()); + if (rawConfig) { + /* + * \todo Take into account the Bayer components ordering once + * we support rotations. + */ + PixelFormat rawFormat = + data_->adjustRawFormat(rawConfig->pixelFormat); + + if (!rawFormat.isValid()) + return Invalid; + + if (rawFormat != rawConfig->pixelFormat) { + LOG(MaliC55, Debug) + << "RAW format adjusted to " << rawFormat; + rawConfig->pixelFormat = rawFormat; + status = Adjusted; + } + + Size rawSize = + data_->adjustRawSizes(rawFormat, rawConfig->size); + if (rawSize != rawConfig->size) { + LOG(MaliC55, Debug) + << "RAW sizes adjusted to " << rawSize; + rawConfig->size = rawSize; + status = Adjusted; + } + + maxSize = rawSize; + + const PixelFormatInfo &info = PixelFormatInfo::info(rawConfig->pixelFormat); + rawConfig->stride = info.stride(rawConfig->size.width, 0, 4); + rawConfig->frameSize = info.frameSize(rawConfig->size, 4); + + rawConfig->setStream(const_cast<Stream *>(&data_->frStream_)); + frPipeAvailable = false; + } + + /* + * Adjust processed streams. + * + * Compute the minimum sensor size to be later used to select the + * sensor configuration. + */ + Size minSensorSize = kMaliC55MinInputSize; + for (StreamConfiguration &config : config_) { + if (isFormatRaw(config.pixelFormat)) + continue; + + /* Adjust format and size for processed streams. */ + const auto it = maliC55FmtToCode.find(config.pixelFormat); + if (it == maliC55FmtToCode.end()) { + LOG(MaliC55, Debug) + << "Format adjusted to " << formats::RGB565; + config.pixelFormat = formats::RGB565; + status = Adjusted; + } + + Size size = std::clamp(config.size, kMaliC55MinSize, maxSize); + if (size != config.size) { + LOG(MaliC55, Debug) + << "Size adjusted to " << size; + config.size = size; + status = Adjusted; + } + + if (minSensorSize < size) + minSensorSize = size; + + if (frPipeAvailable) { + config.setStream(const_cast<Stream *>(&data_->frStream_)); + frPipeAvailable = false; + } else { + config.setStream(const_cast<Stream *>(&data_->dsStream_)); + } + } + + /* Compute the sensor format. */ + + /* If there's a RAW config, sensor configuration follows it. */ + if (rawConfig) { + sensorFormat_.code = data_->pixfmtToMbusCode(rawConfig->pixelFormat); + sensorFormat_.size = rawConfig->size.expandedTo(minSensorSize); + + return status; + } + + /* If there's no RAW config, compute the sensor configuration here. */ + PixelFormat rawFormat = data_->bestRawFormat(); + if (!rawFormat.isValid()) + return Invalid; + + sensorFormat_.code = data_->pixfmtToMbusCode(rawFormat); + + uint16_t distance = std::numeric_limits<uint16_t>::max(); + const auto sizes = data_->sizes(sensorFormat_.code); + Size bestSize; + for (const auto &size : sizes) { + if (minSensorSize.width > size.width || + minSensorSize.height > size.height) + continue; + + uint16_t dist = std::abs(static_cast<int>(minSensorSize.width) - + static_cast<int>(size.width)) + + std::abs(static_cast<int>(minSensorSize.height) - + static_cast<int>(size.height)); + if (dist < distance) { + dist = distance; + bestSize = size; + } + } + sensorFormat_.size = bestSize; + + LOG(MaliC55, Debug) << "Computed sensor configuration " << sensorFormat_; + + return status; +} + +class PipelineHandlerMaliC55 : public PipelineHandler +{ +public: + PipelineHandlerMaliC55(CameraManager *manager); + + std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera, + Span<const StreamRole> roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; + int allocateBuffers(Camera *camera); + void freeBuffers(Camera *camera); + + int start(Camera *camera, const ControlList *controls) override; + void stopDevice(Camera *camera) override; + + int queueRequestDevice(Camera *camera, Request *request) override; + + void imageBufferReady(FrameBuffer *buffer); + void paramsBufferReady(FrameBuffer *buffer); + void statsBufferReady(FrameBuffer *buffer); + void paramsComputed(unsigned int requestId); + void statsProcessed(unsigned int requestId, const ControlList &metadata); + + bool match(DeviceEnumerator *enumerator) override; + +private: + struct MaliC55Pipe { + std::unique_ptr<V4L2Subdevice> resizer; + std::unique_ptr<V4L2VideoDevice> cap; + MediaLink *link; + Stream *stream; + }; + + enum { + MaliC55FR, + MaliC55DS, + MaliC55NumPipes, + }; + + MaliC55CameraData *cameraData(Camera *camera) + { + return static_cast<MaliC55CameraData *>(camera->_d()); + } + + MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, Stream *stream) + { + if (stream == &data->frStream_) + return &pipes_[MaliC55FR]; + else if (stream == &data->dsStream_) + return &pipes_[MaliC55DS]; + else + LOG(MaliC55, Fatal) << "Stream " << stream << " not valid"; + return nullptr; + } + + MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, const Stream *stream) + { + return pipeFromStream(data, const_cast<Stream *>(stream)); + } + + void resetPipes() + { + for (MaliC55Pipe &pipe : pipes_) + pipe.stream = nullptr; + } + + MaliC55FrameInfo *findFrameInfo(FrameBuffer *buffer); + MaliC55FrameInfo *findFrameInfo(Request *request); + void tryComplete(MaliC55FrameInfo *info); + + int configureRawStream(MaliC55CameraData *data, + const StreamConfiguration &config, + V4L2SubdeviceFormat &subdevFormat); + int configureProcessedStream(MaliC55CameraData *data, + const StreamConfiguration &config, + V4L2SubdeviceFormat &subdevFormat); + + void applyScalerCrop(Camera *camera, const ControlList &controls); + + bool registerMaliCamera(std::unique_ptr<MaliC55CameraData> data, + const std::string &name); + bool registerTPGCamera(MediaLink *link); + bool registerSensorCamera(MediaLink *link); + + MediaDevice *media_; + std::unique_ptr<V4L2Subdevice> isp_; + std::unique_ptr<V4L2VideoDevice> stats_; + std::unique_ptr<V4L2VideoDevice> params_; + + std::vector<std::unique_ptr<FrameBuffer>> statsBuffers_; + std::queue<FrameBuffer *> availableStatsBuffers_; + + std::vector<std::unique_ptr<FrameBuffer>> paramsBuffers_; + std::queue<FrameBuffer *> availableParamsBuffers_; + + std::map<unsigned int, MaliC55FrameInfo> frameInfoMap_; + + std::array<MaliC55Pipe, MaliC55NumPipes> pipes_; + + bool dsFitted_; +}; + +PipelineHandlerMaliC55::PipelineHandlerMaliC55(CameraManager *manager) + : PipelineHandler(manager), dsFitted_(true) +{ +} + +std::unique_ptr<CameraConfiguration> +PipelineHandlerMaliC55::generateConfiguration(Camera *camera, + Span<const StreamRole> roles) +{ + MaliC55CameraData *data = cameraData(camera); + std::unique_ptr<CameraConfiguration> config = + std::make_unique<MaliC55CameraConfiguration>(data); + bool frPipeAvailable = true; + + if (roles.empty()) + return config; + + /* Check if one stream is RAW to reserve the FR pipe for it. */ + if (std::find(roles.begin(), roles.end(), StreamRole::Raw) != roles.end()) + frPipeAvailable = false; + + for (const StreamRole &role : roles) { + struct MaliC55Pipe *pipe; + + /* Assign pipe for this role. */ + if (role == StreamRole::Raw) { + pipe = &pipes_[MaliC55FR]; + } else { + if (frPipeAvailable) { + pipe = &pipes_[MaliC55FR]; + frPipeAvailable = false; + } else { + pipe = &pipes_[MaliC55DS]; + } + } + + Size size = std::min(Size{ 1920, 1080 }, data->resolution()); + PixelFormat pixelFormat; + + switch (role) { + case StreamRole::StillCapture: + size = data->resolution(); + [[fallthrough]]; + case StreamRole::VideoRecording: + pixelFormat = formats::NV12; + break; + + case StreamRole::Viewfinder: + pixelFormat = formats::RGB565; + break; + + case StreamRole::Raw: + pixelFormat = data->bestRawFormat(); + if (!pixelFormat.isValid()) { + LOG(MaliC55, Error) + << "Camera does not support RAW formats"; + return nullptr; + } + + size = data->resolution(); + break; + + default: + LOG(MaliC55, Error) + << "Requested stream role not supported: " << role; + return nullptr; + } + + std::map<PixelFormat, std::vector<SizeRange>> formats; + for (const auto &maliFormat : maliC55FmtToCode) { + PixelFormat pixFmt = maliFormat.first; + bool isRaw = isFormatRaw(pixFmt); + + /* RAW formats are only supported on the FR pipe. */ + if (pipe != &pipes_[MaliC55FR] && isRaw) + continue; + + if (isRaw) { + /* Make sure the mbus code is supported. */ + int rawCode = data->pixfmtToMbusCode(pixFmt); + if (rawCode < 0) + continue; + + const auto sizes = data->sizes(rawCode); + if (sizes.empty()) + continue; + + /* And list all sizes the sensor can produce. */ + std::vector<SizeRange> sizeRanges; + std::transform(sizes.begin(), sizes.end(), + std::back_inserter(sizeRanges), + [](const Size &s) { + return SizeRange(s); + }); + + formats[pixFmt] = sizeRanges; + } else { + /* Processed formats are always available. */ + Size maxSize = std::min(kMaliC55MaxSize, + data->resolution()); + formats[pixFmt] = { kMaliC55MinSize, maxSize }; + } + } + + StreamFormats streamFormats(formats); + StreamConfiguration cfg(streamFormats); + cfg.pixelFormat = pixelFormat; + cfg.bufferCount = 4; + cfg.size = size; + + config->addConfiguration(cfg); + } + + if (config->validate() == CameraConfiguration::Invalid) + return nullptr; + + return config; +} + +int PipelineHandlerMaliC55::configureRawStream(MaliC55CameraData *data, + const StreamConfiguration &config, + V4L2SubdeviceFormat &subdevFormat) +{ + Stream *stream = config.stream(); + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + if (pipe != &pipes_[MaliC55FR]) { + LOG(MaliC55, Fatal) << "Only the FR pipe supports RAW capture."; + return -EINVAL; + } + + /* Enable the debayer route to set fixed internal format on pad #0. */ + V4L2Subdevice::Routing routing = {}; + routing.emplace_back(V4L2Subdevice::Stream{ 0, 0 }, + V4L2Subdevice::Stream{ 1, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); + + int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat); + if (ret) + return ret; + + unsigned int rawCode = subdevFormat.code; + subdevFormat.code = kMaliC55ISPInternalFormat; + ret = pipe->resizer->setFormat(0, &subdevFormat); + if (ret) + return ret; + + /* Enable the bypass route and apply RAW formats there. */ + routing.clear(); + routing.emplace_back(V4L2Subdevice::Stream{ 2, 0 }, + V4L2Subdevice::Stream{ 1, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); + ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat); + if (ret) + return ret; + + subdevFormat.code = rawCode; + ret = pipe->resizer->setFormat(2, &subdevFormat); + if (ret) + return ret; + + ret = pipe->resizer->setFormat(1, &subdevFormat); + if (ret) + return ret; + + return 0; +} + +int PipelineHandlerMaliC55::configureProcessedStream(MaliC55CameraData *data, + const StreamConfiguration &config, + V4L2SubdeviceFormat &subdevFormat) +{ + Stream *stream = config.stream(); + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + /* Enable the debayer route on the resizer pipe. */ + V4L2Subdevice::Routing routing = {}; + routing.emplace_back(V4L2Subdevice::Stream{ 0, 0 }, + V4L2Subdevice::Stream{ 1, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); + + int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat); + if (ret) + return ret; + + subdevFormat.code = kMaliC55ISPInternalFormat; + ret = pipe->resizer->setFormat(0, &subdevFormat); + if (ret) + return ret; + + /* + * Compute the scaler-in to scaler-out ratio: first center-crop to align + * the FOV to the desired resolution, then scale to the desired size. + */ + Size scalerIn = subdevFormat.size.boundedToAspectRatio(config.size); + int xCrop = (subdevFormat.size.width - scalerIn.width) / 2; + int yCrop = (subdevFormat.size.height - scalerIn.height) / 2; + Rectangle ispCrop = { xCrop, yCrop, scalerIn }; + ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop); + if (ret) + return ret; + + Rectangle ispCompose = { 0, 0, config.size }; + ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &ispCompose); + if (ret) + return ret; + + /* + * The source pad format size comes directly from the sink + * compose rectangle. + */ + subdevFormat.size = ispCompose.size(); + subdevFormat.code = maliC55FmtToCode.find(config.pixelFormat)->second; + return pipe->resizer->setFormat(1, &subdevFormat); +} + +int PipelineHandlerMaliC55::configure(Camera *camera, + CameraConfiguration *config) +{ + resetPipes(); + + int ret = media_->disableLinks(); + if (ret) + return ret; + + /* Link the graph depending if we are operating the TPG or a sensor. */ + MaliC55CameraData *data = cameraData(camera); + if (data->csi_) { + const MediaEntity *csiEntity = data->csi_->entity(); + ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true); + } else { + ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true); + } + if (ret) + return ret; + + MaliC55CameraConfiguration *maliConfig = + static_cast<MaliC55CameraConfiguration *>(config); + V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_; + ret = data->sd_->getFormat(0, &subdevFormat); + if (ret) + return ret; + + if (data->sensor_) { + ret = data->sensor_->setFormat(&subdevFormat, + maliConfig->combinedTransform()); + if (ret) + return ret; + } + + if (data->csi_) { + ret = data->csi_->setFormat(0, &subdevFormat); + if (ret) + return ret; + + ret = data->csi_->getFormat(1, &subdevFormat); + if (ret) + return ret; + } + + V4L2DeviceFormat statsFormat; + ret = stats_->getFormat(&statsFormat); + if (ret) + return ret; + + if (statsFormat.planes[0].size != sizeof(struct mali_c55_stats_buffer)) { + LOG(MaliC55, Error) << "3a stats buffer size invalid"; + return -EINVAL; + } + + /* + * Propagate the format to the ISP sink pad and configure the input + * crop rectangle (no crop at the moment). + * + * \todo Configure the CSI-2 receiver. + */ + ret = isp_->setFormat(0, &subdevFormat); + if (ret) + return ret; + + Rectangle ispCrop(0, 0, subdevFormat.size); + ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop); + if (ret) + return ret; + + /* + * Configure the resizer: fixed format the sink pad; use the media + * bus code associated with the desired capture format on the source + * pad. + * + * Configure the crop and compose rectangles to match the desired + * stream output size + * + * \todo Make the crop/scaler configurable + */ + for (const StreamConfiguration &streamConfig : *config) { + Stream *stream = streamConfig.stream(); + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + /* + * Enable the media link between the pipe's resizer and the + * capture video device + */ + + ret = pipe->link->setEnabled(true); + if (ret) { + LOG(MaliC55, Error) << "Couldn't enable resizer's link"; + return ret; + } + + if (isFormatRaw(streamConfig.pixelFormat)) + ret = configureRawStream(data, streamConfig, subdevFormat); + else + ret = configureProcessedStream(data, streamConfig, subdevFormat); + if (ret) { + LOG(MaliC55, Error) << "Failed to configure pipeline"; + return ret; + } + + /* Now apply the pixel format and size to the capture device. */ + V4L2DeviceFormat captureFormat; + captureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat); + captureFormat.size = streamConfig.size; + + ret = pipe->cap->setFormat(&captureFormat); + if (ret) + return ret; + + pipe->stream = stream; + } + + if (!data->ipa_) + return 0; + + /* + * Enable the media link between the ISP subdevice and the statistics + * video device. + */ + const MediaEntity *ispEntity = isp_->entity(); + ret = ispEntity->getPadByIndex(3)->links()[0]->setEnabled(true); + if (ret) { + LOG(MaliC55, Error) << "Couldn't enable statistics link"; + return ret; + } + + /* + * Enable the media link between the ISP subdevice and the parameters + * video device. + */ + ret = ispEntity->getPadByIndex(4)->links()[0]->setEnabled(true); + if (ret) { + LOG(MaliC55, Error) << "Couldn't enable parameters link"; + return ret; + } + + /* We need to inform the IPA of the sensor configuration */ + ipa::mali_c55::IPAConfigInfo ipaConfig{}; + + ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo); + if (ret) + return ret; + + ipaConfig.sensorControls = data->sensor_->controls(); + + /* + * And we also need to tell the IPA the bayerOrder of the data (as + * affected by any flips that we've configured) + */ + const Transform &combinedTransform = maliConfig->combinedTransform(); + BayerFormat::Order bayerOrder = data->sensor_->bayerOrder(combinedTransform); + + ControlInfoMap ipaControls; + ret = data->ipa_->configure(ipaConfig, utils::to_underlying(bayerOrder), + &ipaControls); + if (ret) { + LOG(MaliC55, Error) << "Failed to configure IPA"; + return ret; + } + + data->updateControls(ipaControls); + + return 0; +} + +int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) +{ + MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream); + unsigned int count = stream->configuration().bufferCount; + + return pipe->cap->exportBuffers(count, buffers); +} + +void PipelineHandlerMaliC55::freeBuffers(Camera *camera) +{ + MaliC55CameraData *data = cameraData(camera); + + while (!availableStatsBuffers_.empty()) + availableStatsBuffers_.pop(); + while (!availableParamsBuffers_.empty()) + availableParamsBuffers_.pop(); + + statsBuffers_.clear(); + paramsBuffers_.clear(); + + if (data->ipa_) { + data->ipa_->unmapBuffers(data->ipaStatBuffers_); + data->ipa_->unmapBuffers(data->ipaParamBuffers_); + } + data->ipaStatBuffers_.clear(); + data->ipaParamBuffers_.clear(); + + if (stats_->releaseBuffers()) + LOG(MaliC55, Error) << "Failed to release stats buffers"; + + if (params_->releaseBuffers()) + LOG(MaliC55, Error) << "Failed to release params buffers"; + + return; +} + +int PipelineHandlerMaliC55::allocateBuffers(Camera *camera) +{ + MaliC55CameraData *data = cameraData(camera); + unsigned int ipaBufferId = 1; + unsigned int bufferCount; + int ret; + + bufferCount = std::max({ + data->frStream_.configuration().bufferCount, + data->dsStream_.configuration().bufferCount, + }); + + ret = stats_->allocateBuffers(bufferCount, &statsBuffers_); + if (ret < 0) + return ret; + + for (std::unique_ptr<FrameBuffer> &buffer : statsBuffers_) { + buffer->setCookie(ipaBufferId++); + data->ipaStatBuffers_.emplace_back(buffer->cookie(), + buffer->planes()); + availableStatsBuffers_.push(buffer.get()); + } + + ret = params_->allocateBuffers(bufferCount, ¶msBuffers_); + if (ret < 0) + return ret; + + for (std::unique_ptr<FrameBuffer> &buffer : paramsBuffers_) { + buffer->setCookie(ipaBufferId++); + data->ipaParamBuffers_.emplace_back(buffer->cookie(), + buffer->planes()); + availableParamsBuffers_.push(buffer.get()); + } + + if (data->ipa_) { + data->ipa_->mapBuffers(data->ipaStatBuffers_, true); + data->ipa_->mapBuffers(data->ipaParamBuffers_, false); + } + + return 0; +} + +int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const ControlList *controls) +{ + MaliC55CameraData *data = cameraData(camera); + int ret; + + ret = allocateBuffers(camera); + if (ret) + return ret; + + if (data->ipa_) { + ret = data->ipa_->start(); + if (ret) { + LOG(MaliC55, Error) + << "Failed to start IPA" << camera->id(); + freeBuffers(camera); + return ret; + } + } + + for (MaliC55Pipe &pipe : pipes_) { + if (!pipe.stream) + continue; + + Stream *stream = pipe.stream; + + ret = pipe.cap->importBuffers(stream->configuration().bufferCount); + if (ret) { + LOG(MaliC55, Error) << "Failed to import buffers"; + if (data->ipa_) + data->ipa_->stop(); + freeBuffers(camera); + return ret; + } + + ret = pipe.cap->streamOn(); + if (ret) { + LOG(MaliC55, Error) << "Failed to start stream"; + if (data->ipa_) + data->ipa_->stop(); + freeBuffers(camera); + return ret; + } + } + + ret = stats_->streamOn(); + if (ret) { + LOG(MaliC55, Error) << "Failed to start stats stream"; + + if (data->ipa_) + data->ipa_->stop(); + + for (MaliC55Pipe &pipe : pipes_) { + if (pipe.stream) + pipe.cap->streamOff(); + } + + freeBuffers(camera); + return ret; + } + + ret = params_->streamOn(); + if (ret) { + LOG(MaliC55, Error) << "Failed to start params stream"; + + stats_->streamOff(); + if (data->ipa_) + data->ipa_->stop(); + + for (MaliC55Pipe &pipe : pipes_) { + if (pipe.stream) + pipe.cap->streamOff(); + } + + freeBuffers(camera); + return ret; + } + + ret = isp_->setFrameStartEnabled(true); + if (ret) + LOG(MaliC55, Error) << "Failed to enable frame start events"; + + return 0; +} + +void PipelineHandlerMaliC55::stopDevice(Camera *camera) +{ + MaliC55CameraData *data = cameraData(camera); + + isp_->setFrameStartEnabled(false); + + for (MaliC55Pipe &pipe : pipes_) { + if (!pipe.stream) + continue; + + pipe.cap->streamOff(); + pipe.cap->releaseBuffers(); + } + + stats_->streamOff(); + params_->streamOff(); + if (data->ipa_) + data->ipa_->stop(); + freeBuffers(camera); +} + +void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera, + const ControlList &controls) +{ + MaliC55CameraData *data = cameraData(camera); + + const auto &scalerCrop = controls.get<Rectangle>(controls::ScalerCrop); + if (!scalerCrop) + return; + + if (!data->sensor_) { + LOG(MaliC55, Error) << "ScalerCrop not supported for TPG"; + return; + } + + Rectangle nativeCrop = *scalerCrop; + + IPACameraSensorInfo sensorInfo; + int ret = data->sensor_->sensorInfo(&sensorInfo); + if (ret) { + LOG(MaliC55, Error) << "Failed to retrieve sensor info"; + return; + } + + /* + * The ScalerCrop rectangle re-scaling in the ISP crop rectangle + * comes straight from the RPi pipeline handler. + * + * Create a version of the crop rectangle aligned to the analogue crop + * rectangle top-left coordinates and scaled in the [analogue crop to + * output frame] ratio to take into account binning/skipping on the + * sensor. + */ + Rectangle ispCrop = nativeCrop.translatedBy(-sensorInfo.analogCrop + .topLeft()); + ispCrop.scaleBy(sensorInfo.outputSize, sensorInfo.analogCrop.size()); + + /* + * The crop rectangle should be: + * 1. At least as big as ispMinCropSize_, once that's been + * enlarged to the same aspect ratio. + * 2. With the same mid-point, if possible. + * 3. But it can't go outside the sensor area. + */ + Rectangle ispMinCrop{ 0, 0, 640, 480 }; + Size minSize = ispMinCrop.size().expandedToAspectRatio(nativeCrop.size()); + Size size = ispCrop.size().expandedTo(minSize); + ispCrop = size.centeredTo(ispCrop.center()) + .enclosedIn(Rectangle(sensorInfo.outputSize)); + + /* + * As the resizer can't upscale, the crop rectangle has to be larger + * than the larger stream output size. + */ + Size maxYuvSize; + for (MaliC55Pipe &pipe : pipes_) { + if (!pipe.stream) + continue; + + const StreamConfiguration &config = pipe.stream->configuration(); + if (isFormatRaw(config.pixelFormat)) { + LOG(MaliC55, Debug) << "Cannot crop with a RAW stream"; + return; + } + + Size streamSize = config.size; + if (streamSize.width > maxYuvSize.width) + maxYuvSize.width = streamSize.width; + if (streamSize.height > maxYuvSize.height) + maxYuvSize.height = streamSize.height; + } + + ispCrop.size().expandTo(maxYuvSize); + + /* + * Now apply the scaler crop to each enabled output. This overrides the + * crop configuration performed at configure() time and can cause + * square pixels if the crop rectangle and scaler output FOV ratio are + * different. + */ + for (MaliC55Pipe &pipe : pipes_) { + if (!pipe.stream) + continue; + + /* Create a copy to avoid setSelection() to modify ispCrop. */ + Rectangle pipeCrop = ispCrop; + ret = pipe.resizer->setSelection(0, V4L2_SEL_TGT_CROP, &pipeCrop); + if (ret) { + LOG(MaliC55, Error) + << "Failed to apply crop to " + << (pipe.stream == &data->frStream_ ? + "FR" : "DS") << " pipe"; + return; + } + } +} + +int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request) +{ + MaliC55CameraData *data = cameraData(camera); + + /* Do not run the IPA if the TPG is in use. */ + if (!data->ipa_) { + MaliC55FrameInfo frameInfo; + frameInfo.request = request; + frameInfo.statBuffer = nullptr; + frameInfo.paramBuffer = nullptr; + frameInfo.paramsDone = true; + frameInfo.statsDone = true; + + frameInfoMap_[request->sequence()] = frameInfo; + + for (auto &[stream, buffer] : request->buffers()) { + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + pipe->cap->queueBuffer(buffer); + } + + return 0; + } + + if (availableStatsBuffers_.empty()) { + LOG(MaliC55, Error) << "Stats buffer underrun"; + return -ENOENT; + } + + if (availableParamsBuffers_.empty()) { + LOG(MaliC55, Error) << "Params buffer underrun"; + return -ENOENT; + } + + MaliC55FrameInfo frameInfo; + frameInfo.request = request; + + frameInfo.statBuffer = availableStatsBuffers_.front(); + availableStatsBuffers_.pop(); + frameInfo.paramBuffer = availableParamsBuffers_.front(); + availableParamsBuffers_.pop(); + + frameInfo.paramsDone = false; + frameInfo.statsDone = false; + + frameInfoMap_[request->sequence()] = frameInfo; + + data->ipa_->queueRequest(request->sequence(), request->controls()); + data->ipa_->fillParams(request->sequence(), + frameInfo.paramBuffer->cookie()); + + return 0; +} + +MaliC55FrameInfo *PipelineHandlerMaliC55::findFrameInfo(Request *request) +{ + for (auto &[sequence, info] : frameInfoMap_) { + if (info.request == request) + return &info; + } + + return nullptr; +} + +MaliC55FrameInfo *PipelineHandlerMaliC55::findFrameInfo(FrameBuffer *buffer) +{ + for (auto &[sequence, info] : frameInfoMap_) { + if (info.paramBuffer == buffer || + info.statBuffer == buffer) + return &info; + } + + return nullptr; +} + +void PipelineHandlerMaliC55::tryComplete(MaliC55FrameInfo *info) +{ + if (!info->paramsDone) + return; + if (!info->statsDone) + return; + + Request *request = info->request; + if (request->hasPendingBuffers()) + return; + + if (info->statBuffer) + availableStatsBuffers_.push(info->statBuffer); + if (info->paramBuffer) + availableParamsBuffers_.push(info->paramBuffer); + + frameInfoMap_.erase(request->sequence()); + + completeRequest(request); +} + +void PipelineHandlerMaliC55::imageBufferReady(FrameBuffer *buffer) +{ + Request *request = buffer->request(); + MaliC55FrameInfo *info = findFrameInfo(request); + ASSERT(info); + + if (completeBuffer(request, buffer)) + tryComplete(info); +} + +void PipelineHandlerMaliC55::paramsBufferReady(FrameBuffer *buffer) +{ + MaliC55FrameInfo *info = findFrameInfo(buffer); + ASSERT(info); + + info->paramsDone = true; + + tryComplete(info); +} + +void PipelineHandlerMaliC55::statsBufferReady(FrameBuffer *buffer) +{ + MaliC55FrameInfo *info = findFrameInfo(buffer); + ASSERT(info); + + Request *request = info->request; + MaliC55CameraData *data = cameraData(request->_d()->camera()); + + ControlList sensorControls = data->delayedCtrls_->get(buffer->metadata().sequence); + + data->ipa_->processStats(request->sequence(), buffer->cookie(), + sensorControls); +} + +void PipelineHandlerMaliC55::paramsComputed(unsigned int requestId) +{ + MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId]; + Request *request = frameInfo.request; + MaliC55CameraData *data = cameraData(request->_d()->camera()); + + /* + * Queue buffers for stats and params, then queue buffers to the capture + * video devices. + */ + + frameInfo.paramBuffer->_d()->metadata().planes()[0].bytesused = + sizeof(struct mali_c55_params_buffer); + params_->queueBuffer(frameInfo.paramBuffer); + stats_->queueBuffer(frameInfo.statBuffer); + + for (auto &[stream, buffer] : request->buffers()) { + MaliC55Pipe *pipe = pipeFromStream(data, stream); + + pipe->cap->queueBuffer(buffer); + } +} + +void PipelineHandlerMaliC55::statsProcessed(unsigned int requestId, + const ControlList &metadata) +{ + MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId]; + + frameInfo.statsDone = true; + frameInfo.request->metadata().merge(metadata); + + tryComplete(&frameInfo); +} + +bool PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data, + const std::string &name) +{ + if (data->loadIPA()) + return false; + + if (data->ipa_) { + data->ipa_->statsProcessed.connect(this, &PipelineHandlerMaliC55::statsProcessed); + data->ipa_->paramsComputed.connect(this, &PipelineHandlerMaliC55::paramsComputed); + } + + std::set<Stream *> streams{ &data->frStream_ }; + if (dsFitted_) + streams.insert(&data->dsStream_); + + std::shared_ptr<Camera> camera = Camera::create(std::move(data), + name, streams); + registerCamera(std::move(camera)); + + return true; +} + +/* + * The only camera we support through direct connection to the ISP is the + * Mali-C55 TPG. Check we have that and warn if not. + */ +bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link) +{ + const std::string &name = link->source()->entity()->name(); + if (name != "mali-c55 tpg") { + LOG(MaliC55, Warning) << "Unsupported direct connection to " + << link->source()->entity()->name(); + /* + * Return true and just skip registering a camera for this + * entity. + */ + return true; + } + + std::unique_ptr<MaliC55CameraData> data = + std::make_unique<MaliC55CameraData>(this, link->source()->entity()); + + if (data->init()) + return false; + + return registerMaliCamera(std::move(data), name); +} + +/* + * Register a Camera for each sensor connected to the ISP through a CSI-2 + * receiver. + * + * \todo Support more complex topologies, such as video muxes. + */ +bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink) +{ + MediaEntity *csi2 = ispLink->source()->entity(); + const MediaPad *csi2Sink = csi2->getPadByIndex(0); + + for (MediaLink *link : csi2Sink->links()) { + MediaEntity *sensor = link->source()->entity(); + unsigned int function = sensor->function(); + + if (function != MEDIA_ENT_F_CAM_SENSOR) + continue; + + std::unique_ptr<MaliC55CameraData> data = + std::make_unique<MaliC55CameraData>(this, sensor); + if (data->init()) + return false; + + data->properties_ = data->sensor_->properties(); + + const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); + std::unordered_map<uint32_t, DelayedControls::ControlParams> params = { + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + }; + + data->delayedCtrls_ = + std::make_unique<DelayedControls>(data->sensor_->device(), + params); + isp_->frameStart.connect(data->delayedCtrls_.get(), + &DelayedControls::applyControls); + + /* \todo Init properties. */ + + if (!registerMaliCamera(std::move(data), sensor->name())) + return false; + } + + return true; +} + +bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator) +{ + const MediaPad *ispSink; + + /* + * We search for just the always-available elements of the media graph. + * The TPG and the downscale pipe are both optional blocks and may not + * be fitted. + */ + DeviceMatch dm("mali-c55"); + dm.add("mali-c55 isp"); + dm.add("mali-c55 resizer fr"); + dm.add("mali-c55 fr"); + dm.add("mali-c55 3a stats"); + dm.add("mali-c55 3a params"); + + media_ = acquireMediaDevice(enumerator, dm); + if (!media_) + return false; + + isp_ = V4L2Subdevice::fromEntityName(media_, "mali-c55 isp"); + if (isp_->open() < 0) + return false; + + stats_ = V4L2VideoDevice::fromEntityName(media_, "mali-c55 3a stats"); + if (stats_->open() < 0) + return false; + + params_ = V4L2VideoDevice::fromEntityName(media_, "mali-c55 3a params"); + if (params_->open() < 0) + return false; + + MaliC55Pipe *frPipe = &pipes_[MaliC55FR]; + frPipe->resizer = V4L2Subdevice::fromEntityName(media_, "mali-c55 resizer fr"); + if (frPipe->resizer->open() < 0) + return false; + + frPipe->cap = V4L2VideoDevice::fromEntityName(media_, "mali-c55 fr"); + if (frPipe->cap->open() < 0) + return false; + + frPipe->link = media_->link("mali-c55 resizer fr", 1, "mali-c55 fr", 0); + if (!frPipe->link) { + LOG(MaliC55, Error) << "No link between fr resizer and video node"; + return false; + } + + frPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::imageBufferReady); + + dsFitted_ = !!media_->getEntityByName("mali-c55 ds"); + if (dsFitted_) { + LOG(MaliC55, Debug) << "Downscaler pipe is fitted"; + + MaliC55Pipe *dsPipe = &pipes_[MaliC55DS]; + + dsPipe->resizer = V4L2Subdevice::fromEntityName(media_, "mali-c55 resizer ds"); + if (dsPipe->resizer->open() < 0) + return false; + + dsPipe->cap = V4L2VideoDevice::fromEntityName(media_, "mali-c55 ds"); + if (dsPipe->cap->open() < 0) + return false; + + dsPipe->link = media_->link("mali-c55 resizer ds", 1, + "mali-c55 ds", 0); + if (!dsPipe->link) { + LOG(MaliC55, Error) << "No link between ds resizer and video node"; + return false; + } + + dsPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::imageBufferReady); + } + + stats_->bufferReady.connect(this, &PipelineHandlerMaliC55::statsBufferReady); + params_->bufferReady.connect(this, &PipelineHandlerMaliC55::paramsBufferReady); + + ispSink = isp_->entity()->getPadByIndex(0); + if (!ispSink || ispSink->links().empty()) { + LOG(MaliC55, Error) << "ISP sink pad error"; + return false; + } + + /* + * We could have several links pointing to the ISP's sink pad, which + * will be from entities with one of the following functions: + * + * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator + * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver + * MEDIA_ENT_F_IO_V4L - An input device + * + * The last one will be unsupported for now. The TPG is relatively easy, + * we just register a Camera for it. If we have a CSI-2 receiver we need + * to check its sink pad and register Cameras for anything connected to + * it (probably...there are some complex situations in which that might + * not be true but let's pretend they don't exist until we come across + * them) + */ + bool registered; + for (MediaLink *link : ispSink->links()) { + unsigned int function = link->source()->entity()->function(); + + switch (function) { + case MEDIA_ENT_F_CAM_SENSOR: + registered = registerTPGCamera(link); + if (!registered) + return registered; + + break; + case MEDIA_ENT_F_VID_IF_BRIDGE: + registered = registerSensorCamera(link); + if (!registered) + return registered; + + break; + case MEDIA_ENT_F_IO_V4L: + LOG(MaliC55, Warning) << "Memory input not yet supported"; + break; + default: + LOG(MaliC55, Error) << "Unsupported entity function"; + return false; + } + } + + return true; +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerMaliC55, "mali-c55") + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build new file mode 100644 index 00000000..eba8e5a3 --- /dev/null +++ b/src/libcamera/pipeline/mali-c55/meson.build @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_sources += files([ + 'mali-c55.cpp' +]) diff --git a/src/libcamera/pipeline/rkisp1/meson.build b/src/libcamera/pipeline/rkisp1/meson.build index cad66535..d21a6ef9 100644 --- a/src/libcamera/pipeline/rkisp1/meson.build +++ b/src/libcamera/pipeline/rkisp1/meson.build @@ -1,6 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'rkisp1.cpp', 'rkisp1_path.cpp', ]) diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 586b46d6..675f0a74 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -2,15 +2,16 @@ /* * Copyright (C) 2019, Google Inc. * - * rkisp1.cpp - Pipeline handler for Rockchip ISP1 + * Pipeline handler for Rockchip ISP1 */ #include <algorithm> -#include <array> -#include <iomanip> +#include <map> #include <memory> #include <numeric> +#include <optional> #include <queue> +#include <vector> #include <linux/media-bus-format.h> #include <linux/rkisp1-config.h> @@ -23,6 +24,7 @@ #include <libcamera/control_ids.h> #include <libcamera/formats.h> #include <libcamera/framebuffer.h> +#include <libcamera/property_ids.h> #include <libcamera/request.h> #include <libcamera/stream.h> #include <libcamera/transform.h> @@ -33,11 +35,14 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" +#include "libcamera/internal/converter/converter_v4l2_m2m.h" #include "libcamera/internal/delayed_controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/media_device.h" +#include "libcamera/internal/media_pipeline.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -94,6 +99,7 @@ public: } PipelineHandlerRkISP1 *pipe(); + const PipelineHandlerRkISP1 *pipe() const; int loadIPA(unsigned int hwRevision); Stream mainPathStream_; @@ -109,8 +115,15 @@ public: std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_; + ControlInfoMap ipaControls_; + + /* + * All entities in the pipeline, from the camera sensor to the RKISP1. + */ + MediaPipeline pipe_; + private: - void paramFilled(unsigned int frame); + void paramsComputed(unsigned int frame, unsigned int bytesused); void setSensorControls(unsigned int frame, const ControlList &sensorControls); @@ -170,25 +183,27 @@ private: } friend RkISP1CameraData; + friend RkISP1CameraConfiguration; friend RkISP1Frames; - int initLinks(Camera *camera, const CameraSensor *sensor, - const RkISP1CameraConfiguration &config); + int initLinks(Camera *camera, const RkISP1CameraConfiguration &config); int createCamera(MediaEntity *sensor); void tryCompleteRequest(RkISP1FrameInfo *info); - void bufferReady(FrameBuffer *buffer); - void paramReady(FrameBuffer *buffer); - void statReady(FrameBuffer *buffer); + void imageBufferReady(FrameBuffer *buffer); + void paramBufferReady(FrameBuffer *buffer); + void statBufferReady(FrameBuffer *buffer); + void dewarpBufferReady(FrameBuffer *buffer); void frameStart(uint32_t sequence); int allocateBuffers(Camera *camera); int freeBuffers(Camera *camera); + int updateControls(RkISP1CameraData *data); + MediaDevice *media_; std::unique_ptr<V4L2Subdevice> isp_; std::unique_ptr<V4L2VideoDevice> param_; std::unique_ptr<V4L2VideoDevice> stat_; - std::unique_ptr<V4L2Subdevice> csi_; bool hasSelfPath_; bool isRaw_; @@ -196,14 +211,22 @@ private: RkISP1MainPath mainPath_; RkISP1SelfPath selfPath_; + std::unique_ptr<V4L2M2MConverter> dewarper_; + Rectangle scalerMaxCrop_; + bool useDewarper_; + + std::optional<Rectangle> activeCrop_; + + /* Internal buffers used when dewarper is being used */ + std::vector<std::unique_ptr<FrameBuffer>> mainPathBuffers_; + std::queue<FrameBuffer *> availableMainPathBuffers_; + std::vector<std::unique_ptr<FrameBuffer>> paramBuffers_; std::vector<std::unique_ptr<FrameBuffer>> statBuffers_; std::queue<FrameBuffer *> availableParamBuffers_; std::queue<FrameBuffer *> availableStatBuffers_; Camera *activeCamera_; - - const MediaPad *ispSink_; }; RkISP1Frames::RkISP1Frames(PipelineHandler *pipe) @@ -218,6 +241,8 @@ RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *req FrameBuffer *paramBuffer = nullptr; FrameBuffer *statBuffer = nullptr; + FrameBuffer *mainPathBuffer = nullptr; + FrameBuffer *selfPathBuffer = nullptr; if (!isRaw) { if (pipe_->availableParamBuffers_.empty()) { @@ -235,10 +260,16 @@ RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *req statBuffer = pipe_->availableStatBuffers_.front(); pipe_->availableStatBuffers_.pop(); + + if (pipe_->useDewarper_) { + mainPathBuffer = pipe_->availableMainPathBuffers_.front(); + pipe_->availableMainPathBuffers_.pop(); + } } - FrameBuffer *mainPathBuffer = request->findBuffer(&data->mainPathStream_); - FrameBuffer *selfPathBuffer = request->findBuffer(&data->selfPathStream_); + if (!mainPathBuffer) + mainPathBuffer = request->findBuffer(&data->mainPathStream_); + selfPathBuffer = request->findBuffer(&data->selfPathStream_); RkISP1FrameInfo *info = new RkISP1FrameInfo; @@ -264,6 +295,7 @@ int RkISP1Frames::destroy(unsigned int frame) pipe_->availableParamBuffers_.push(info->paramBuffer); pipe_->availableStatBuffers_.push(info->statBuffer); + pipe_->availableMainPathBuffers_.push(info->mainPathBuffer); frameInfo_.erase(info->frame); @@ -279,6 +311,7 @@ void RkISP1Frames::clear() pipe_->availableParamBuffers_.push(info->paramBuffer); pipe_->availableStatBuffers_.push(info->statBuffer); + pipe_->availableMainPathBuffers_.push(info->mainPathBuffer); delete info; } @@ -334,6 +367,11 @@ PipelineHandlerRkISP1 *RkISP1CameraData::pipe() return static_cast<PipelineHandlerRkISP1 *>(Camera::Private::pipe()); } +const PipelineHandlerRkISP1 *RkISP1CameraData::pipe() const +{ + return static_cast<const PipelineHandlerRkISP1 *>(Camera::Private::pipe()); +} + int RkISP1CameraData::loadIPA(unsigned int hwRevision) { ipa_ = IPAManager::createIPA<ipa::rkisp1::IPAProxyRkISP1>(pipe(), 1, 1); @@ -341,26 +379,12 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision) return -ENOENT; ipa_->setSensorControls.connect(this, &RkISP1CameraData::setSensorControls); - ipa_->paramsBufferReady.connect(this, &RkISP1CameraData::paramFilled); + ipa_->paramsComputed.connect(this, &RkISP1CameraData::paramsComputed); ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady); - /* - * The API tuning file is made from the sensor name unless the - * environment variable overrides it. - */ - std::string ipaTuningFile; - char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RKISP1_TUNING_FILE"); - if (!configFromEnv || *configFromEnv == '\0') { - ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml"); - /* - * If the tuning file isn't found, fall back to the - * 'uncalibrated' configuration file. - */ - if (ipaTuningFile.empty()) - ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml"); - } else { - ipaTuningFile = std::string(configFromEnv); - } + /* The IPA tuning file is made from the sensor name. */ + std::string ipaTuningFile = + ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml"); IPACameraSensorInfo sensorInfo{}; int ret = sensor_->sensorInfo(&sensorInfo); @@ -370,7 +394,7 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision) } ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision, - sensorInfo, sensor_->controls(), &controlInfo_); + sensorInfo, sensor_->controls(), &ipaControls_); if (ret < 0) { LOG(RkISP1, Error) << "IPA initialization failure"; return ret; @@ -379,15 +403,14 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision) return 0; } -void RkISP1CameraData::paramFilled(unsigned int frame) +void RkISP1CameraData::paramsComputed(unsigned int frame, unsigned int bytesused) { PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe(); RkISP1FrameInfo *info = frameInfo_.find(frame); if (!info) return; - info->paramBuffer->_d()->metadata().planes()[0].bytesused = - sizeof(struct rkisp1_params_cfg); + info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused; pipe->param_->queueBuffer(info->paramBuffer); pipe->stat_->queueBuffer(info->statBuffer); @@ -454,11 +477,12 @@ bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg) StreamConfiguration config; config = cfg; - if (data_->mainPath_->validate(sensor, &config) != Valid) + if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid) return false; config = cfg; - if (data_->selfPath_ && data_->selfPath_->validate(sensor, &config) != Valid) + if (data_->selfPath_ && + data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid) return false; return true; @@ -466,6 +490,7 @@ bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg) CameraConfiguration::Status RkISP1CameraConfiguration::validate() { + const PipelineHandlerRkISP1 *pipe = data_->pipe(); const CameraSensor *sensor = data_->sensor_.get(); unsigned int pathCount = data_->selfPath_ ? 2 : 1; Status status; @@ -475,6 +500,27 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace); + /* + * Make sure that if a sensor configuration has been requested it + * is valid. + */ + if (sensorConfig) { + if (!sensorConfig->isValid()) { + LOG(RkISP1, Error) + << "Invalid sensor configuration request"; + + return Invalid; + } + + unsigned int bitDepth = sensorConfig->bitDepth; + if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) { + LOG(RkISP1, Error) + << "Invalid sensor configuration bit depth"; + + return Invalid; + } + } + /* Cap the number of entries to the available streams. */ if (config_.size() > pathCount) { config_.resize(pathCount); @@ -501,6 +547,18 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() } } + bool useDewarper = false; + if (pipe->dewarper_) { + /* + * Platforms with dewarper support, such as i.MX8MP, support + * only a single stream. We can inspect config_[0] only here. + */ + bool isRaw = PixelFormatInfo::info(config_[0].pixelFormat).colourEncoding == + PixelFormatInfo::ColourEncodingRAW; + if (!isRaw) + useDewarper = true; + } + /* * If there are more than one stream in the configuration figure out the * order to evaluate the streams. The first stream has the highest @@ -513,50 +571,72 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() if (config_.size() == 2 && fitsAllPaths(config_[0])) std::reverse(order.begin(), order.end()); + /* + * Validate the configuration against the desired path and, if the + * platform supports it, the dewarper. + */ + auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path, + Stream *stream, Status expectedStatus) { + StreamConfiguration tryCfg = cfg; + + Status ret = path->validate(sensor, sensorConfig, &tryCfg); + if (ret == Invalid) + return false; + + if (!useDewarper && + (expectedStatus == Valid && ret == Adjusted)) + return false; + + if (useDewarper) { + bool adjusted; + + pipe->dewarper_->validateOutput(&tryCfg, &adjusted, + Converter::Alignment::Down); + if (expectedStatus == Valid && adjusted) + return false; + } + + cfg = tryCfg; + cfg.setStream(stream); + return true; + }; + bool mainPathAvailable = true; bool selfPathAvailable = data_->selfPath_; + RkISP1Path *mainPath = data_->mainPath_; + RkISP1Path *selfPath = data_->selfPath_; + Stream *mainPathStream = const_cast<Stream *>(&data_->mainPathStream_); + Stream *selfPathStream = const_cast<Stream *>(&data_->selfPathStream_); for (unsigned int index : order) { StreamConfiguration &cfg = config_[index]; /* Try to match stream without adjusting configuration. */ if (mainPathAvailable) { - StreamConfiguration tryCfg = cfg; - if (data_->mainPath_->validate(sensor, &tryCfg) == Valid) { + if (validateConfig(cfg, mainPath, mainPathStream, Valid)) { mainPathAvailable = false; - cfg = tryCfg; - cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_)); continue; } } if (selfPathAvailable) { - StreamConfiguration tryCfg = cfg; - if (data_->selfPath_->validate(sensor, &tryCfg) == Valid) { + if (validateConfig(cfg, selfPath, selfPathStream, Valid)) { selfPathAvailable = false; - cfg = tryCfg; - cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_)); continue; } } /* Try to match stream allowing adjusting configuration. */ if (mainPathAvailable) { - StreamConfiguration tryCfg = cfg; - if (data_->mainPath_->validate(sensor, &tryCfg) == Adjusted) { + if (validateConfig(cfg, mainPath, mainPathStream, Adjusted)) { mainPathAvailable = false; - cfg = tryCfg; - cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_)); status = Adjusted; continue; } } if (selfPathAvailable) { - StreamConfiguration tryCfg = cfg; - if (data_->selfPath_->validate(sensor, &tryCfg) == Adjusted) { + if (validateConfig(cfg, selfPath, selfPathStream, Adjusted)) { selfPathAvailable = false; - cfg = tryCfg; - cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_)); status = Adjusted; continue; } @@ -590,7 +670,8 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() [](const auto &value) { return value.second; }); } - sensorFormat_ = sensor->getFormat(mbusCodes, maxSize); + sensorFormat_ = sensor->getFormat(mbusCodes, maxSize, + mainPath->maxResolution()); if (sensorFormat_.size.isNull()) sensorFormat_.size = sensor->resolution(); @@ -603,7 +684,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() */ PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager) - : PipelineHandler(manager), hasSelfPath_(true) + : PipelineHandler(manager), hasSelfPath_(true), useDewarper_(false) { } @@ -719,7 +800,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) CameraSensor *sensor = data->sensor_.get(); int ret; - ret = initLinks(camera, sensor, *config); + ret = initLinks(camera, *config); if (ret) return ret; @@ -730,44 +811,70 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) V4L2SubdeviceFormat format = config->sensorFormat(); LOG(RkISP1, Debug) << "Configuring sensor with " << format; - ret = sensor->setFormat(&format, config->combinedTransform()); + if (config->sensorConfig) + ret = sensor->applyConfiguration(*config->sensorConfig, + config->combinedTransform(), + &format); + else + ret = sensor->setFormat(&format, config->combinedTransform()); + if (ret < 0) return ret; LOG(RkISP1, Debug) << "Sensor configured with " << format; - if (csi_) { - ret = csi_->setFormat(0, &format); - if (ret < 0) - return ret; - } + /* Propagate format through the internal media pipeline up to the ISP */ + ret = data->pipe_.configure(sensor, &format); + if (ret < 0) + return ret; + LOG(RkISP1, Debug) << "Configuring ISP with : " << format; ret = isp_->setFormat(0, &format); if (ret < 0) return ret; - Rectangle rect(0, 0, format.size); - ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &rect); + Rectangle inputCrop(0, 0, format.size); + ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &inputCrop); if (ret < 0) return ret; LOG(RkISP1, Debug) << "ISP input pad configured with " << format - << " crop " << rect; + << " crop " << inputCrop; + Rectangle outputCrop = inputCrop; const PixelFormat &streamFormat = config->at(0).pixelFormat; const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat); isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW; + useDewarper_ = dewarper_ && !isRaw_; /* YUYV8_2X8 is required on the ISP source path pad for YUV output. */ if (!isRaw_) - format.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8; + format.code = MEDIA_BUS_FMT_YUYV8_2X8; + + /* + * On devices without DUAL_CROP (like the imx8mp) cropping needs to be + * done on the ISP/IS output. + */ + if (media_->hwRevision() == RKISP1_V_IMX8MP) { + /* imx8mp has only a single path. */ + const auto &cfg = config->at(0); + Size ispCrop = format.size.boundedToAspectRatio(cfg.size); + if (useDewarper_) + ispCrop = dewarper_->adjustInputSize(cfg.pixelFormat, + ispCrop); + else + ispCrop.alignUpTo(2, 2); + + outputCrop = ispCrop.centeredTo(Rectangle(format.size).center()); + format.size = ispCrop; + } LOG(RkISP1, Debug) << "Configuring ISP output pad with " << format - << " crop " << rect; + << " crop " << outputCrop; - ret = isp_->setSelection(2, V4L2_SEL_TGT_CROP, &rect); + ret = isp_->setSelection(2, V4L2_SEL_TGT_CROP, &outputCrop); if (ret < 0) return ret; @@ -778,15 +885,37 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) LOG(RkISP1, Debug) << "ISP output pad configured with " << format - << " crop " << rect; + << " crop " << outputCrop; + + IPACameraSensorInfo sensorInfo; + ret = data->sensor_->sensorInfo(&sensorInfo); + if (ret) + return ret; std::map<unsigned int, IPAStream> streamConfig; + std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs; for (const StreamConfiguration &cfg : *config) { if (cfg.stream() == &data->mainPathStream_) { ret = mainPath_.configure(cfg, format); streamConfig[0] = IPAStream(cfg.pixelFormat, cfg.size); + /* Configure dewarp */ + if (dewarper_ && !isRaw_) { + outputCfgs.push_back(const_cast<StreamConfiguration &>(cfg)); + ret = dewarper_->configure(cfg, outputCfgs); + if (ret) + return ret; + + /* + * Calculate the crop rectangle of the data + * flowing into the dewarper in sensor + * coordinates. + */ + scalerMaxCrop_ = + outputCrop.transformedBetween(inputCrop, + sensorInfo.analogCrop); + } } else if (hasSelfPath_) { ret = selfPath_.configure(cfg, format); streamConfig[1] = IPAStream(cfg.pixelFormat, @@ -800,7 +929,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) } V4L2DeviceFormat paramFormat; - paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_PARAMS); + paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS); ret = param_->setFormat(¶mFormat); if (ret) return ret; @@ -812,20 +941,17 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) return ret; /* Inform IPA of stream configuration and sensor controls. */ - ipa::rkisp1::IPAConfigInfo ipaConfig{}; + ipa::rkisp1::IPAConfigInfo ipaConfig{ sensorInfo, + data->sensor_->controls(), + paramFormat.fourcc }; - ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo); - if (ret) - return ret; - - ipaConfig.sensorControls = data->sensor_->controls(); - - ret = data->ipa_->configure(ipaConfig, streamConfig, &data->controlInfo_); + ret = data->ipa_->configure(ipaConfig, streamConfig, &data->ipaControls_); if (ret) { LOG(RkISP1, Error) << "failed configuring IPA (" << ret << ")"; return ret; } - return 0; + + return updateControls(data); } int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream, @@ -834,10 +960,19 @@ int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, S RkISP1CameraData *data = cameraData(camera); unsigned int count = stream->configuration().bufferCount; - if (stream == &data->mainPathStream_) - return mainPath_.exportBuffers(count, buffers); - else if (hasSelfPath_ && stream == &data->selfPathStream_) + if (stream == &data->mainPathStream_) { + /* + * Currently, i.MX8MP is the only platform with DW100 dewarper. + * It has mainpath and no self path. Hence, export buffers from + * dewarper just for the main path stream, for now. + */ + if (useDewarper_) + return dewarper_->exportBuffers(&data->mainPathStream_, count, buffers); + else + return mainPath_.exportBuffers(count, buffers); + } else if (hasSelfPath_ && stream == &data->selfPathStream_) { return selfPath_.exportBuffers(count, buffers); + } return -EINVAL; } @@ -863,6 +998,16 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera) goto error; } + /* If the dewarper is being used, allocate internal buffers for ISP. */ + if (useDewarper_) { + ret = mainPath_.exportBuffers(maxCount, &mainPathBuffers_); + if (ret < 0) + goto error; + + for (std::unique_ptr<FrameBuffer> &buffer : mainPathBuffers_) + availableMainPathBuffers_.push(buffer.get()); + } + for (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) { buffer->setCookie(ipaBufferId++); data->ipaBuffers_.emplace_back(buffer->cookie(), @@ -884,6 +1029,7 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera) error: paramBuffers_.clear(); statBuffers_.clear(); + mainPathBuffers_.clear(); return ret; } @@ -898,8 +1044,12 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera) while (!availableParamBuffers_.empty()) availableParamBuffers_.pop(); + while (!availableMainPathBuffers_.empty()) + availableMainPathBuffers_.pop(); + paramBuffers_.clear(); statBuffers_.clear(); + mainPathBuffers_.clear(); std::vector<unsigned int> ids; for (IPABuffer &ipabuf : data->ipaBuffers_) @@ -920,71 +1070,71 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera) int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlList *controls) { RkISP1CameraData *data = cameraData(camera); + utils::ScopeExitActions actions; int ret; /* Allocate buffers for internal pipeline usage. */ ret = allocateBuffers(camera); if (ret) return ret; + actions += [&]() { freeBuffers(camera); }; ret = data->ipa_->start(); if (ret) { - freeBuffers(camera); LOG(RkISP1, Error) << "Failed to start IPA " << camera->id(); return ret; } + actions += [&]() { data->ipa_->stop(); }; data->frame_ = 0; if (!isRaw_) { ret = param_->streamOn(); if (ret) { - data->ipa_->stop(); - freeBuffers(camera); LOG(RkISP1, Error) << "Failed to start parameters " << camera->id(); return ret; } + actions += [&]() { param_->streamOff(); }; ret = stat_->streamOn(); if (ret) { - param_->streamOff(); - data->ipa_->stop(); - freeBuffers(camera); LOG(RkISP1, Error) << "Failed to start statistics " << camera->id(); return ret; } + actions += [&]() { stat_->streamOff(); }; + + if (useDewarper_) { + ret = dewarper_->start(); + if (ret) { + LOG(RkISP1, Error) << "Failed to start dewarper"; + return ret; + } + actions += [&]() { dewarper_->stop(); }; + } } if (data->mainPath_->isEnabled()) { ret = mainPath_.start(); - if (ret) { - param_->streamOff(); - stat_->streamOff(); - data->ipa_->stop(); - freeBuffers(camera); + if (ret) return ret; - } + actions += [&]() { mainPath_.stop(); }; } if (hasSelfPath_ && data->selfPath_->isEnabled()) { ret = selfPath_.start(); - if (ret) { - mainPath_.stop(); - param_->streamOff(); - stat_->streamOff(); - data->ipa_->stop(); - freeBuffers(camera); + if (ret) return ret; - } } isp_->setFrameStartEnabled(true); activeCamera_ = camera; - return ret; + + actions.release(); + return 0; } void PipelineHandlerRkISP1::stopDevice(Camera *camera) @@ -1010,6 +1160,9 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera) if (ret) LOG(RkISP1, Warning) << "Failed to stop parameters for " << camera->id(); + + if (useDewarper_) + dewarper_->stop(); } ASSERT(data->queuedRequests_.empty()); @@ -1036,8 +1189,8 @@ int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request) if (data->selfPath_ && info->selfPathBuffer) data->selfPath_->queueBuffer(info->selfPathBuffer); } else { - data->ipa_->fillParamsBuffer(data->frame_, - info->paramBuffer->cookie()); + data->ipa_->computeParams(data->frame_, + info->paramBuffer->cookie()); } data->frame_++; @@ -1050,7 +1203,6 @@ int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request) */ int PipelineHandlerRkISP1::initLinks(Camera *camera, - const CameraSensor *sensor, const RkISP1CameraConfiguration &config) { RkISP1CameraData *data = cameraData(camera); @@ -1061,31 +1213,16 @@ int PipelineHandlerRkISP1::initLinks(Camera *camera, return ret; /* - * Configure the sensor links: enable the link corresponding to this - * camera. + * Configure the sensor links: enable the links corresponding to this + * pipeline all the way up to the ISP, through any connected CSI receiver. */ - for (MediaLink *link : ispSink_->links()) { - if (link->source()->entity() != sensor->entity()) - continue; - - LOG(RkISP1, Debug) - << "Enabling link from sensor '" - << link->source()->entity()->name() - << "' to ISP"; - - ret = link->setEnabled(true); - if (ret < 0) - return ret; - } - - if (csi_) { - MediaLink *link = isp_->entity()->getPadByIndex(0)->links().at(0); - - ret = link->setEnabled(true); - if (ret < 0) - return ret; + ret = data->pipe_.initLinks(); + if (ret) { + LOG(RkISP1, Error) << "Failed to set up pipe links"; + return ret; } + /* Configure the paths after the ISP */ for (const StreamConfiguration &cfg : config) { if (cfg.stream() == &data->mainPathStream_) ret = data->mainPath_->setEnabled(true); @@ -1101,6 +1238,58 @@ int PipelineHandlerRkISP1::initLinks(Camera *camera, return 0; } +/** + * \brief Update the camera controls + * \param[in] data The camera data + * + * Compute the camera controls by calculating controls which the pipeline + * is reponsible for and merge them with the controls computed by the IPA. + * + * This function needs data->ipaControls_ to be refreshed when a new + * configuration is applied to the camera by the IPA configure() function. + * + * Always call this function after IPA configure() to make sure to have a + * properly refreshed IPA controls list. + * + * \return 0 on success or a negative error code otherwise + */ +int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data) +{ + ControlInfoMap::Map controls; + + if (dewarper_) { + std::pair<Rectangle, Rectangle> cropLimits; + if (dewarper_->isConfigured(&data->mainPathStream_)) + cropLimits = dewarper_->inputCropBounds(&data->mainPathStream_); + else + cropLimits = dewarper_->inputCropBounds(); + + /* + * ScalerCrop is specified to be in Sensor coordinates. + * So we need to transform the limits to sensor coordinates. + * We can safely assume that the maximum crop limit contains the + * full fov of the dewarper. + */ + Rectangle min = cropLimits.first.transformedBetween(cropLimits.second, + scalerMaxCrop_); + + controls[&controls::ScalerCrop] = ControlInfo(min, + scalerMaxCrop_, + scalerMaxCrop_); + data->properties_.set(properties::ScalerCropMaximum, scalerMaxCrop_); + activeCrop_ = scalerMaxCrop_; + } + + /* Add the IPA registered controls to list of camera controls. */ + for (const auto &ipaControl : data->ipaControls_) + controls[ipaControl.first] = ipaControl.second; + + data->controlInfo_ = ControlInfoMap(std::move(controls), + controls::controls); + + return 0; +} + int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) { int ret; @@ -1109,22 +1298,27 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) std::make_unique<RkISP1CameraData>(this, &mainPath_, hasSelfPath_ ? &selfPath_ : nullptr); - data->sensor_ = std::make_unique<CameraSensor>(sensor); - ret = data->sensor_->init(); - if (ret) + /* Identify the pipeline path between the sensor and the rkisp1_isp */ + ret = data->pipe_.init(sensor, "rkisp1_isp"); + if (ret) { + LOG(RkISP1, Error) << "Failed to identify path from sensor to sink"; return ret; + } + + data->sensor_ = CameraSensorFactoryBase::create(sensor); + if (!data->sensor_) + return -ENODEV; /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); - /* - * \todo Read dealy values from the sensor itself or from a - * a sensor database. For now use generic values taken from - * the Raspberry Pi and listed as generic values. - */ + scalerMaxCrop_ = Rectangle(data->sensor_->resolution()); + + const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); std::unordered_map<uint32_t, DelayedControls::ControlParams> params = { - { V4L2_CID_ANALOGUE_GAIN, { 1, false } }, - { V4L2_CID_EXPOSURE, { 2, false } }, + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, false } }, }; data->delayedCtrls_ = @@ -1137,6 +1331,8 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) if (ret) return ret; + updateControls(data.get()); + std::set<Stream *> streams{ &data->mainPathStream_, &data->selfPathStream_, @@ -1144,6 +1340,7 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) const std::string &id = data->sensor_->id(); std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams); + registerCamera(std::move(camera)); return 0; @@ -1151,8 +1348,6 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator) { - const MediaPad *pad; - DeviceMatch dm("rkisp1"); dm.add("rkisp1_isp"); dm.add("rkisp1_resizer_mainpath"); @@ -1177,22 +1372,6 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator) if (isp_->open() < 0) return false; - /* Locate and open the optional CSI-2 receiver. */ - ispSink_ = isp_->entity()->getPadByIndex(0); - if (!ispSink_ || ispSink_->links().empty()) - return false; - - pad = ispSink_->links().at(0)->source(); - if (pad->entity()->function() == MEDIA_ENT_F_VID_IF_BRIDGE) { - csi_ = std::make_unique<V4L2Subdevice>(pad->entity()); - if (csi_->open() < 0) - return false; - - ispSink_ = csi_->entity()->getPadByIndex(0); - if (!ispSink_) - return false; - } - /* Locate and open the stats and params video nodes. */ stat_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1_stats"); if (stat_->open() < 0) @@ -1209,19 +1388,44 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator) if (hasSelfPath_ && !selfPath_.init(media_)) return false; - mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady); + mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady); if (hasSelfPath_) - selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::bufferReady); - stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statReady); - param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramReady); + selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady); + stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statBufferReady); + param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramBufferReady); + + /* If dewarper is present, create its instance. */ + DeviceMatch dwp("dw100"); + dwp.add("dw100-source"); + dwp.add("dw100-sink"); + + std::shared_ptr<MediaDevice> dwpMediaDevice = enumerator->search(dwp); + if (dwpMediaDevice) { + dewarper_ = std::make_unique<V4L2M2MConverter>(dwpMediaDevice.get()); + if (dewarper_->isValid()) { + dewarper_->outputBufferReady.connect( + this, &PipelineHandlerRkISP1::dewarpBufferReady); + + LOG(RkISP1, Info) + << "Using DW100 dewarper " << dewarper_->deviceNode(); + } else { + LOG(RkISP1, Warning) + << "Found DW100 dewarper " << dewarper_->deviceNode() + << " but invalid"; + + dewarper_.reset(); + } + } /* * Enumerate all sensors connected to the ISP and create one * camera instance for each of them. */ bool registered = false; - for (MediaLink *link : ispSink_->links()) { - if (!createCamera(link->source()->entity())) + + for (MediaEntity *entity : media_->locateEntities(MEDIA_ENT_F_CAM_SENSOR)) { + LOG(RkISP1, Debug) << "Identified " << entity->name(); + if (!createCamera(entity)) registered = true; } @@ -1251,7 +1455,7 @@ void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info) completeRequest(request); } -void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer) +void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); @@ -1261,7 +1465,7 @@ void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer) return; const FrameMetadata &metadata = buffer->metadata(); - Request *request = buffer->request(); + Request *request = info->request; if (metadata.status != FrameMetadata::FrameCancelled) { /* @@ -1276,18 +1480,102 @@ void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer) if (isRaw_) { const ControlList &ctrls = data->delayedCtrls_->get(metadata.sequence); - data->ipa_->processStatsBuffer(info->frame, 0, ctrls); + data->ipa_->processStats(info->frame, 0, ctrls); } } else { if (isRaw_) info->metadataProcessed = true; } + if (!useDewarper_) { + completeBuffer(request, buffer); + tryCompleteRequest(info); + + return; + } + + /* Do not queue cancelled frames to dewarper. */ + if (metadata.status == FrameMetadata::FrameCancelled) { + /* + * i.MX8MP is the only known platform with dewarper. It has + * no self path. Hence, only main path buffer completion is + * required. + * + * Also, we cannot completeBuffer(request, buffer) as buffer + * here, is an internal buffer (between ISP and dewarper) and + * is not associated to the any specific request. The request + * buffer associated with main path stream is the one that + * is required to be completed (not the internal buffer). + */ + for (auto it : request->buffers()) { + if (it.first == &data->mainPathStream_) + completeBuffer(request, it.second); + } + + tryCompleteRequest(info); + return; + } + + /* Handle scaler crop control. */ + const auto &crop = request->controls().get(controls::ScalerCrop); + if (crop) { + Rectangle rect = crop.value(); + + /* + * ScalerCrop is specified to be in Sensor coordinates. + * So we need to transform it into dewarper coordinates. + * We can safely assume that the maximum crop limit contains the + * full fov of the dewarper. + */ + std::pair<Rectangle, Rectangle> cropLimits = + dewarper_->inputCropBounds(&data->mainPathStream_); + + rect = rect.transformedBetween(scalerMaxCrop_, cropLimits.second); + int ret = dewarper_->setInputCrop(&data->mainPathStream_, + &rect); + rect = rect.transformedBetween(cropLimits.second, scalerMaxCrop_); + if (!ret && rect != crop.value()) { + /* + * If the rectangle is changed by setInputCrop on the + * dewarper, log a debug message and cache the actual + * applied rectangle for metadata reporting. + */ + LOG(RkISP1, Debug) + << "Applied rectangle " << rect.toString() + << " differs from requested " << crop.value().toString(); + } + + activeCrop_ = rect; + } + + /* + * Queue input and output buffers to the dewarper. The output + * buffers for the dewarper are the buffers of the request, supplied + * by the application. + */ + int ret = dewarper_->queueBuffers(buffer, request->buffers()); + if (ret < 0) + LOG(RkISP1, Error) << "Cannot queue buffers to dewarper: " + << strerror(-ret); + + request->metadata().set(controls::ScalerCrop, activeCrop_.value()); +} + +void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer) +{ + ASSERT(activeCamera_); + RkISP1CameraData *data = cameraData(activeCamera_); + Request *request = buffer->request(); + + RkISP1FrameInfo *info = data->frameInfo_.find(buffer->request()); + if (!info) + return; + completeBuffer(request, buffer); tryCompleteRequest(info); } -void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer) +void PipelineHandlerRkISP1::paramBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); @@ -1300,7 +1588,7 @@ void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer) tryCompleteRequest(info); } -void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer) +void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); @@ -1318,10 +1606,10 @@ void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer) if (data->frame_ <= buffer->metadata().sequence) data->frame_ = buffer->metadata().sequence + 1; - data->ipa_->processStatsBuffer(info->frame, info->statBuffer->cookie(), - data->delayedCtrls_->get(buffer->metadata().sequence)); + data->ipa_->processStats(info->frame, info->statBuffer->cookie(), + data->delayedCtrls_->get(buffer->metadata().sequence)); } -REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1) +REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1, "rkisp1") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp index b62b645c..64018dc5 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * rkisp1path.cpp - Rockchip ISP1 path helper + * Rockchip ISP1 path helper */ #include "rkisp1_path.h" @@ -126,12 +126,50 @@ void RkISP1Path::populateFormats() } } +/** + * \brief Filter the sensor resolutions that can be supported + * \param[in] sensor The camera sensor + * + * This function retrieves all the sizes supported by the sensor and + * filters all the resolutions that can be supported on the pipeline. + * It is possible that the sensor's maximum output resolution is higher + * than the ISP maximum input. In that case, this function filters out all + * the resolution incapable of being supported and returns the maximum + * sensor resolution that can be supported by the pipeline. + * + * \return Maximum sensor size supported on the pipeline + */ +Size RkISP1Path::filterSensorResolution(const CameraSensor *sensor) +{ + auto iter = sensorSizesMap_.find(sensor); + if (iter != sensorSizesMap_.end()) + return iter->second.back(); + + std::vector<Size> &sizes = sensorSizesMap_[sensor]; + for (unsigned int code : sensor->mbusCodes()) { + for (const Size &size : sensor->sizes(code)) { + if (size.width > maxResolution_.width || + size.height > maxResolution_.height) + continue; + + sizes.push_back(size); + } + } + + /* Sort in increasing order and remove duplicates. */ + std::sort(sizes.begin(), sizes.end()); + auto last = std::unique(sizes.begin(), sizes.end()); + sizes.erase(last, sizes.end()); + + return sizes.back(); +} + StreamConfiguration RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, StreamRole role) { const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes(); - const Size &resolution = sensor->resolution(); + Size resolution = filterSensorResolution(sensor); /* Min and max resolutions to populate the available stream formats. */ Size maxResolution = maxResolution_.boundedToAspectRatio(resolution) @@ -216,11 +254,13 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, return cfg; } -CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor, - StreamConfiguration *cfg) +CameraConfiguration::Status +RkISP1Path::validate(const CameraSensor *sensor, + const std::optional<SensorConfiguration> &sensorConfig, + StreamConfiguration *cfg) { const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes(); - const Size &resolution = sensor->resolution(); + Size resolution = filterSensorResolution(sensor); const StreamConfiguration reqCfg = *cfg; CameraConfiguration::Status status = CameraConfiguration::Valid; @@ -247,9 +287,14 @@ CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor, continue; /* - * Store the raw format with the highest bits per pixel - * for later usage. + * If the bits per pixel is supplied from the sensor + * configuration, choose a raw format that complies with + * it. Otherwise, store the raw format with the highest + * bits per pixel for later usage. */ + if (sensorConfig && info.bitsPerPixel != sensorConfig->bitDepth) + continue; + if (info.bitsPerPixel > rawBitsPerPixel) { rawBitsPerPixel = info.bitsPerPixel; rawFormat = format; @@ -262,6 +307,9 @@ CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor, } } + if (sensorConfig && !rawFormat.isValid()) + return CameraConfiguration::Invalid; + bool isRaw = PixelFormatInfo::info(cfg->pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW; @@ -281,14 +329,48 @@ CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor, if (isRaw) { /* * Use the sensor output size closest to the requested stream - * size. + * size while ensuring the output size doesn't exceed ISP limits. + * + * As 'resolution' is the largest sensor resolution + * supported by the ISP, CameraSensor::getFormat() will never + * return a V4L2SubdeviceFormat with a larger size. */ uint32_t mbusCode = formatToMediaBus.at(cfg->pixelFormat); + cfg->size.boundTo(resolution); + + Size rawSize = sensorConfig ? sensorConfig->outputSize + : cfg->size; + V4L2SubdeviceFormat sensorFormat = - sensor->getFormat({ mbusCode }, cfg->size); + sensor->getFormat({ mbusCode }, rawSize); + + if (sensorConfig && + sensorConfig->outputSize != sensorFormat.size) + return CameraConfiguration::Invalid; minResolution = sensorFormat.size; maxResolution = sensorFormat.size; + } else if (sensorConfig) { + /* + * We have already ensured 'rawFormat' has the matching bit + * depth with sensorConfig.bitDepth hence, only validate the + * sensorConfig's output size here. + */ + Size sensorSize = sensorConfig->outputSize; + + if (sensorSize > resolution) + return CameraConfiguration::Invalid; + + uint32_t mbusCode = formatToMediaBus.at(rawFormat); + V4L2SubdeviceFormat sensorFormat = + sensor->getFormat({ mbusCode }, sensorSize); + + if (sensorFormat.size != sensorSize) + return CameraConfiguration::Invalid; + + minResolution = minResolution_.expandedToAspectRatio(sensorSize); + maxResolution = maxResolution_.boundedTo(sensorSize) + .boundedToAspectRatio(sensorSize); } else { /* * Adjust the size based on the sensor resolution and absolute @@ -338,11 +420,14 @@ int RkISP1Path::configure(const StreamConfiguration &config, /* * Crop on the resizer input to maintain FOV before downscaling. * - * \todo The alignment to a multiple of 2 pixels is required but may - * change the aspect ratio very slightly. A more advanced algorithm to - * compute the resizer input crop rectangle is needed, and it should - * also take into account the need to crop away the edge pixels affected - * by the ISP processing blocks. + * Note that this does not apply to devices without DUAL_CROP support + * (like imx8mp) , where the cropping needs to be done on the + * ImageStabilizer block on the ISP source pad and therefore is + * configured before this stage. For simplicity we still set the crop. + * This gets ignored by the kernel driver because the hardware is + * missing the capability. + * + * Alignment to a multiple of 2 pixels is required by the resizer. */ Size ispCrop = inputFormat.size.boundedToAspectRatio(config.size) .alignedUpTo(2, 2); @@ -365,7 +450,7 @@ int RkISP1Path::configure(const StreamConfiguration &config, * The configuration has been validated, the pixel format is guaranteed * to be supported and thus found in formatToMediaBus. */ - ispFormat.mbus_code = formatToMediaBus.at(config.pixelFormat); + ispFormat.code = formatToMediaBus.at(config.pixelFormat); ret = resizer_->setFormat(1, &ispFormat); if (ret < 0) @@ -435,8 +520,8 @@ void RkISP1Path::stop() } /* - * \todo Remove the hardcoded resolutions and formats once all users will have - * migrated to a recent enough kernel. + * \todo Remove the hardcoded resolutions and formats once kernels older than + * v6.4 will stop receiving LTS support (scheduled for December 2027 for v6.1). */ namespace { constexpr Size RKISP1_RSZ_MP_SRC_MIN{ 32, 16 }; diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h index cd77957e..430181d3 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2020, Google Inc. * - * rkisp1path.h - Rockchip ISP1 path helper + * Rockchip ISP1 path helper */ #pragma once +#include <map> #include <memory> #include <set> #include <vector> @@ -25,6 +26,7 @@ namespace libcamera { class CameraSensor; class MediaDevice; +class SensorConfiguration; class V4L2Subdevice; struct StreamConfiguration; struct V4L2SubdeviceFormat; @@ -44,6 +46,7 @@ public: const Size &resolution, StreamRole role); CameraConfiguration::Status validate(const CameraSensor *sensor, + const std::optional<SensorConfiguration> &sensorConfig, StreamConfiguration *cfg); int configure(const StreamConfiguration &config, @@ -60,9 +63,11 @@ public: int queueBuffer(FrameBuffer *buffer) { return video_->queueBuffer(buffer); } Signal<FrameBuffer *> &bufferReady() { return video_->bufferReady; } + const Size &maxResolution() const { return maxResolution_; } private: void populateFormats(); + Size filterSensorResolution(const CameraSensor *sensor); static constexpr unsigned int RKISP1_BUFFER_COUNT = 4; @@ -77,6 +82,12 @@ private: std::unique_ptr<V4L2Subdevice> resizer_; std::unique_ptr<V4L2VideoDevice> video_; MediaLink *link_; + + /* + * Map from camera sensors to the sizes (in increasing order), + * which are guaranteed to be supported by the pipeline. + */ + std::map<const CameraSensor *, std::vector<Size>> sensorSizesMap_; }; class RkISP1MainPath : public RkISP1Path diff --git a/src/libcamera/pipeline/rpi/common/delayed_controls.cpp b/src/libcamera/pipeline/rpi/common/delayed_controls.cpp index 3db92e7d..ad50a7c8 100644 --- a/src/libcamera/pipeline/rpi/common/delayed_controls.cpp +++ b/src/libcamera/pipeline/rpi/common/delayed_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * delayed_controls.cpp - Helper to deal with controls that take effect with a delay + * Helper to deal with controls that take effect with a delay * * Note: This has been forked from the libcamera core implementation. */ diff --git a/src/libcamera/pipeline/rpi/common/delayed_controls.h b/src/libcamera/pipeline/rpi/common/delayed_controls.h index 61f755f0..487b0057 100644 --- a/src/libcamera/pipeline/rpi/common/delayed_controls.h +++ b/src/libcamera/pipeline/rpi/common/delayed_controls.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * delayed_controls.h - Helper to deal with controls that take effect with a delay + * Helper to deal with controls that take effect with a delay * * Note: This has been forked from the libcamera core implementation. */ diff --git a/src/libcamera/pipeline/rpi/common/meson.build b/src/libcamera/pipeline/rpi/common/meson.build index 8fb7e823..b2b1a0a6 100644 --- a/src/libcamera/pipeline/rpi/common/meson.build +++ b/src/libcamera/pipeline/rpi/common/meson.build @@ -1,6 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'delayed_controls.cpp', 'pipeline_base.cpp', 'rpi_stream.cpp', diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index ee222d06..1f13e523 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2023, Raspberry Pi Ltd * - * pipeline_base.cpp - Pipeline handler base class for Raspberry Pi devices + * Pipeline handler base class for Raspberry Pi devices */ #include "pipeline_base.h" @@ -37,10 +37,10 @@ namespace { constexpr unsigned int defaultRawBitDepth = 12; -PixelFormat mbusCodeToPixelFormat(unsigned int mbus_code, +PixelFormat mbusCodeToPixelFormat(unsigned int code, BayerFormat::Packing packingReq) { - BayerFormat bayer = BayerFormat::fromMbusCode(mbus_code); + BayerFormat bayer = BayerFormat::fromMbusCode(code); ASSERT(bayer.isValid()); @@ -105,7 +105,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_ Status status = Valid; yuvColorSpace_.reset(); - for (auto cfg : config_) { + for (auto &cfg : config_) { /* First fix up raw streams to have the "raw" colour space. */ if (PipelineHandlerBase::isRaw(cfg.pixelFormat)) { /* If there was no value here, that doesn't count as "adjusted". */ @@ -130,7 +130,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_ rgbColorSpace_->range = ColorSpace::Range::Full; /* Go through the streams again and force everyone to the same colour space. */ - for (auto cfg : config_) { + for (auto &cfg : config_) { if (cfg.colorSpace == ColorSpace::Raw) continue; @@ -181,12 +181,14 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() rawStreams_.clear(); outStreams_.clear(); + unsigned int rawStreamIndex = 0; + unsigned int outStreamIndex = 0; - for (const auto &[index, cfg] : utils::enumerate(config_)) { + for (auto &cfg : config_) { if (PipelineHandlerBase::isRaw(cfg.pixelFormat)) - rawStreams_.emplace_back(index, &cfg); + rawStreams_.emplace_back(rawStreamIndex++, &cfg); else - outStreams_.emplace_back(index, &cfg); + outStreams_.emplace_back(outStreamIndex++, &cfg); } /* Sort the streams so the highest resolution is first. */ @@ -221,7 +223,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() * without modifications. */ if (sensorConfig) { - BayerFormat bayer = BayerFormat::fromMbusCode(sensorFormat_.mbus_code); + BayerFormat bayer = BayerFormat::fromMbusCode(sensorFormat_.code); if (bayer.bitDepth != sensorConfig->bitDepth || sensorFormat_.size != sensorConfig->outputSize) { @@ -235,24 +237,16 @@ CameraConfiguration::Status RPiCameraConfiguration::validate() for (auto &raw : rawStreams_) { StreamConfiguration *rawStream = raw.cfg; - /* Adjust the RAW stream to match the computed sensor format. */ - BayerFormat sensorBayer = BayerFormat::fromMbusCode(sensorFormat_.mbus_code); - /* - * Some sensors change their Bayer order when they are h-flipped - * or v-flipped, according to the transform. If this one does, we - * must advertise the transformed Bayer order in the raw stream. - * Note how we must fetch the "native" (i.e. untransformed) Bayer - * order, because the sensor may currently be flipped! + * Some sensors change their Bayer order when they are + * h-flipped or v-flipped, according to the transform. Adjust + * the RAW stream to match the computed sensor format by + * applying the sensor Bayer order resulting from the transform + * to the user request. */ - if (data_->flipsAlterBayerOrder_) { - sensorBayer.order = data_->nativeBayerOrder_; - sensorBayer = sensorBayer.transform(combinedTransform_); - } - /* Apply the sensor adjusted Bayer order to the user request. */ BayerFormat cfgBayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat); - cfgBayer.order = sensorBayer.order; + cfgBayer.order = data_->sensor_->bayerOrder(combinedTransform_); if (rawStream->pixelFormat != cfgBayer.toPixelFormat()) { rawStream->pixelFormat = cfgBayer.toPixelFormat(); @@ -367,6 +361,7 @@ V4L2DeviceFormat PipelineHandlerBase::toV4L2DeviceFormat(const V4L2VideoDevice * deviceFormat.planesCount = info.numPlanes(); deviceFormat.fourcc = dev->toV4L2PixelFormat(stream->pixelFormat); deviceFormat.size = stream->size; + deviceFormat.planes[0].bpl = stream->stride; deviceFormat.colorSpace = stream->colorSpace; return deviceFormat; @@ -376,8 +371,8 @@ V4L2DeviceFormat PipelineHandlerBase::toV4L2DeviceFormat(const V4L2VideoDevice * const V4L2SubdeviceFormat &format, BayerFormat::Packing packingReq) { - unsigned int mbus_code = format.mbus_code; - const PixelFormat pix = mbusCodeToPixelFormat(mbus_code, packingReq); + unsigned int code = format.code; + const PixelFormat pix = mbusCodeToPixelFormat(code, packingReq); V4L2DeviceFormat deviceFormat; deviceFormat.fourcc = dev->toV4L2PixelFormat(pix); @@ -408,7 +403,7 @@ PipelineHandlerBase::generateConfiguration(Camera *camera, Span<const StreamRole case StreamRole::Raw: size = sensorSize; sensorFormat = data->findBestFormat(size, defaultRawBitDepth); - pixelFormat = mbusCodeToPixelFormat(sensorFormat.mbus_code, + pixelFormat = mbusCodeToPixelFormat(sensorFormat.code, BayerFormat::Packing::CSI2); ASSERT(pixelFormat.isValid()); colorSpace = ColorSpace::Raw; @@ -481,7 +476,11 @@ PipelineHandlerBase::generateConfiguration(Camera *camera, Span<const StreamRole */ for (const auto &format : fmts) { PixelFormat pf = format.first.toPixelFormat(); - if (pf.isValid()) { + /* + * Some V4L2 formats translate to the same pixel format (e.g. YU12, YM12 + * both give YUV420). We must avoid duplicating the range in this case. + */ + if (pf.isValid() && deviceFormats.find(pf) == deviceFormats.end()) { const SizeRange &ispSizes = format.second[0]; deviceFormats[pf].emplace_back(ispSizes.min, sensorSize, ispSizes.hStep, ispSizes.vStep); @@ -499,8 +498,6 @@ PipelineHandlerBase::generateConfiguration(Camera *camera, Span<const StreamRole config->addConfiguration(cfg); } - config->validate(); - return config; } @@ -538,6 +535,7 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config) * Platform specific internal stream configuration. This also assigns * external streams which get configured below. */ + data->cropParams_.clear(); ret = data->platformConfigure(rpiConfig); if (ret) return ret; @@ -550,12 +548,6 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config) } /* - * Set the scaler crop to the value we are using (scaled to native sensor - * coordinates). - */ - data->scalerCrop_ = data->scaleIspCrop(data->ispCrop_); - - /* * Update the ScalerCropMaximum to the correct value for this camera mode. * For us, it's the same as the "analogue crop". * @@ -572,9 +564,28 @@ int PipelineHandlerBase::configure(Camera *camera, CameraConfiguration *config) for (auto const &c : result.controlInfo) ctrlMap.emplace(c.first, c.second); - /* Add the ScalerCrop control limits based on the current mode. */ - Rectangle ispMinCrop = data->scaleIspCrop(Rectangle(data->ispMinCropSize_)); - ctrlMap[&controls::ScalerCrop] = ControlInfo(ispMinCrop, data->sensorInfo_.analogCrop, data->scalerCrop_); + const auto cropParamsIt = data->cropParams_.find(0); + if (cropParamsIt != data->cropParams_.end()) { + const CameraData::CropParams &cropParams = cropParamsIt->second; + /* + * Add the ScalerCrop control limits based on the current mode and + * the first configured stream. + */ + Rectangle ispMinCrop = data->scaleIspCrop(Rectangle(cropParams.ispMinCropSize)); + ctrlMap[&controls::ScalerCrop] = ControlInfo(ispMinCrop, data->sensorInfo_.analogCrop, + data->scaleIspCrop(cropParams.ispCrop)); + if (data->cropParams_.size() == 2) { + /* + * The control map for rpi::ScalerCrops has the min value + * as the default crop for stream 0, max value as the default + * value for stream 1. + */ + ctrlMap[&controls::rpi::ScalerCrops] = + ControlInfo(data->scaleIspCrop(data->cropParams_.at(0).ispCrop), + data->scaleIspCrop(data->cropParams_.at(1).ispCrop), + ctrlMap[&controls::ScalerCrop].def()); + } + } data->controlInfo_ = ControlInfoMap(std::move(ctrlMap), result.controlInfo.idmap()); @@ -731,7 +742,8 @@ int PipelineHandlerBase::queueRequestDevice(Camera *camera, Request *request) if (!data->isRunning()) return -EINVAL; - LOG(RPI, Debug) << "queueRequestDevice: New request."; + LOG(RPI, Debug) << "queueRequestDevice: New request sequence: " + << request->sequence(); /* Push all buffers supplied in the Request to the respective streams. */ for (auto stream : data->streams_) { @@ -778,13 +790,10 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera CameraData *data = cameraData.get(); int ret; - data->sensor_ = std::make_unique<CameraSensor>(sensorEntity); + data->sensor_ = CameraSensorFactoryBase::create(sensorEntity); if (!data->sensor_) return -EINVAL; - if (data->sensor_->init()) - return -EINVAL; - /* Populate the map of sensor supported formats and sizes. */ for (auto const mbusCode : data->sensor_->mbusCodes()) data->sensorFormats_.emplace(mbusCode, @@ -807,11 +816,12 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera * Setup our delayed control writer with the sensor default * gain and exposure delays. Mark VBLANK for priority write. */ + const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); std::unordered_map<uint32_t, RPi::DelayedControls::ControlParams> params = { - { V4L2_CID_ANALOGUE_GAIN, { result.sensorConfig.gainDelay, false } }, - { V4L2_CID_EXPOSURE, { result.sensorConfig.exposureDelay, false } }, - { V4L2_CID_HBLANK, { result.sensorConfig.hblankDelay, false } }, - { V4L2_CID_VBLANK, { result.sensorConfig.vblankDelay, true } } + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_HBLANK, { delays.hblankDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } } }; data->delayedCtrls_ = std::make_unique<RPi::DelayedControls>(data->sensor_->device(), params); data->sensorMetadata_ = result.sensorConfig.sensorMetadata; @@ -838,40 +848,6 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera */ data->properties_.set(properties::ScalerCropMaximum, Rectangle{}); - /* - * We cache two things about the sensor in relation to transforms - * (meaning horizontal and vertical flips): if they affect the Bayer - * ordering, and what the "native" Bayer order is, when no transforms - * are applied. - * - * If flips are supported verify if they affect the Bayer ordering - * and what the "native" Bayer order is, when no transforms are - * applied. - * - * We note that the sensor's cached list of supported formats is - * already in the "native" order, with any flips having been undone. - */ - const V4L2Subdevice *sensor = data->sensor_->device(); - const struct v4l2_query_ext_ctrl *hflipCtrl = sensor->controlInfo(V4L2_CID_HFLIP); - if (hflipCtrl) { - /* We assume it will support vflips too... */ - data->flipsAlterBayerOrder_ = hflipCtrl->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT; - } - - /* Look for a valid Bayer format. */ - BayerFormat bayerFormat; - for (const auto &iter : data->sensorFormats_) { - bayerFormat = BayerFormat::fromMbusCode(iter.first); - if (bayerFormat.isValid()) - break; - } - - if (!bayerFormat.isValid()) { - LOG(RPI, Error) << "No Bayer format found"; - return -EINVAL; - } - data->nativeBayerOrder_ = bayerFormat.order; - ret = platformRegister(cameraData, frontend, backend); if (ret) return ret; @@ -988,7 +964,7 @@ V4L2SubdeviceFormat CameraData::findBestFormat(const Size &req, unsigned int bit if (score <= bestScore) { bestScore = score; - bestFormat.mbus_code = mbusCode; + bestFormat.code = mbusCode; bestFormat.size = size; } @@ -1180,20 +1156,11 @@ int CameraData::loadIPA(ipa::RPi::InitResult *result) if (!ipa_) return -ENOENT; - /* - * The configuration (tuning file) is made from the sensor name unless - * the environment variable overrides it. - */ - std::string configurationFile; - char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RPI_TUNING_FILE"); - if (!configFromEnv || *configFromEnv == '\0') { - std::string model = sensor_->model(); - if (isMonoSensor(sensor_)) - model += "_mono"; - configurationFile = ipa_->configurationFile(model + ".json"); - } else { - configurationFile = std::string(configFromEnv); - } + /* The configuration (tuning file) is made from the sensor name. */ + std::string model = sensor_->model(); + if (isMonoSensor(sensor_)) + model += "_mono"; + std::string configurationFile = ipa_->configurationFile(model + ".json"); IPASettings settings(configurationFile, sensor_->model()); ipa::RPi::InitParams params; @@ -1214,7 +1181,6 @@ int CameraData::loadIPA(ipa::RPi::InitResult *result) int CameraData::configureIPA(const CameraConfiguration *config, ipa::RPi::ConfigResult *result) { - std::map<unsigned int, ControlInfoMap> entityControls; ipa::RPi::ConfigParams params; int ret; @@ -1334,9 +1300,30 @@ Rectangle CameraData::scaleIspCrop(const Rectangle &ispCrop) const void CameraData::applyScalerCrop(const ControlList &controls) { - const auto &scalerCrop = controls.get<Rectangle>(controls::ScalerCrop); - if (scalerCrop) { - Rectangle nativeCrop = *scalerCrop; + const auto &scalerCropRPi = controls.get<Span<const Rectangle>>(controls::rpi::ScalerCrops); + const auto &scalerCropCore = controls.get<Rectangle>(controls::ScalerCrop); + std::vector<Rectangle> scalerCrops; + + /* + * First thing to do is create a vector of crops to apply to each ISP output + * based on either controls::ScalerCrop or controls::rpi::ScalerCrops if + * present. + * + * If controls::rpi::ScalerCrops is preset, apply the given crops to the + * ISP output streams, indexed by the same order in which they had been + * configured. This is not the same as the ISP output index. Otherwise + * if controls::ScalerCrop is present, apply the same crop to all ISP + * output streams. + */ + for (unsigned int i = 0; i < cropParams_.size(); i++) { + if (scalerCropRPi && i < scalerCropRPi->size()) + scalerCrops.push_back(scalerCropRPi->data()[i]); + else if (scalerCropCore) + scalerCrops.push_back(*scalerCropCore); + } + + for (auto const &[i, scalerCrop] : utils::enumerate(scalerCrops)) { + Rectangle nativeCrop = scalerCrop; if (!nativeCrop.width || !nativeCrop.height) nativeCrop = { 0, 0, 1, 1 }; @@ -1352,20 +1339,13 @@ void CameraData::applyScalerCrop(const ControlList &controls) * 2. With the same mid-point, if possible. * 3. But it can't go outside the sensor area. */ - Size minSize = ispMinCropSize_.expandedToAspectRatio(nativeCrop.size()); + Size minSize = cropParams_.at(i).ispMinCropSize.expandedToAspectRatio(nativeCrop.size()); Size size = ispCrop.size().expandedTo(minSize); ispCrop = size.centeredTo(ispCrop.center()).enclosedIn(Rectangle(sensorInfo_.outputSize)); - if (ispCrop != ispCrop_) { - ispCrop_ = ispCrop; - platformSetIspCrop(); - - /* - * Also update the ScalerCrop in the metadata with what we actually - * used. But we must first rescale that from ISP (camera mode) pixels - * back into sensor native pixels. - */ - scalerCrop_ = scaleIspCrop(ispCrop_); + if (ispCrop != cropParams_.at(i).ispCrop) { + cropParams_.at(i).ispCrop = ispCrop; + platformSetIspCrop(cropParams_.at(i).ispIndex, ispCrop); } } } @@ -1437,6 +1417,8 @@ void CameraData::handleStreamBuffer(FrameBuffer *buffer, RPi::Stream *stream) * Tag the buffer as completed, returning it to the * application. */ + LOG(RPI, Debug) << "Completing request buffer for stream " + << stream->name(); pipe()->completeBuffer(request, buffer); } else { /* @@ -1445,6 +1427,8 @@ void CameraData::handleStreamBuffer(FrameBuffer *buffer, RPi::Stream *stream) * unconditionally for internal streams), or there is no pending * request, so we can recycle it. */ + LOG(RPI, Debug) << "Returning buffer to stream " + << stream->name(); stream->returnBuffer(buffer); } } @@ -1488,6 +1472,9 @@ void CameraData::checkRequestCompleted() if (state_ != State::IpaComplete) return; + LOG(RPI, Debug) << "Completing request sequence: " + << request->sequence(); + pipe()->completeRequest(request); requestQueue_.pop(); requestCompleted = true; @@ -1500,6 +1487,7 @@ void CameraData::checkRequestCompleted() if (state_ == State::IpaComplete && ((ispOutputCount_ == ispOutputTotal_ && dropFrameCount_) || requestCompleted)) { + LOG(RPI, Debug) << "Going into Idle state"; state_ = State::Idle; if (dropFrameCount_) { dropFrameCount_--; @@ -1514,7 +1502,18 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request request->metadata().set(controls::SensorTimestamp, bufferControls.get(controls::SensorTimestamp).value_or(0)); - request->metadata().set(controls::ScalerCrop, scalerCrop_); + if (cropParams_.size()) { + std::vector<Rectangle> crops; + + for (auto const &[k, v] : cropParams_) + crops.push_back(scaleIspCrop(v.ispCrop)); + + request->metadata().set(controls::ScalerCrop, crops[0]); + if (crops.size() > 1) { + request->metadata().set(controls::rpi::ScalerCrops, + Span<const Rectangle>(crops.data(), crops.size())); + } + } } } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index 267eef11..aae0c2f3 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2023, Raspberry Pi Ltd * - * pipeline_base.h - Pipeline handler base class for Raspberry Pi devices + * Pipeline handler base class for Raspberry Pi devices */ #include <map> @@ -48,7 +48,7 @@ class CameraData : public Camera::Private public: CameraData(PipelineHandler *pipe) : Camera::Private(pipe), state_(State::Stopped), - flipsAlterBayerOrder_(false), dropFrameCount_(0), buffersAllocated_(false), + dropFrameCount_(0), buffersAllocated_(false), ispOutputCount_(0), ispOutputTotal_(0) { } @@ -83,7 +83,7 @@ public: Rectangle scaleIspCrop(const Rectangle &ispCrop) const; void applyScalerCrop(const ControlList &controls); - virtual void platformSetIspCrop() = 0; + virtual void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) = 0; void cameraTimeout(); void frameStarted(uint32_t sequence); @@ -131,15 +131,25 @@ public: std::queue<Request *> requestQueue_; - /* Store the "native" Bayer order (that is, with no transforms applied). */ - bool flipsAlterBayerOrder_; - BayerFormat::Order nativeBayerOrder_; - /* For handling digital zoom. */ IPACameraSensorInfo sensorInfo_; - Rectangle ispCrop_; /* crop in ISP (camera mode) pixels */ - Rectangle scalerCrop_; /* crop in sensor native pixels */ - Size ispMinCropSize_; + + struct CropParams { + CropParams(Rectangle ispCrop_, Size ispMinCropSize_, unsigned int ispIndex_) + : ispCrop(ispCrop_), ispMinCropSize(ispMinCropSize_), ispIndex(ispIndex_) + { + } + + /* Crop in ISP (camera mode) pixels */ + Rectangle ispCrop; + /* Minimum crop size in ISP output pixels */ + Size ispMinCropSize; + /* Index of the ISP output channel for this crop */ + unsigned int ispIndex; + }; + + /* Mapping of CropParams keyed by the output stream order in CameraConfiguration */ + std::map<unsigned int, CropParams> cropParams_; unsigned int dropFrameCount_; diff --git a/src/libcamera/pipeline/rpi/common/rpi_stream.cpp b/src/libcamera/pipeline/rpi/common/rpi_stream.cpp index 70f115f1..accf59eb 100644 --- a/src/libcamera/pipeline/rpi/common/rpi_stream.cpp +++ b/src/libcamera/pipeline/rpi/common/rpi_stream.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * rpi_stream.cpp - Raspberry Pi device stream abstraction class. + * Raspberry Pi device stream abstraction class. */ #include "rpi_stream.h" diff --git a/src/libcamera/pipeline/rpi/common/rpi_stream.h b/src/libcamera/pipeline/rpi/common/rpi_stream.h index fc2bdfe2..0dba1296 100644 --- a/src/libcamera/pipeline/rpi/common/rpi_stream.h +++ b/src/libcamera/pipeline/rpi/common/rpi_stream.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * rpi_stream.h - Raspberry Pi device stream abstraction class. + * Raspberry Pi device stream abstraction class. */ #pragma once @@ -14,6 +14,7 @@ #include <vector> #include <libcamera/base/flags.h> +#include <libcamera/base/utils.h> #include <libcamera/stream.h> @@ -81,6 +82,16 @@ public: * to be applied after ISP processing. */ Needs32bitConv = (1 << 4), + /* + * Indicates that the input stream needs a software 16-bit endian + * conversion to be applied before ISP processing. + */ + Needs16bitEndianSwap = (1 << 5), + /* + * Indicates that the input stream needs a software 14-bit to + * 16-bit unpacking. + */ + Needs14bitUnpack = (1 << 6), }; using StreamFlags = Flags<StreamFlag>; @@ -180,19 +191,14 @@ private: template<typename E, std::size_t N> class Device : public std::array<class Stream, N> { -private: - constexpr auto index(E e) const noexcept - { - return static_cast<std::underlying_type_t<E>>(e); - } public: Stream &operator[](E e) { - return std::array<class Stream, N>::operator[](index(e)); + return std::array<class Stream, N>::operator[](utils::to_underlying(e)); } const Stream &operator[](E e) const { - return std::array<class Stream, N>::operator[](index(e)); + return std::array<class Stream, N>::operator[](utils::to_underlying(e)); } }; diff --git a/src/libcamera/pipeline/rpi/common/shared_mem_object.h b/src/libcamera/pipeline/rpi/common/shared_mem_object.h deleted file mode 100644 index aa56c220..00000000 --- a/src/libcamera/pipeline/rpi/common/shared_mem_object.h +++ /dev/null @@ -1,128 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2023, Raspberry Pi Ltd - * - * shared_mem_object.h - Helper class for shared memory allocations - */ -#pragma once - -#include <cstddef> -#include <fcntl.h> -#include <string> -#include <sys/mman.h> -#include <sys/stat.h> -#include <unistd.h> -#include <utility> - -#include <libcamera/base/class.h> -#include <libcamera/base/shared_fd.h> - -namespace libcamera { - -namespace RPi { - -template<class T> -class SharedMemObject -{ -public: - static constexpr std::size_t SIZE = sizeof(T); - - SharedMemObject() - : obj_(nullptr) - { - } - - template<class... Args> - SharedMemObject(const std::string &name, Args &&...args) - : name_(name), obj_(nullptr) - { - void *mem; - int ret; - - ret = memfd_create(name_.c_str(), MFD_CLOEXEC); - if (ret < 0) - return; - - fd_ = SharedFD(std::move(ret)); - if (!fd_.isValid()) - return; - - ret = ftruncate(fd_.get(), SIZE); - if (ret < 0) - return; - - mem = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, - fd_.get(), 0); - if (mem == MAP_FAILED) - return; - - obj_ = new (mem) T(std::forward<Args>(args)...); - } - - SharedMemObject(SharedMemObject<T> &&rhs) - { - this->name_ = std::move(rhs.name_); - this->fd_ = std::move(rhs.fd_); - this->obj_ = rhs.obj_; - rhs.obj_ = nullptr; - } - - ~SharedMemObject() - { - if (obj_) { - obj_->~T(); - munmap(obj_, SIZE); - } - } - - /* Make SharedMemObject non-copyable for now. */ - LIBCAMERA_DISABLE_COPY(SharedMemObject) - - SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs) - { - this->name_ = std::move(rhs.name_); - this->fd_ = std::move(rhs.fd_); - this->obj_ = rhs.obj_; - rhs.obj_ = nullptr; - return *this; - } - - T *operator->() - { - return obj_; - } - - const T *operator->() const - { - return obj_; - } - - T &operator*() - { - return *obj_; - } - - const T &operator*() const - { - return *obj_; - } - - const SharedFD &fd() const - { - return fd_; - } - - explicit operator bool() const - { - return !!obj_; - } - -private: - std::string name_; - SharedFD fd_; - T *obj_; -}; - -} /* namespace RPi */ - -} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/pisp/data/example.yaml b/src/libcamera/pipeline/rpi/pisp/data/example.yaml new file mode 100644 index 00000000..d67e654a --- /dev/null +++ b/src/libcamera/pipeline/rpi/pisp/data/example.yaml @@ -0,0 +1,45 @@ +{ + "version": 1.0, + "target": "pisp", + + "pipeline_handler": + { + # Number of CFE config and stats buffers to allocate and use. A + # larger number minimises the possibility of dropping frames, + # but increases the latency for updating the HW configuration. + # + # "num_cfe_config_stats_buffers": 12, + + # Number of jobs to queue ahead to the CFE on startup. A larger + # number will increase latency for 3A changes, but may reduce + # avoidable frame drops. + # + # "num_cfe_config_queue": 2, + + # Override any request from the IPA to drop a number of startup + # frames. + # + # "disable_startup_frame_drops": false, + + # Custom timeout value (in ms) for camera to use. This overrides + # the value computed by the pipeline handler based on frame + # durations. + # + # Set this value to 0 to use the pipeline handler computed + # timeout value. + # + # "camera_timeout_value_ms": 0, + + # Disables temporal denoise functionality in the ISP pipeline. + # Disabling temporal denoise avoids allocating 2 additional + # Bayer framebuffers required for its operation. + # + # "disable_tdn": false, + + # Disables multiframe HDR functionality in the ISP pipeline. + # Disabling multiframe HDR avoids allocating 2 additional Bayer + # framebuffers required for its operation. + # + # "disable_hdr": false, + } +} diff --git a/src/libcamera/pipeline/rpi/pisp/data/meson.build b/src/libcamera/pipeline/rpi/pisp/data/meson.build new file mode 100644 index 00000000..17dfc435 --- /dev/null +++ b/src/libcamera/pipeline/rpi/pisp/data/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: CC0-1.0 + +conf_files = files([ + 'example.yaml', +]) + +install_data(conf_files, + install_dir : pipeline_data_dir / 'rpi' / 'pisp') diff --git a/src/libcamera/pipeline/rpi/pisp/meson.build b/src/libcamera/pipeline/rpi/pisp/meson.build new file mode 100644 index 00000000..178df94c --- /dev/null +++ b/src/libcamera/pipeline/rpi/pisp/meson.build @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_sources += files([ + 'pisp.cpp', +]) + +librt = cc.find_library('rt', required : true) +libpisp_dep = dependency('libpisp', fallback : ['libpisp', 'libpisp_dep']) + +libcamera_deps += [libpisp_dep, librt] + +subdir('data') diff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp new file mode 100644 index 00000000..91e7f4c9 --- /dev/null +++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp @@ -0,0 +1,2372 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Raspberry Pi Ltd + * + * pisp.cpp - Pipeline handler for PiSP based Raspberry Pi devices + */ + +#include <algorithm> +#include <fstream> +#include <memory> +#include <mutex> +#include <numeric> +#include <queue> +#include <set> +#include <sstream> +#include <string> +#include <sys/ioctl.h> +#include <unordered_map> +#include <vector> + +#include <linux/dma-buf.h> +#include <linux/v4l2-controls.h> +#include <linux/videodev2.h> + +#include <libcamera/base/shared_fd.h> +#include <libcamera/formats.h> + +#include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/shared_mem_object.h" + +#include "libpisp/backend/backend.hpp" +#include "libpisp/common/logging.hpp" +#include "libpisp/common/utils.hpp" +#include "libpisp/common/version.hpp" +#include "libpisp/frontend/frontend.hpp" +#include "libpisp/variants/variant.hpp" + +#include "../common/pipeline_base.h" +#include "../common/rpi_stream.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(RPI) + +using StreamFlag = RPi::Stream::StreamFlag; +using StreamParams = RPi::RPiCameraConfiguration::StreamParams; + +namespace { + +enum class Cfe : unsigned int { Output0, Embedded, Stats, Config }; +enum class Isp : unsigned int { Input, Output0, Output1, TdnInput, TdnOutput, + StitchInput, StitchOutput, Config }; + +/* Offset for all compressed buffers; mode for TDN and Stitch. */ +constexpr unsigned int DefaultCompressionOffset = 2048; +constexpr unsigned int DefaultCompressionMode = 1; + +const std::vector<std::pair<BayerFormat, unsigned int>> BayerToMbusCodeMap{ + { { BayerFormat::BGGR, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR8_1X8, }, + { { BayerFormat::GBRG, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG8_1X8, }, + { { BayerFormat::GRBG, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG8_1X8, }, + { { BayerFormat::RGGB, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB8_1X8, }, + { { BayerFormat::BGGR, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR10_1X10, }, + { { BayerFormat::GBRG, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG10_1X10, }, + { { BayerFormat::GRBG, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG10_1X10, }, + { { BayerFormat::RGGB, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB10_1X10, }, + { { BayerFormat::BGGR, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR12_1X12, }, + { { BayerFormat::GBRG, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG12_1X12, }, + { { BayerFormat::GRBG, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG12_1X12, }, + { { BayerFormat::RGGB, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB12_1X12, }, + { { BayerFormat::BGGR, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR14_1X14, }, + { { BayerFormat::GBRG, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG14_1X14, }, + { { BayerFormat::GRBG, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG14_1X14, }, + { { BayerFormat::RGGB, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB14_1X14, }, + { { BayerFormat::BGGR, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR16_1X16, }, + { { BayerFormat::GBRG, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG16_1X16, }, + { { BayerFormat::GRBG, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG16_1X16, }, + { { BayerFormat::RGGB, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB16_1X16, }, + { { BayerFormat::BGGR, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SBGGR16_1X16, }, + { { BayerFormat::GBRG, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SGBRG16_1X16, }, + { { BayerFormat::GRBG, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SGRBG16_1X16, }, + { { BayerFormat::RGGB, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SRGGB16_1X16, }, + { { BayerFormat::RGGB, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SRGGB16_1X16, }, + { { BayerFormat::MONO, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_Y16_1X16, }, + { { BayerFormat::MONO, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_Y16_1X16, }, +}; + +unsigned int bayerToMbusCode(const BayerFormat &bayer) +{ + const auto it = std::find_if(BayerToMbusCodeMap.begin(), BayerToMbusCodeMap.end(), + [bayer](const std::pair<BayerFormat, unsigned int> &match) { + return bayer == match.first; + }); + + if (it != BayerToMbusCodeMap.end()) + return it->second; + + return 0; +} + +uint32_t mbusCodeUnpacked16(unsigned int code) +{ + BayerFormat bayer = BayerFormat::fromMbusCode(code); + BayerFormat bayer16(bayer.order, 16, BayerFormat::Packing::None); + + return bayerToMbusCode(bayer16); +} + +uint8_t toPiSPBayerOrder(V4L2PixelFormat format) +{ + BayerFormat bayer = BayerFormat::fromV4L2PixelFormat(format); + + switch (bayer.order) { + case BayerFormat::Order::BGGR: + return PISP_BAYER_ORDER_BGGR; + case BayerFormat::Order::GBRG: + return PISP_BAYER_ORDER_GBRG; + case BayerFormat::Order::GRBG: + return PISP_BAYER_ORDER_GRBG; + case BayerFormat::Order::RGGB: + return PISP_BAYER_ORDER_RGGB; + case BayerFormat::Order::MONO: + return PISP_BAYER_ORDER_GREYSCALE; + default: + ASSERT(0); + return -1; + } +} + +pisp_image_format_config toPiSPImageFormat(V4L2DeviceFormat &format) +{ + pisp_image_format_config image = {}; + + image.width = format.size.width; + image.height = format.size.height; + image.stride = format.planes[0].bpl; + + PixelFormat pix = format.fourcc.toPixelFormat(); + + if (RPi::PipelineHandlerBase::isRaw(pix)) { + BayerFormat bayer = BayerFormat::fromPixelFormat(pix); + switch (bayer.packing) { + case BayerFormat::Packing::None: + image.format = PISP_IMAGE_FORMAT_BPS_16 + + PISP_IMAGE_FORMAT_UNCOMPRESSED; + break; + case BayerFormat::Packing::PISP1: + image.format = PISP_IMAGE_FORMAT_COMPRESSION_MODE_1; + break; + case BayerFormat::Packing::PISP2: + image.format = PISP_IMAGE_FORMAT_COMPRESSION_MODE_2; + break; + default: + ASSERT(0); + } + return image; + } + + switch (pix) { + case formats::YUV420: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_420 + + PISP_IMAGE_FORMAT_PLANARITY_PLANAR; + image.stride2 = image.stride / 2; + break; + case formats::NV12: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_420 + + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR; + image.stride2 = image.stride; + break; + case formats::NV21: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_420 + + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR + + PISP_IMAGE_FORMAT_ORDER_SWAPPED; + image.stride2 = image.stride; + break; + case formats::YUYV: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_422 + + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED; + break; + case formats::UYVY: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_422 + + PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED + + PISP_IMAGE_FORMAT_ORDER_SWAPPED; + break; + case formats::NV16: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_422 + + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR; + image.stride2 = image.stride; + break; + case formats::NV61: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + + PISP_IMAGE_FORMAT_BPS_8 + + PISP_IMAGE_FORMAT_SAMPLING_422 + + PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR + + PISP_IMAGE_FORMAT_ORDER_SWAPPED; + image.stride2 = image.stride; + break; + case formats::RGB888: + case formats::BGR888: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL; + break; + case formats::XRGB8888: + case formats::XBGR8888: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32; + break; + case formats::RGBX8888: + case formats::BGRX8888: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32 + + PISP_IMAGE_FORMAT_ORDER_SWAPPED; + break; + case formats::RGB161616: + case formats::BGR161616: + image.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_16; + break; + default: + LOG(RPI, Error) << "Pixel format " << pix << " unsupported"; + ASSERT(0); + } + + return image; +} + +void computeOptimalStride(V4L2DeviceFormat &format) +{ + pisp_image_format_config fmt = toPiSPImageFormat(format); + + libpisp::compute_optimal_stride(fmt); + + uint32_t fourcc = format.fourcc.fourcc(); + + /* + * For YUV420/422 non-multiplanar formats, double the U/V stride for the + * Y-plane to ensure we get the optimal alignment on all three planes. + */ + if (fourcc == V4L2_PIX_FMT_YUV420 || fourcc == V4L2_PIX_FMT_YUV422P || + fourcc == V4L2_PIX_FMT_YVU420) + fmt.stride = fmt.stride2 * 2; + + format.planes[0].bpl = fmt.stride; + format.planes[1].bpl = fmt.stride2; + format.planes[2].bpl = fmt.stride2; + + /* + * Need to set planesCount correctly so that V4L2VideoDevice::trySetFormatMultiplane() + * copies the bpl fields correctly. + */ + const PixelFormat &pixFormat = format.fourcc.toPixelFormat(); + const PixelFormatInfo &info = PixelFormatInfo::info(pixFormat); + format.planesCount = info.numPlanes(); +} + +void setupOutputClipping(const V4L2DeviceFormat &v4l2Format, + pisp_be_output_format_config &outputFormat) +{ + const PixelFormat &pixFormat = v4l2Format.fourcc.toPixelFormat(); + const PixelFormatInfo &info = PixelFormatInfo::info(pixFormat); + + if (info.colourEncoding != PixelFormatInfo::ColourEncodingYUV) + return; + + if (v4l2Format.colorSpace == ColorSpace::Sycc) { + outputFormat.lo = 0; + outputFormat.hi = 65535; + outputFormat.lo2 = 0; + outputFormat.hi2 = 65535; + } else if (v4l2Format.colorSpace == ColorSpace::Smpte170m || + v4l2Format.colorSpace == ColorSpace::Rec709) { + outputFormat.lo = 16 << 8; + outputFormat.hi = 235 << 8; + outputFormat.lo2 = 16 << 8; + outputFormat.hi2 = 240 << 8; + } else { + LOG(RPI, Warning) + << "Unrecognised colour space " + << ColorSpace::toString(v4l2Format.colorSpace) + << ", using full range"; + outputFormat.lo = 0; + outputFormat.hi = 65535; + outputFormat.lo2 = 0; + outputFormat.hi2 = 65535; + } +} + +int dmabufSyncStart(const SharedFD &fd) +{ + struct dma_buf_sync dma_sync {}; + dma_sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW; + + int ret = ::ioctl(fd.get(), DMA_BUF_IOCTL_SYNC, &dma_sync); + if (ret) + LOG(RPI, Error) << "failed to lock-sync-write dma buf"; + + return ret; +} + +int dmabufSyncEnd(const SharedFD &fd) +{ + struct dma_buf_sync dma_sync {}; + dma_sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW; + + int ret = ::ioctl(fd.get(), DMA_BUF_IOCTL_SYNC, &dma_sync); + + if (ret) + LOG(RPI, Error) << "failed to unlock-sync-write dma buf"; + + return ret; +} + +void do32BitConversion(void *mem, unsigned int width, unsigned int height, + unsigned int stride) +{ + /* + * The arm64 version is actually not that much quicker because the + * vast bulk of the time is spent waiting for memory. + */ +#if __aarch64__ + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + uint64_t count = (width + 15) / 16; + uint8_t *dest = ptr + count * 64; + uint8_t *src = ptr + count * 48; + + /* Pre-decrement would have been nice. */ + asm volatile("movi v3.16b, #255 \n" + "1: \n" + "sub %[src], %[src], #48 \n" + "sub %[dest], %[dest], #64 \n" + "subs %[count], %[count], #1 \n" + "ld3 {v0.16b, v1.16b, v2.16b}, [%[src]] \n" + "st4 {v0.16b, v1.16b, v2.16b, v3.16b}, [%[dest]] \n" + "b.gt 1b \n" + : [count]"+r" (count) + : [src]"r" (src), [dest]"r" (dest) + : "cc", "v1", "v2", "v3", "v4", "memory" + ); + } +#else + std::vector<uint8_t> incache(3 * width); + std::vector<uint8_t> outcache(4 * width); + + memcpy(incache.data(), mem, 3 * width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *ptr3 = incache.data(); + uint8_t *ptr4 = outcache.data(); + for (unsigned int i = 0; i < width; i++) { + *(ptr4++) = *(ptr3++); + *(ptr4++) = *(ptr3++); + *(ptr4++) = *(ptr3++); + *(ptr4++) = 255; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 3 * width); + memcpy(ptr, outcache.data(), 4 * width); + } +#endif +} + +void do16BitEndianSwap([[maybe_unused]] void *mem, [[maybe_unused]] unsigned int width, + [[maybe_unused]] unsigned int height, [[maybe_unused]] unsigned int stride) +{ +#if __aarch64__ + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + uint64_t count = (width + 7) / 8; + + asm volatile("1: \n" + "ld1 {v1.16b}, [%[ptr]] \n" + "rev16 v1.16b, v1.16b \n" + "st1 {v1.16b}, [%[ptr]], #16 \n" + "subs %[count], %[count], #1 \n" + "b.gt 1b \n" + : [count]"+r" (count), [ptr]"+r" (ptr) + : + : "cc", "v1", "memory" + ); + } +#endif +} + +void do14bitUnpack(void *mem, unsigned int width, unsigned int height, + unsigned int stride) +{ + std::vector<uint8_t> cache(stride); + + for (unsigned int j = 0; j < height; j++) { + const uint8_t *in = ((uint8_t *)mem) + j * stride; + uint8_t *out = ((uint8_t *)mem) + j * stride; + uint8_t *p = cache.data(); + + std::memcpy(p, in, stride); + for (unsigned int i = 0; i < width; i += 4, p += 7) { + uint16_t p0 = (p[0] << 8) | ((p[4] & 0x3f) << 2); + uint16_t p1 = (p[1] << 8) | ((p[4] & 0xc0) >> 4) | ((p[5] & 0x0f) << 4); + uint16_t p2 = (p[2] << 8) | ((p[5] & 0xf0) >> 2) | ((p[6] & 0x03) << 6); + uint16_t p3 = (p[3] << 8) | (p[6] & 0xfc); + + *(uint16_t *)(out + i * 2 + 0) = p0; + *(uint16_t *)(out + i * 2 + 2) = p1; + *(uint16_t *)(out + i * 2 + 4) = p2; + *(uint16_t *)(out + i * 2 + 6) = p3; + } + } +} + +void downscaleInterleaved3(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride) +{ + std::vector<uint8_t> incache(3 * src_width); + unsigned int dst_width = src_width / 2; + std::vector<uint8_t> outcache(3 * dst_width); + + memcpy(incache.data(), mem, 3 * src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 6, dst += 3) { + dst[0] = ((int)src[0] + (int)src[3] + 1) >> 1; + dst[1] = ((int)src[1] + (int)src[4] + 1) >> 1; + dst[2] = ((int)src[2] + (int)src[5] + 1) >> 1; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 3 * src_width); + memcpy(ptr, outcache.data(), 3 * dst_width); + } +} + +void downscaleInterleaved4(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride) +{ + std::vector<uint8_t> incache(4 * src_width); + unsigned int dst_width = src_width / 2; + std::vector<uint8_t> outcache(4 * dst_width); + + memcpy(incache.data(), mem, 4 * src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) { + dst[0] = ((int)src[0] + (int)src[4] + 1) >> 1; + dst[1] = ((int)src[1] + (int)src[5] + 1) >> 1; + dst[2] = ((int)src[2] + (int)src[6] + 1) >> 1; + dst[3] = ((int)src[3] + (int)src[7] + 1) >> 1; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 4 * src_width); + memcpy(ptr, outcache.data(), 4 * dst_width); + } +} + +void downscalePlaneInternal(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride, std::vector<uint8_t> &incache, + std::vector<uint8_t> &outcache) +{ + unsigned int dst_width = src_width / 2; + memcpy(incache.data(), mem, src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 2, dst++) + *dst = ((int)src[0] + (int)src[1] + 1) >> 1; + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, src_width); + memcpy(ptr, outcache.data(), dst_width); + } +} + +void downscalePlanar420(void *memY, void *memU, void *memV, unsigned int height, + unsigned int src_width, unsigned int stride) +{ + std::vector<uint8_t> incache(src_width); + std::vector<uint8_t> outcache(src_width / 2); + + downscalePlaneInternal(memY, height, src_width, stride, incache, outcache); + downscalePlaneInternal(memU, height / 2, src_width / 2, stride / 2, incache, outcache); + downscalePlaneInternal(memV, height / 2, src_width / 2, stride / 2, incache, outcache); +} + +void downscalePlanar422(void *memY, void *memU, void *memV, + unsigned int height, unsigned int src_width, unsigned int stride) +{ + std::vector<uint8_t> incache(src_width); + std::vector<uint8_t> outcache(src_width / 2); + + downscalePlaneInternal(memY, height, src_width, stride, incache, outcache); + downscalePlaneInternal(memU, height, src_width / 2, stride / 2, incache, outcache); + downscalePlaneInternal(memV, height, src_width / 2, stride / 2, incache, outcache); +} + +void downscaleInterleavedYuyv(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride) +{ + std::vector<uint8_t> incache(2 * src_width); + unsigned int dst_width = src_width / 2; + std::vector<uint8_t> outcache(2 * dst_width); + + memcpy(incache.data(), mem, 2 * src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) { + dst[0] = ((int)src[0] + (int)src[2] + 1) >> 1; + dst[1] = ((int)src[1] + (int)src[5] + 1) >> 1; + dst[2] = ((int)src[4] + (int)src[6] + 1) >> 1; + dst[3] = ((int)src[3] + (int)src[7] + 1) >> 1; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 4 * src_width); + memcpy(ptr, outcache.data(), 2 * dst_width); + } +} + +void downscaleInterleavedUyvy(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride) +{ + std::vector<uint8_t> incache(2 * src_width); + unsigned int dst_width = src_width / 2; + std::vector<uint8_t> outcache(2 * dst_width); + + memcpy(incache.data(), mem, 2 * src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) { + dst[0] = ((int)src[0] + (int)src[4] + 1) >> 1; + dst[1] = ((int)src[1] + (int)src[3] + 1) >> 1; + dst[2] = ((int)src[2] + (int)src[6] + 1) >> 1; + dst[3] = ((int)src[5] + (int)src[7] + 1) >> 1; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 4 * src_width); + memcpy(ptr, outcache.data(), 2 * dst_width); + } +} + +void downscaleInterleaved2Internal(void *mem, unsigned int height, unsigned int src_width, + unsigned int stride, std::vector<uint8_t> &incache, + std::vector<uint8_t> &outcache) +{ + unsigned int dst_width = src_width / 2; + memcpy(incache.data(), mem, 2 * src_width); + for (unsigned int j = 0; j < height; j++) { + uint8_t *ptr = (uint8_t *)mem + j * stride; + + uint8_t *src = incache.data(), *dst = outcache.data(); + for (unsigned int i = 0; i < dst_width; i++, src += 4, dst += 2) { + dst[0] = ((int)src[0] + (int)src[2] + 1) >> 1; + dst[1] = ((int)src[1] + (int)src[3] + 1) >> 1; + } + + if (j < height - 1) + memcpy(incache.data(), ptr + stride, 2 * src_width); + memcpy(ptr, outcache.data(), 2 * dst_width); + } +} + +void downscaleSemiPlanar420(void *memY, void *memUV, unsigned int height, + unsigned int src_width, unsigned int stride) +{ + std::vector<uint8_t> incache(src_width); + std::vector<uint8_t> outcache(src_width / 2); + + downscalePlaneInternal(memY, height, src_width, stride, incache, outcache); + downscaleInterleaved2Internal(memUV, height / 2, src_width / 2, stride, + incache, outcache); +} + +void downscaleStreamBuffer(RPi::Stream *stream, int index) +{ + unsigned int downscale = stream->swDownscale(); + /* Must be a power of 2. */ + ASSERT((downscale & (downscale - 1)) == 0); + + unsigned int stride = stream->configuration().stride; + unsigned int dst_width = stream->configuration().size.width; + unsigned int height = stream->configuration().size.height; + const PixelFormat &pixFormat = stream->configuration().pixelFormat; + const RPi::BufferObject &b = stream->getBuffer(index); + void *mem = b.mapped->planes()[0].data(); + ASSERT(b.mapped); + + /* Do repeated downscale-by-2 in place until we're done. */ + for (; downscale > 1; downscale >>= 1) { + unsigned int src_width = downscale * dst_width; + + if (pixFormat == formats::RGB888 || pixFormat == formats::BGR888) { + downscaleInterleaved3(mem, height, src_width, stride); + } else if (pixFormat == formats::XRGB8888 || pixFormat == formats::XBGR8888) { + /* On some devices these may actually be 24bpp at this point. */ + if (stream->getFlags() & StreamFlag::Needs32bitConv) + downscaleInterleaved3(mem, height, src_width, stride); + else + downscaleInterleaved4(mem, height, src_width, stride); + } else if (pixFormat == formats::YUV420 || pixFormat == formats::YVU420) { + /* These may look like either single or multi-planar buffers. */ + void *mem1; + void *mem2; + if (b.mapped->planes().size() == 3) { + mem1 = b.mapped->planes()[1].data(); + mem2 = b.mapped->planes()[2].data(); + } else { + unsigned int ySize = height * stride; + mem1 = static_cast<uint8_t *>(mem) + ySize; + mem2 = static_cast<uint8_t *>(mem1) + ySize / 4; + } + downscalePlanar420(mem, mem1, mem2, height, src_width, stride); + } else if (pixFormat == formats::YUV422 || pixFormat == formats::YVU422) { + /* These may look like either single or multi-planar buffers. */ + void *mem1; + void *mem2; + if (b.mapped->planes().size() == 3) { + mem1 = b.mapped->planes()[1].data(); + mem2 = b.mapped->planes()[2].data(); + } else { + unsigned int ySize = height * stride; + mem1 = static_cast<uint8_t *>(mem) + ySize; + mem2 = static_cast<uint8_t *>(mem1) + ySize / 2; + } + downscalePlanar422(mem, mem1, mem2, height, src_width, stride); + } else if (pixFormat == formats::YUYV || pixFormat == formats::YVYU) { + downscaleInterleavedYuyv(mem, height, src_width, stride); + } else if (pixFormat == formats::UYVY || pixFormat == formats::VYUY) { + downscaleInterleavedUyvy(mem, height, src_width, stride); + } else if (pixFormat == formats::NV12 || pixFormat == formats::NV21) { + /* These may look like either single or multi-planar buffers. */ + void *mem1; + if (b.mapped->planes().size() == 2) + mem1 = b.mapped->planes()[1].data(); + else + mem1 = static_cast<uint8_t *>(mem) + height * stride; + downscaleSemiPlanar420(mem, mem1, height, src_width, stride); + } else { + LOG(RPI, Error) << "Sw downscale unsupported for " << pixFormat; + ASSERT(0); + } + } +} + +/* Return largest width of any of these streams (or of the camera input). */ +unsigned int getLargestWidth(const V4L2SubdeviceFormat &sensorFormat, + const std::vector<StreamParams> &outStreams) +{ + unsigned int largestWidth = sensorFormat.size.width; + + for (const auto &stream : outStreams) + largestWidth = std::max(largestWidth, stream.cfg->size.width); + + return largestWidth; +} + +/* Return the minimum number of pixels required to write out multiples of 16 bytes. */ +unsigned int getFormatAlignment(const V4L2PixelFormat &fourcc) +{ + const PixelFormatInfo &info = PixelFormatInfo::info(fourcc); + unsigned int formatAlignment = 0; + for (const auto &plane : info.planes) { + if (plane.bytesPerGroup) { + /* How many pixels we need in this plane for a multiple of 16 bytes (??). */ + unsigned int align = 16 * info.pixelsPerGroup / + std::gcd(16u, plane.bytesPerGroup); + formatAlignment = std::max(formatAlignment, align); + } + } + + return formatAlignment; +} + +/* Calculate the amount of software downscale required (which is a power of 2). */ +unsigned int calculateSwDownscale(const V4L2DeviceFormat &format, unsigned int largestWidth, + unsigned int platformMaxDownscale) +{ + unsigned int formatAlignment = getFormatAlignment(format.fourcc); + unsigned int maxDownscale = platformMaxDownscale * 16 / formatAlignment; + unsigned int limitWidth = largestWidth / maxDownscale; + + unsigned int hwWidth = format.size.width; + unsigned int swDownscale = 1; + for (; hwWidth < limitWidth; hwWidth *= 2, swDownscale *= 2); + + return swDownscale; +} + +} /* namespace */ + +using ::libpisp::BackEnd; +using ::libpisp::FrontEnd; + +class PiSPCameraData final : public RPi::CameraData +{ +public: + PiSPCameraData(PipelineHandler *pipe, const libpisp::PiSPVariant &variant) + : RPi::CameraData(pipe), pispVariant_(variant) + { + /* Initialise internal libpisp logging. */ + ::libpisp::logging_init(); + LOG(RPI, Info) << "libpisp version " << ::libpisp::version(); + } + + ~PiSPCameraData() + { + freeBuffers(); + } + + V4L2VideoDevice::Formats ispFormats() const override + { + return isp_[Isp::Output0].dev()->formats(); + } + + V4L2VideoDevice::Formats rawFormats() const override + { + return cfe_[Cfe::Output0].dev()->formats(); + } + + V4L2VideoDevice *frontendDevice() override + { + return cfe_[Cfe::Output0].dev(); + } + + CameraConfiguration::Status + platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override; + + int platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override; + + void platformStart() override; + void platformStop() override; + void platformFreeBuffers() override; + + void cfeBufferDequeue(FrameBuffer *buffer); + void beInputDequeue(FrameBuffer *buffer); + void beOutputDequeue(FrameBuffer *buffer); + + void processStatsComplete(const ipa::RPi::BufferIds &buffers); + void prepareIspComplete(const ipa::RPi::BufferIds &buffers, bool stitchSwapBuffers); + void setCameraTimeout(uint32_t maxFrameLengthMs); + + /* Array of CFE and ISP device streams and associated buffers/streams. */ + RPi::Device<Cfe, 4> cfe_; + RPi::Device<Isp, 8> isp_; + + const libpisp::PiSPVariant &pispVariant_; + + /* Frontend/Backend objects shared with the IPA. */ + SharedMemObject<FrontEnd> fe_; + SharedMemObject<BackEnd> be_; + bool beEnabled_; + + std::unique_ptr<V4L2Subdevice> csi2Subdev_; + std::unique_ptr<V4L2Subdevice> feSubdev_; + + std::vector<FrameBuffer *> tdnBuffers_; + std::vector<FrameBuffer *> stitchBuffers_; + unsigned int tdnInputIndex_; + unsigned int stitchInputIndex_; + + struct Config { + /* + * Number of CFE config and stats buffers to allocate and use. A + * larger number minimises the possibility of dropping frames, + * but increases the latency for updating the HW configuration. + */ + unsigned int numCfeConfigStatsBuffers; + /* + * Number of jobs to queue ahead to the CFE on startup. + * A larger number will increase latency for 3A changes. + */ + unsigned int numCfeConfigQueue; + /* Don't use BE temporal denoise and free some memory resources. */ + bool disableTdn; + /* Don't use BE HDR and free some memory resources. */ + bool disableHdr; + }; + + Config config_; + + bool adjustDeviceFormat(V4L2DeviceFormat &format) const; + +private: + int platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) override; + + int platformConfigureIpa([[maybe_unused]] ipa::RPi::ConfigParams ¶ms) override + { + return 0; + } + + int platformInitIpa(ipa::RPi::InitParams ¶ms) override; + + int configureEntities(V4L2SubdeviceFormat sensorFormat, + V4L2SubdeviceFormat &embeddedFormat); + int configureCfe(); + bool calculateCscConfiguration(const V4L2DeviceFormat &v4l2Format, pisp_be_ccm_config &csc); + int configureBe(const std::optional<ColorSpace> &yuvColorSpace); + + void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) override; + + void prepareCfe(); + void prepareBe(uint32_t bufferId, bool stitchSwapBuffers); + + void tryRunPipeline() override; + + struct CfeJob { + ControlList sensorControls; + unsigned int delayContext; + std::unordered_map<const RPi::Stream *, FrameBuffer *> buffers; + }; + + std::queue<CfeJob> cfeJobQueue_; + + bool cfeJobComplete() const + { + if (cfeJobQueue_.empty()) + return false; + + const CfeJob &job = cfeJobQueue_.back(); + return job.buffers.count(&cfe_[Cfe::Output0]) && + job.buffers.count(&cfe_[Cfe::Stats]) && + (!sensorMetadata_ || + job.buffers.count(&cfe_[Cfe::Embedded])); + } + + std::string last_dump_file_; +}; + +class PipelineHandlerPiSP : public RPi::PipelineHandlerBase +{ +public: + PipelineHandlerPiSP(CameraManager *manager) + : RPi::PipelineHandlerBase(manager) + { + } + + ~PipelineHandlerPiSP() + { + } + + bool match(DeviceEnumerator *enumerator) override; + +private: + PiSPCameraData *cameraData(Camera *camera) + { + return static_cast<PiSPCameraData *>(camera->_d()); + } + + int prepareBuffers(Camera *camera) override; + int platformRegister(std::unique_ptr<RPi::CameraData> &cameraData, + MediaDevice *cfe, MediaDevice *isp) override; +}; + +bool PipelineHandlerPiSP::match(DeviceEnumerator *enumerator) +{ + constexpr unsigned int numCfeDevices = 2; + + /* + * Loop over all CFE instances, but return out once a match is found. + * This is to ensure we correctly enumerate the camera when an instance + * of the CFE has registered with media controller, but has not registered + * device nodes due to a sensor subdevice failure. + */ + for (unsigned int i = 0; i < numCfeDevices; i++) { + DeviceMatch cfe("rp1-cfe"); + cfe.add("rp1-cfe-fe-image0"); + cfe.add("rp1-cfe-fe-stats"); + cfe.add("rp1-cfe-fe-config"); + MediaDevice *cfeDevice = acquireMediaDevice(enumerator, cfe); + + if (!cfeDevice) { + LOG(RPI, Debug) << "Unable to acquire a CFE instance"; + break; + } + + DeviceMatch isp("pispbe"); + isp.add("pispbe-input"); + isp.add("pispbe-config"); + isp.add("pispbe-output0"); + isp.add("pispbe-output1"); + isp.add("pispbe-tdn_output"); + isp.add("pispbe-tdn_input"); + isp.add("pispbe-stitch_output"); + isp.add("pispbe-stitch_input"); + MediaDevice *ispDevice = acquireMediaDevice(enumerator, isp); + + if (!ispDevice) { + LOG(RPI, Debug) << "Unable to acquire ISP instance"; + break; + } + + /* + * The loop below is used to register multiple cameras behind + * one or more video mux devices that are attached to a + * particular CFE instance. Obviously these cameras cannot be + * used simultaneously. + */ + unsigned int numCameras = 0; + for (MediaEntity *entity : cfeDevice->entities()) { + if (entity->function() != MEDIA_ENT_F_CAM_SENSOR) + continue; + + const libpisp::PiSPVariant &variant = + libpisp::get_variant(cfeDevice->hwRevision(), + ispDevice->hwRevision()); + if (!variant.NumFrontEnds() || !variant.NumBackEnds()) { + LOG(RPI, Error) << "Unsupported PiSP variant"; + break; + } + + std::unique_ptr<RPi::CameraData> cameraData = + std::make_unique<PiSPCameraData>(this, variant); + PiSPCameraData *pisp = + static_cast<PiSPCameraData *>(cameraData.get()); + + pisp->fe_ = SharedMemObject<FrontEnd> + ("pisp_frontend", true, pisp->pispVariant_); + pisp->be_ = SharedMemObject<BackEnd> + ("pisp_backend", BackEnd::Config({}), pisp->pispVariant_); + + if (!pisp->fe_.fd().isValid() || !pisp->be_.fd().isValid()) { + LOG(RPI, Error) << "Failed to create ISP shared objects"; + break; + } + + int ret = registerCamera(cameraData, cfeDevice, "csi2", + ispDevice, entity); + if (ret) + LOG(RPI, Error) << "Failed to register camera " + << entity->name() << ": " << ret; + else + numCameras++; + } + + if (numCameras) + return true; + } + + return false; +} + +int PipelineHandlerPiSP::prepareBuffers(Camera *camera) +{ + PiSPCameraData *data = cameraData(camera); + unsigned int numRawBuffers = 0; + int ret; + + for (Stream *s : camera->streams()) { + if (PipelineHandlerBase::isRaw(s->configuration().pixelFormat)) { + numRawBuffers = s->configuration().bufferCount; + break; + } + } + + /* Decide how many internal buffers to allocate. */ + for (auto const stream : data->streams_) { + unsigned int numBuffers; + /* + * For CFE, allocate a minimum of 4 buffers as we want + * to avoid any frame drops. + */ + constexpr unsigned int minBuffers = 4; + if (stream == &data->cfe_[Cfe::Output0]) { + /* + * If an application has configured a RAW stream, allocate + * additional buffers to make up the minimum, but ensure + * we have at least 2 sets of internal buffers to use to + * minimise frame drops. + */ + numBuffers = std::max<int>(2, minBuffers - numRawBuffers); + } else if (stream == &data->isp_[Isp::Input]) { + /* + * ISP input buffers are imported from the CFE, so follow + * similar logic as above to count all the RAW buffers + * available. + */ + numBuffers = numRawBuffers + + std::max<int>(2, minBuffers - numRawBuffers); + } else if (stream == &data->cfe_[Cfe::Embedded]) { + /* + * Embedded data buffers are (currently) for internal use, + * so allocate a reasonably large amount. + */ + numBuffers = 12; + } else if (stream == &data->cfe_[Cfe::Stats] || + stream == &data->cfe_[Cfe::Config]) { + numBuffers = data->config_.numCfeConfigStatsBuffers; + } else if (!data->beEnabled_) { + /* Backend not enabled, we don't need to allocate buffers. */ + numBuffers = 0; + } else if (stream == &data->isp_[Isp::TdnOutput] && data->config_.disableTdn) { + /* TDN is explicitly disabled. */ + continue; + } else if (stream == &data->isp_[Isp::StitchOutput] && data->config_.disableHdr) { + /* Stitch/HDR is explicitly disabled. */ + continue; + } else { + /* Allocate 2 sets of all other Backend buffers */ + numBuffers = 2; + } + + LOG(RPI, Debug) << "Preparing " << numBuffers + << " buffers for stream " << stream->name(); + + ret = stream->prepareBuffers(numBuffers); + if (ret < 0) + return ret; + } + + /* + * Store the Framebuffer pointers for convenience as we will ping-pong + * these buffers between the input and output nodes for TDN and Stitch. + * + * The buffer size needs to be setup here as well. Conveniently this is + * the same for both TDN and stitch. + */ + pisp_image_format_config tdn; + data->be_->GetTdnOutputFormat(tdn); + unsigned int size = tdn.stride * tdn.height; + for (auto const &buffer : data->isp_[Isp::TdnOutput].getBuffers()) { + FrameBuffer *b = buffer.second.buffer; + b->_d()->metadata().planes()[0].bytesused = size; + data->tdnBuffers_.push_back(b); + } + for (auto const &buffer : data->isp_[Isp::StitchOutput].getBuffers()) { + FrameBuffer *b = buffer.second.buffer; + b->_d()->metadata().planes()[0].bytesused = size; + data->stitchBuffers_.push_back(b); + } + + /* Size up the config buffers as well. */ + for (auto &b : data->isp_[Isp::Config].getBuffers()) { + FrameMetadata::Plane &plane = b.second.buffer->_d()->metadata().planes()[0]; + plane.bytesused = sizeof(pisp_be_tiles_config); + } + + /* + * Pass the stats and embedded data buffers to the IPA. No other + * buffers need to be passed. + */ + mapBuffers(camera, data->cfe_[Cfe::Stats].getBuffers(), RPi::MaskStats); + if (data->sensorMetadata_) + mapBuffers(camera, data->cfe_[Cfe::Embedded].getBuffers(), + RPi::MaskEmbeddedData); + + return 0; +} + +int PipelineHandlerPiSP::platformRegister(std::unique_ptr<RPi::CameraData> &cameraData, + MediaDevice *cfe, MediaDevice *isp) +{ + PiSPCameraData *data = static_cast<PiSPCameraData *>(cameraData.get()); + int ret; + + MediaEntity *cfeImage = cfe->getEntityByName("rp1-cfe-fe-image0"); + MediaEntity *cfeEmbedded = cfe->getEntityByName("rp1-cfe-csi2-ch1"); + MediaEntity *cfeStats = cfe->getEntityByName("rp1-cfe-fe-stats"); + MediaEntity *cfeConfig = cfe->getEntityByName("rp1-cfe-fe-config"); + MediaEntity *ispInput = isp->getEntityByName("pispbe-input"); + MediaEntity *IpaPrepare = isp->getEntityByName("pispbe-config"); + MediaEntity *ispOutput0 = isp->getEntityByName("pispbe-output0"); + MediaEntity *ispOutput1 = isp->getEntityByName("pispbe-output1"); + MediaEntity *ispTdnOutput = isp->getEntityByName("pispbe-tdn_output"); + MediaEntity *ispTdnInput = isp->getEntityByName("pispbe-tdn_input"); + MediaEntity *ispStitchOutput = isp->getEntityByName("pispbe-stitch_output"); + MediaEntity *ispStitchInput = isp->getEntityByName("pispbe-stitch_input"); + + /* Locate and open the cfe video streams. */ + data->cfe_[Cfe::Output0] = RPi::Stream("CFE Image", cfeImage, StreamFlag::RequiresMmap); + data->cfe_[Cfe::Embedded] = RPi::Stream("CFE Embedded", cfeEmbedded); + data->cfe_[Cfe::Stats] = RPi::Stream("CFE Stats", cfeStats); + data->cfe_[Cfe::Config] = RPi::Stream("CFE Config", cfeConfig, + StreamFlag::Recurrent | StreamFlag::RequiresMmap); + + /* Tag the ISP input stream as an import stream. */ + data->isp_[Isp::Input] = + RPi::Stream("ISP Input", ispInput, StreamFlag::ImportOnly); + data->isp_[Isp::Config] = + RPi::Stream("ISP Config", IpaPrepare, StreamFlag::Recurrent | + StreamFlag::RequiresMmap); + data->isp_[Isp::Output0] = + RPi::Stream("ISP Output0", ispOutput0, StreamFlag::RequiresMmap); + data->isp_[Isp::Output1] = + RPi::Stream("ISP Output1", ispOutput1, StreamFlag::RequiresMmap); + data->isp_[Isp::TdnOutput] = + RPi::Stream("ISP TDN Output", ispTdnOutput, StreamFlag::Recurrent); + data->isp_[Isp::TdnInput] = + RPi::Stream("ISP TDN Input", ispTdnInput, StreamFlag::ImportOnly | + StreamFlag::Recurrent); + data->isp_[Isp::StitchOutput] = + RPi::Stream("ISP Stitch Output", ispStitchOutput, StreamFlag::Recurrent); + data->isp_[Isp::StitchInput] = + RPi::Stream("ISP Stitch Input", ispStitchInput, StreamFlag::ImportOnly | + StreamFlag::Recurrent); + + /* Wire up all the buffer connections. */ + data->cfe_[Cfe::Output0].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue); + data->cfe_[Cfe::Stats].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue); + data->cfe_[Cfe::Config].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue); + data->isp_[Isp::Input].dev()->bufferReady.connect(data, &PiSPCameraData::beInputDequeue); + data->isp_[Isp::Config].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue); + data->isp_[Isp::Output0].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue); + data->isp_[Isp::Output1].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue); + data->cfe_[Cfe::Embedded].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue); + + data->csi2Subdev_ = std::make_unique<V4L2Subdevice>(cfe->getEntityByName("csi2")); + data->feSubdev_ = std::make_unique<V4L2Subdevice>(cfe->getEntityByName("pisp-fe")); + data->csi2Subdev_->open(); + data->feSubdev_->open(); + + /* + * Open all CFE and ISP streams. The exception is the embedded data + * stream, which only gets opened below if the IPA reports that the sensor + * supports embedded data. + * + * The below grouping is just for convenience so that we can easily + * iterate over all streams in one go. + */ + data->streams_.push_back(&data->cfe_[Cfe::Output0]); + data->streams_.push_back(&data->cfe_[Cfe::Config]); + data->streams_.push_back(&data->cfe_[Cfe::Stats]); + if (data->sensorMetadata_) + data->streams_.push_back(&data->cfe_[Cfe::Embedded]); + + data->streams_.push_back(&data->isp_[Isp::Input]); + data->streams_.push_back(&data->isp_[Isp::Output0]); + data->streams_.push_back(&data->isp_[Isp::Output1]); + data->streams_.push_back(&data->isp_[Isp::Config]); + data->streams_.push_back(&data->isp_[Isp::TdnInput]); + data->streams_.push_back(&data->isp_[Isp::TdnOutput]); + data->streams_.push_back(&data->isp_[Isp::StitchInput]); + data->streams_.push_back(&data->isp_[Isp::StitchOutput]); + + for (auto stream : data->streams_) { + ret = stream->dev()->open(); + if (ret) + return ret; + } + + /* Write up all the IPA connections. */ + data->ipa_->prepareIspComplete.connect(data, &PiSPCameraData::prepareIspComplete); + data->ipa_->processStatsComplete.connect(data, &PiSPCameraData::processStatsComplete); + data->ipa_->setCameraTimeout.connect(data, &PiSPCameraData::setCameraTimeout); + + /* + * List the available streams an application may request. At present, we + * do not advertise CFE Embedded and ISP Statistics streams, as there + * is no mechanism for the application to request non-image buffer formats. + */ + std::set<Stream *> streams; + streams.insert(&data->cfe_[Cfe::Output0]); + streams.insert(&data->isp_[Isp::Output0]); + streams.insert(&data->isp_[Isp::Output1]); + + /* Create and register the camera. */ + const std::string &id = data->sensor_->id(); + std::shared_ptr<Camera> camera = + Camera::create(std::move(cameraData), id, streams); + PipelineHandler::registerCamera(std::move(camera)); + + LOG(RPI, Info) << "Registered camera " << id + << " to CFE device " << cfe->deviceNode() + << " and ISP device " << isp->deviceNode() + << " using PiSP variant " << data->pispVariant_.Name(); + + return 0; +} + +CameraConfiguration::Status +PiSPCameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const +{ + std::vector<StreamParams> &rawStreams = rpiConfig->rawStreams_; + std::vector<StreamParams> &outStreams = rpiConfig->outStreams_; + + CameraConfiguration::Status status = CameraConfiguration::Status::Valid; + + /* Can only output 1 RAW stream and/or 2 YUV/RGB streams for now. */ + if (rawStreams.size() > 1 || outStreams.size() > 2) { + LOG(RPI, Error) << "Invalid number of streams requested"; + return CameraConfiguration::Status::Invalid; + } + + if (!rawStreams.empty()) { + rawStreams[0].dev = cfe_[Cfe::Output0].dev(); + + StreamConfiguration *rawStream = rawStreams[0].cfg; + BayerFormat bayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat); + /* + * We cannot output CSI2 packed or non 16-bit output from the frontend, + * so signal the output as unpacked 16-bits in these cases. + */ + if (bayer.packing == BayerFormat::Packing::CSI2 || bayer.bitDepth != 16) { + bayer.packing = (bayer.packing == BayerFormat::Packing::CSI2) ? + BayerFormat::Packing::PISP1 : BayerFormat::Packing::None; + bayer.bitDepth = 16; + } + + /* The RAW stream size cannot exceed the sensor frame output - for now. */ + if (rawStream->size != rpiConfig->sensorFormat_.size || + rawStream->pixelFormat != bayer.toPixelFormat()) { + rawStream->size = rpiConfig->sensorFormat_.size; + rawStream->pixelFormat = bayer.toPixelFormat(); + status = CameraConfiguration::Adjusted; + } + + rawStreams[0].format = + RPi::PipelineHandlerBase::toV4L2DeviceFormat(cfe_[Cfe::Output0].dev(), rawStream); + + computeOptimalStride(rawStreams[0].format); + } + + /* + * For the two ISP outputs, the lower resolution must be routed from + * Output 1 + * + * Index 0 contains the largest requested resolution. + */ + unsigned int largestWidth = getLargestWidth(rpiConfig->sensorFormat_, + rpiConfig->outStreams_); + + for (unsigned int i = 0; i < outStreams.size(); i++) { + StreamConfiguration *cfg = outStreams[i].cfg; + + /* + * Output 1 must be for the smallest resolution. We will + * have that fixed up in the code above. + */ + auto ispOutput = i == 1 || outStreams.size() == 1 ? Isp::Output1 + : Isp::Output0; + outStreams[i].dev = isp_[ispOutput].dev(); + + /* + * Don't let The output streams downscale by more than 64x when + * a downscaler block is available, or 16x when there's only the + * resampler. + */ + Size rawSize = rpiConfig->sensorFormat_.size.boundedToAspectRatio(cfg->size); + unsigned int outputIndex = ispOutput == Isp::Output0 ? 0 : 1; + Size minSize; + if (pispVariant_.BackEndDownscalerAvailable(0, outputIndex)) { + /* + * Downscaler available. Allow up to 64x downscale. If not a multiple of + * 64, round up to the next integer, but also ensure the result is even. + */ + const unsigned int downscale = 64; + minSize.width = (rawSize.width + downscale - 1) / downscale; + minSize.width = (minSize.width + 1) & ~1; /* ensure even */ + minSize.height = (rawSize.height + downscale - 1) / downscale; + minSize.height = (minSize.height + 1) & ~1; /* ensure even */ + } else { + /* No downscale. Resampler requires: (output_dim - 1) * 16 <= input_dim - 1 */ + const unsigned int downscale = 16; + minSize.width = (rawSize.width - 1 + downscale - 1) / downscale + 1; + minSize.width = (minSize.width + 1) & ~1; /* ensure even */ + minSize.height = (rawSize.height - 1 + downscale - 1) / downscale + 1; + minSize.height = (minSize.height + 1) & ~1; /* ensure even */ + } + LOG(RPI, Debug) << "minSize: width " << minSize.width << " height " << minSize.height; + + /* Bound the output size to minSize, preserve aspect ratio, and ensure even numbers. */ + if (cfg->size.width < minSize.width) { + cfg->size.height = (cfg->size.height * minSize.width / cfg->size.width + 1) & ~1; + cfg->size.width = minSize.width; + status = CameraConfiguration::Status::Adjusted; + } + + if (cfg->size.height < minSize.height) { + cfg->size.width = (cfg->size.width * minSize.height / cfg->size.height + 1) & ~1; + cfg->size.height = minSize.height; + status = CameraConfiguration::Status::Adjusted; + } + + /* Make sure output1 is no larger than output 0. */ + Size size = cfg->size.boundedTo(outStreams[0].cfg->size); + + /* \todo Warn if upscaling: reduces image quality. */ + + if (cfg->size != size) { + cfg->size = size; + status = CameraConfiguration::Status::Adjusted; + } + + outStreams[i].format = + RPi::PipelineHandlerBase::toV4L2DeviceFormat(outStreams[i].dev, outStreams[i].cfg); + + /* Compute the optimal stride for the BE output buffers. */ + computeOptimalStride(outStreams[i].format); + + /* + * We need to check for software downscaling. This must happen + * after adjusting the device format so that we can choose the + * largest stride - which might have been the original + * unadjusted format, or the adjusted one (if software + * downscaling means it's larger). + */ + V4L2DeviceFormat adjustedFormat = outStreams[i].format; + adjustDeviceFormat(adjustedFormat); + + unsigned int swDownscale = + calculateSwDownscale(adjustedFormat, largestWidth, + be_->GetMaxDownscale()); + LOG(RPI, Debug) << "For stream " << adjustedFormat + << " swDownscale is " << swDownscale; + if (swDownscale > 1) { + adjustedFormat.size.width *= swDownscale; + computeOptimalStride(adjustedFormat); + for (unsigned int p = 0; p < outStreams[i].format.planesCount; p++) + outStreams[i].format.planes[p].bpl = + std::max(outStreams[i].format.planes[p].bpl, adjustedFormat.planes[p].bpl); + } + } + + return status; +} + +int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) +{ + config_ = { + .numCfeConfigStatsBuffers = 12, + .numCfeConfigQueue = 2, + .disableTdn = false, + .disableHdr = false, + }; + + if (!root) + return 0; + + std::optional<double> ver = (*root)["version"].get<double>(); + if (!ver || *ver != 1.0) { + LOG(RPI, Error) << "Unexpected configuration file version reported"; + return -EINVAL; + } + + std::optional<std::string> target = (*root)["target"].get<std::string>(); + if (target != "pisp") { + LOG(RPI, Error) << "Unexpected target reported: expected \"pisp\", got " + << (target ? target->c_str() : "(unknown)"); + return -EINVAL; + } + + const YamlObject &phConfig = (*root)["pipeline_handler"]; + config_.numCfeConfigStatsBuffers = + phConfig["num_cfe_config_stats_buffers"].get<unsigned int>(config_.numCfeConfigStatsBuffers); + config_.numCfeConfigQueue = + phConfig["num_cfe_config_queue"].get<unsigned int>(config_.numCfeConfigQueue); + config_.disableTdn = phConfig["disable_tdn"].get<bool>(config_.disableTdn); + config_.disableHdr = phConfig["disable_hdr"].get<bool>(config_.disableHdr); + + if (config_.disableTdn) { + LOG(RPI, Info) << "TDN disabled by user config"; + streams_.erase(std::remove_if(streams_.begin(), streams_.end(), + [this] (const RPi::Stream *s) { return s == &isp_[Isp::TdnInput] || + s == &isp_[Isp::TdnInput]; }), + streams_.end()); + } + + if (config_.disableHdr) { + LOG(RPI, Info) << "HDR disabled by user config"; + streams_.erase(std::remove_if(streams_.begin(), streams_.end(), + [this] (const RPi::Stream *s) { return s == &isp_[Isp::StitchInput] || + s == &isp_[Isp::StitchOutput]; }), + streams_.end()); + } + + if (config_.numCfeConfigStatsBuffers < 1) { + LOG(RPI, Error) + << "Invalid configuration: num_cfe_config_stats_buffers must be >= 1"; + return -EINVAL; + } + + if (config_.numCfeConfigQueue < 1) { + LOG(RPI, Error) + << "Invalid configuration: numCfeConfigQueue must be >= 1"; + return -EINVAL; + } + + return 0; +} + +std::unordered_map<uint32_t, uint32_t> deviceAdjustTable = { + { V4L2_PIX_FMT_RGBX32, V4L2_PIX_FMT_RGB24 }, + { V4L2_PIX_FMT_XBGR32, V4L2_PIX_FMT_BGR24 } +}; + +bool PiSPCameraData::adjustDeviceFormat(V4L2DeviceFormat &format) const +{ + auto it = deviceAdjustTable.find(format.fourcc.fourcc()); + + if (pispVariant_.BackendRGB32Supported(0)) + return false; + + if (it != deviceAdjustTable.end()) { + LOG(RPI, Debug) << "Swapping 32-bit for 24-bit format"; + format.fourcc = V4L2PixelFormat(it->second); + return true; + } + + return false; +} + +int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) +{ + const std::vector<RPi::RPiCameraConfiguration::StreamParams> &rawStreams = rpiConfig->rawStreams_; + const std::vector<RPi::RPiCameraConfiguration::StreamParams> &outStreams = rpiConfig->outStreams_; + int ret; + V4L2VideoDevice *cfe = cfe_[Cfe::Output0].dev(); + V4L2DeviceFormat cfeFormat; + + /* + * See which streams are requested, and route the user + * StreamConfiguration appropriately. + */ + if (rawStreams.empty()) { + /* + * The CFE Frontend output will always be 16-bits unpacked, so adjust the + * mbus code right at the start. + */ + V4L2SubdeviceFormat sensorFormatMod = rpiConfig->sensorFormat_; + sensorFormatMod.code = mbusCodeUnpacked16(sensorFormatMod.code); + cfeFormat = RPi::PipelineHandlerBase::toV4L2DeviceFormat(cfe, + sensorFormatMod, + BayerFormat::Packing::PISP1); + computeOptimalStride(cfeFormat); + } else { + rawStreams[0].cfg->setStream(&cfe_[Cfe::Output0]); + cfe_[Cfe::Output0].setFlags(StreamFlag::External); + cfeFormat = rawStreams[0].format; + } + + /* + * If the sensor output is 16-bits, we must endian swap the buffer + * contents to account for the HW missing this feature. + */ + cfe_[Cfe::Output0].clearFlags(StreamFlag::Needs16bitEndianSwap); + if (MediaBusFormatInfo::info(rpiConfig->sensorFormat_.code).bitsPerPixel == 16) { + cfe_[Cfe::Output0].setFlags(StreamFlag::Needs16bitEndianSwap); + LOG(RPI, Warning) + << "The sensor is configured for a 16-bit output, statistics" + << " will not be correct. You must use manual camera settings."; + } + + /* Ditto for the 14-bit unpacking. */ + cfe_[Cfe::Output0].clearFlags(StreamFlag::Needs14bitUnpack); + if (MediaBusFormatInfo::info(rpiConfig->sensorFormat_.code).bitsPerPixel == 14) { + cfe_[Cfe::Output0].setFlags(StreamFlag::Needs14bitUnpack); + LOG(RPI, Warning) + << "The sensor is configured for a 14-bit output, statistics" + << " will not be correct. You must use manual camera settings."; + } + + ret = cfe->setFormat(&cfeFormat); + if (ret) + return ret; + + /* Set the TDN and Stitch node formats in case they are turned on. */ + isp_[Isp::TdnOutput].dev()->setFormat(&cfeFormat); + isp_[Isp::TdnInput].dev()->setFormat(&cfeFormat); + isp_[Isp::StitchOutput].dev()->setFormat(&cfeFormat); + isp_[Isp::StitchInput].dev()->setFormat(&cfeFormat); + + ret = isp_[Isp::Input].dev()->setFormat(&cfeFormat); + if (ret) + return ret; + + LOG(RPI, Info) << "Sensor: " << sensor_->id() + << " - Selected sensor format: " << rpiConfig->sensorFormat_ + << " - Selected CFE format: " << cfeFormat; + + /* + * Find the largest width of any stream; we'll use it later to check for + * excessive downscaling. + */ + unsigned int largestWidth = getLargestWidth(rpiConfig->sensorFormat_, outStreams); + + unsigned int beEnables = 0; + V4L2DeviceFormat format; + + /* + * First thing is to remove Isp::Output0 and Isp::Output1 from streams_ + * as they may be unused depending on the configuration. Add them back + * only if needed. + */ + streams_.erase(std::remove_if(streams_.begin(), streams_.end(), + [this] (const RPi::Stream *s) { return s == &isp_[Isp::Output0] || + s == &isp_[Isp::Output1]; }), + streams_.end()); + + cropParams_.clear(); + for (unsigned int i = 0; i < outStreams.size(); i++) { + StreamConfiguration *cfg = outStreams[i].cfg; + unsigned int ispIndex; + + /* + * Output 1 must be for the smallest resolution. We will + * have that fixed up in the code above. + */ + RPi::Stream *stream; + if (i == 1 || outStreams.size() == 1) { + stream = &isp_[Isp::Output1]; + beEnables |= PISP_BE_RGB_ENABLE_OUTPUT1; + ispIndex = 1; + } else { + stream = &isp_[Isp::Output0]; + beEnables |= PISP_BE_RGB_ENABLE_OUTPUT0; + ispIndex = 0; + } + + format = outStreams[i].format; + bool needs32BitConversion = adjustDeviceFormat(format); + + /* + * This pixel format may not be the same as the configured + * pixel format if adjustDeviceFormat() above has reqused a change. + */ + PixelFormat pixFmt = format.fourcc.toPixelFormat(); + + /* If there's excessive downscaling we'll do some of it in software. */ + unsigned int swDownscale = calculateSwDownscale(format, largestWidth, + be_->GetMaxDownscale()); + unsigned int hwWidth = format.size.width * swDownscale; + format.size.width = hwWidth; + + LOG(RPI, Debug) << "Setting " << stream->name() << " to " + << format << " (sw downscale " << swDownscale << ")"; + + ret = stream->dev()->setFormat(&format); + if (ret) + return -EINVAL; + LOG(RPI, Debug) << "After setFormat, stride " << format.planes[0].bpl; + + if (format.size.height != cfg->size.height || + format.size.width != hwWidth || format.fourcc.toPixelFormat() != pixFmt) { + LOG(RPI, Error) + << "Failed to set requested format on " << stream->name() + << ", returned " << format; + return -EINVAL; + } + + LOG(RPI, Debug) + << "Stream " << stream->name() << " has color space " + << ColorSpace::toString(cfg->colorSpace); + + libcamera::RPi::Stream::StreamFlags flags = StreamFlag::External; + + stream->clearFlags(StreamFlag::Needs32bitConv); + if (needs32BitConversion) + flags |= StreamFlag::Needs32bitConv; + + /* Set smallest selection the ISP will allow. */ + Size minCrop{ 32, 32 }; + + /* Adjust aspect ratio by providing crops on the input image. */ + Size size = cfeFormat.size.boundedToAspectRatio(outStreams[i].cfg->size); + Rectangle ispCrop = size.centeredTo(Rectangle(cfeFormat.size).center()); + + /* + * Calculate the minimum crop. The rule is that (output_dim - 1) / (input_dim - 1) + * must be strictly < 16. We add 2 after dividing because +1 + * comes from the division that rounds down, and +1 because we + * had (input_dim - 1). + */ + Size scalingMinSize = outStreams[i].cfg->size.shrunkBy({ 1, 1 }) / 16; + scalingMinSize.growBy({ 2, 2 }); + minCrop.expandTo(scalingMinSize); + + platformSetIspCrop(ispIndex, ispCrop); + /* + * Set the scaler crop to the value we are using (scaled to native sensor + * coordinates). + */ + cropParams_.emplace(std::piecewise_construct, + std::forward_as_tuple(outStreams[i].index), + std::forward_as_tuple(ispCrop, minCrop, ispIndex)); + + cfg->setStream(stream); + stream->setFlags(flags); + stream->setSwDownscale(swDownscale); + streams_.push_back(stream); + } + + pisp_be_global_config global; + be_->GetGlobal(global); + global.rgb_enables &= ~(PISP_BE_RGB_ENABLE_OUTPUT0 + PISP_BE_RGB_ENABLE_OUTPUT1); + global.rgb_enables |= beEnables; + be_->SetGlobal(global); + + beEnabled_ = beEnables & (PISP_BE_RGB_ENABLE_OUTPUT0 | PISP_BE_RGB_ENABLE_OUTPUT1); + + /* CFE statistics output format. */ + format = {}; + format.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPI_FE_STATS); + ret = cfe_[Cfe::Stats].dev()->setFormat(&format); + if (ret) { + LOG(RPI, Error) << "Failed to set format on CFE stats stream: " + << format; + return ret; + } + + /* CFE config format. */ + format = {}; + format.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPI_FE_CFG); + ret = cfe_[Cfe::Config].dev()->setFormat(&format); + if (ret) { + LOG(RPI, Error) << "Failed to set format on CFE config stream: " + << format; + return ret; + } + + /* + * Configure the CFE embedded data output format only if the sensor + * supports it. + */ + V4L2SubdeviceFormat embeddedFormat = sensor_->embeddedDataFormat(); + if (sensorMetadata_) { + static const std::map<uint32_t, V4L2PixelFormat> metaFormats{ + { MEDIA_BUS_FMT_META_8, V4L2PixelFormat(V4L2_META_FMT_GENERIC_8) }, + { MEDIA_BUS_FMT_META_10, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_10) }, + { MEDIA_BUS_FMT_META_12, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_12) }, + { MEDIA_BUS_FMT_META_14, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_14) }, + }; + + const auto metaFormat = metaFormats.find(embeddedFormat.code); + if (metaFormat == metaFormats.end()) { + LOG(RPI, Error) + << "Unsupported metadata format " + << utils::hex(embeddedFormat.code, 4); + return -EINVAL; + } + + format = {}; + format.fourcc = metaFormat->second; + format.size = embeddedFormat.size; + + LOG(RPI, Debug) << "Setting embedded data format " << format; + ret = cfe_[Cfe::Embedded].dev()->setFormat(&format); + if (ret) { + LOG(RPI, Error) << "Failed to set format on CFE embedded: " + << format; + return ret; + } + } + + configureEntities(rpiConfig->sensorFormat_, embeddedFormat); + configureCfe(); + + if (beEnabled_) + configureBe(rpiConfig->yuvColorSpace_); + + return 0; +} + +void PiSPCameraData::platformStart() +{ + /* + * We don't need to worry about dequeue events for the TDN and Stitch + * nodes as the buffers are simply ping-ponged every frame. But we do + * want to track the currently used input index. + */ + tdnInputIndex_ = 0; + stitchInputIndex_ = 0; + + cfeJobQueue_ = {}; + + for (unsigned int i = 0; i < config_.numCfeConfigQueue; i++) + prepareCfe(); + + /* Clear the debug dump file history. */ + last_dump_file_.clear(); +} + +void PiSPCameraData::platformStop() +{ + cfeJobQueue_ = {}; +} + +void PiSPCameraData::platformFreeBuffers() +{ + tdnBuffers_.clear(); + stitchBuffers_.clear(); +} + +void PiSPCameraData::cfeBufferDequeue(FrameBuffer *buffer) +{ + RPi::Stream *stream = nullptr; + int index = 0; + + if (!isRunning()) + return; + + for (RPi::Stream &s : cfe_) { + index = s.getBufferId(buffer); + if (index) { + stream = &s; + break; + } + } + + /* If the last CFE job has completed, we need a new job entry in the queue. */ + if (cfeJobQueue_.empty() || cfeJobComplete()) + cfeJobQueue_.push({}); + + CfeJob &job = cfeJobQueue_.back(); + + /* The buffer must belong to one of our streams. */ + ASSERT(stream); + + LOG(RPI, Debug) << "Stream " << stream->name() << " buffer dequeue" + << ", buffer id " << index + << ", timestamp: " << buffer->metadata().timestamp; + + job.buffers[stream] = buffer; + + if (stream == &cfe_[Cfe::Output0]) { + /* Do an endian swap or 14-bit unpacking if needed. */ + if (stream->getFlags() & StreamFlag::Needs16bitEndianSwap || + stream->getFlags() & StreamFlag::Needs14bitUnpack) { + const unsigned int stride = stream->configuration().stride; + const unsigned int width = stream->configuration().size.width; + const unsigned int height = stream->configuration().size.height; + const RPi::BufferObject &b = stream->getBuffer(index); + + ASSERT(b.mapped); + void *mem = b.mapped->planes()[0].data(); + + dmabufSyncStart(buffer->planes()[0].fd); + if (stream->getFlags() & StreamFlag::Needs16bitEndianSwap) + do16BitEndianSwap(mem, width, height, stride); + else + do14bitUnpack(mem, width, height, stride); + dmabufSyncEnd(buffer->planes()[0].fd); + } + + /* + * Lookup the sensor controls used for this frame sequence from + * DelayedControl and queue them along with the frame buffer. + */ + auto [ctrl, delayContext] = delayedCtrls_->get(buffer->metadata().sequence); + /* + * Add the frame timestamp to the ControlList for the IPA to use + * as it does not receive the FrameBuffer object. + */ + ctrl.set(controls::SensorTimestamp, buffer->metadata().timestamp); + job.sensorControls = std::move(ctrl); + job.delayContext = delayContext; + } else if (stream == &cfe_[Cfe::Config]) { + /* The config buffer can be re-queued back straight away. */ + handleStreamBuffer(buffer, &cfe_[Cfe::Config]); + prepareCfe(); + } + + handleState(); +} + +void PiSPCameraData::beInputDequeue(FrameBuffer *buffer) +{ + if (!isRunning()) + return; + + LOG(RPI, Debug) << "Stream ISP Input buffer complete" + << ", buffer id " << cfe_[Cfe::Output0].getBufferId(buffer) + << ", timestamp: " << buffer->metadata().timestamp; + + /* The ISP input buffer gets re-queued into CFE. */ + handleStreamBuffer(buffer, &cfe_[Cfe::Output0]); + handleState(); +} + +void PiSPCameraData::beOutputDequeue(FrameBuffer *buffer) +{ + RPi::Stream *stream = nullptr; + int index; + + if (!isRunning()) + return; + + for (RPi::Stream &s : isp_) { + index = s.getBufferId(buffer); + if (index) { + stream = &s; + break; + } + } + + /* The buffer must belong to one of our ISP output streams. */ + ASSERT(stream); + + LOG(RPI, Debug) << "Stream " << stream->name() << " buffer complete" + << ", buffer id " << index + << ", timestamp: " << buffer->metadata().timestamp; + + bool downscale = stream->swDownscale() > 1; + bool needs32bitConv = !!(stream->getFlags() & StreamFlag::Needs32bitConv); + + if (downscale || needs32bitConv) + dmabufSyncStart(buffer->planes()[0].fd); + + if (downscale) { + /* Further software downscaling must be applied. */ + downscaleStreamBuffer(stream, index); + } + + /* Convert 24bpp outputs to 32bpp outputs where necessary. */ + if (needs32bitConv) { + unsigned int stride = stream->configuration().stride; + unsigned int width = stream->configuration().size.width; + unsigned int height = stream->configuration().size.height; + + const RPi::BufferObject &b = stream->getBuffer(index); + + ASSERT(b.mapped); + void *mem = b.mapped->planes()[0].data(); + do32BitConversion(mem, width, height, stride); + } + + if (downscale || needs32bitConv) + dmabufSyncEnd(buffer->planes()[0].fd); + + handleStreamBuffer(buffer, stream); + + /* + * Increment the number of ISP outputs generated. + * This is needed to track dropped frames. + */ + ispOutputCount_++; + handleState(); +} + +void PiSPCameraData::processStatsComplete(const ipa::RPi::BufferIds &buffers) +{ + if (!isRunning()) + return; + + handleStreamBuffer(cfe_[Cfe::Stats].getBuffers().at(buffers.stats & RPi::MaskID).buffer, + &cfe_[Cfe::Stats]); +} + +void PiSPCameraData::setCameraTimeout(uint32_t maxFrameLengthMs) +{ + /* + * Set the dequeue timeout to the larger of 5x the maximum reported + * frame length advertised by the IPA over a number of frames. Allow + * a minimum timeout value of 1s. + */ + utils::Duration timeout = + std::max<utils::Duration>(1s, 5 * maxFrameLengthMs * 1ms); + + LOG(RPI, Debug) << "Setting CFE timeout to " << timeout; + cfe_[Cfe::Output0].dev()->setDequeueTimeout(timeout); +} + +void PiSPCameraData::prepareIspComplete(const ipa::RPi::BufferIds &buffers, bool stitchSwapBuffers) +{ + unsigned int embeddedId = buffers.embedded & RPi::MaskID; + unsigned int bayerId = buffers.bayer & RPi::MaskID; + FrameBuffer *buffer; + + if (!isRunning()) + return; + + if (sensorMetadata_ && embeddedId) { + buffer = cfe_[Cfe::Embedded].getBuffers().at(embeddedId).buffer; + handleStreamBuffer(buffer, &cfe_[Cfe::Embedded]); + } + + if (!beEnabled_) { + /* + * If there is no need to run the Backend, just signal that the + * input buffer is completed and all Backend outputs are ready. + */ + ispOutputCount_ = ispOutputTotal_; + buffer = cfe_[Cfe::Output0].getBuffers().at(bayerId).buffer; + handleStreamBuffer(buffer, &cfe_[Cfe::Output0]); + } else + prepareBe(bayerId, stitchSwapBuffers); + + state_ = State::IpaComplete; + handleState(); +} + +int PiSPCameraData::configureCfe() +{ + V4L2DeviceFormat cfeFormat; + cfe_[Cfe::Output0].dev()->getFormat(&cfeFormat); + + std::scoped_lock<FrontEnd> l(*fe_); + + pisp_fe_global_config global; + fe_->GetGlobal(global); + global.enables &= ~PISP_FE_ENABLE_COMPRESS0; + + global.enables |= PISP_FE_ENABLE_OUTPUT0; + global.bayer_order = toPiSPBayerOrder(cfeFormat.fourcc); + + pisp_image_format_config image = toPiSPImageFormat(cfeFormat); + pisp_fe_input_config input = {}; + + input.streaming = 1; + input.format = image; + input.format.format = PISP_IMAGE_FORMAT_BPS_16; + + if (PISP_IMAGE_FORMAT_COMPRESSED(image.format)) { + pisp_compress_config compress; + compress.offset = DefaultCompressionOffset; + compress.mode = (image.format & PISP_IMAGE_FORMAT_COMPRESSION_MASK) / + PISP_IMAGE_FORMAT_COMPRESSION_MODE_1; + global.enables |= PISP_FE_ENABLE_COMPRESS0; + fe_->SetCompress(0, compress); + } + + if (input.format.width > pispVariant_.FrontEndDownscalerMaxWidth(0, 0)) + global.enables |= PISP_FE_ENABLE_DECIMATE; + + fe_->SetGlobal(global); + fe_->SetInput(input); + fe_->SetOutputFormat(0, image); + + return 0; +} + +bool PiSPCameraData::calculateCscConfiguration(const V4L2DeviceFormat &v4l2Format, pisp_be_ccm_config &csc) +{ + const PixelFormat &pixFormat = v4l2Format.fourcc.toPixelFormat(); + const PixelFormatInfo &info = PixelFormatInfo::info(pixFormat); + memset(&csc, 0, sizeof(csc)); + + if (info.colourEncoding == PixelFormatInfo::ColourEncodingYUV) { + /* Look up the correct YCbCr conversion matrix for this colour space. */ + if (v4l2Format.colorSpace == ColorSpace::Sycc) + be_->InitialiseYcbcr(csc, "jpeg"); + else if (v4l2Format.colorSpace == ColorSpace::Smpte170m) + be_->InitialiseYcbcr(csc, "smpte170m"); + else if (v4l2Format.colorSpace == ColorSpace::Rec709) + be_->InitialiseYcbcr(csc, "rec709"); + else { + LOG(RPI, Warning) + << "Unrecognised colour space " + << ColorSpace::toString(v4l2Format.colorSpace) + << ", defaulting to sYCC"; + be_->InitialiseYcbcr(csc, "jpeg"); + } + return true; + } + /* There will be more formats to check for in due course. */ + else if (pixFormat == formats::RGB888 || pixFormat == formats::RGBX8888 || + pixFormat == formats::XRGB8888 || pixFormat == formats::RGB161616) { + /* Identity matrix but with RB colour swap. */ + csc.coeffs[2] = csc.coeffs[4] = csc.coeffs[6] = 1 << 10; + return true; + } + + return false; +} + +int PiSPCameraData::configureBe(const std::optional<ColorSpace> &yuvColorSpace) +{ + pisp_image_format_config inputFormat; + V4L2DeviceFormat cfeFormat; + + isp_[Isp::Input].dev()->getFormat(&cfeFormat); + inputFormat = toPiSPImageFormat(cfeFormat); + + pisp_be_global_config global; + be_->GetGlobal(global); + global.bayer_enables &= ~(PISP_BE_BAYER_ENABLE_DECOMPRESS + + PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS + + PISP_BE_BAYER_ENABLE_TDN_COMPRESS + + PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS + + PISP_BE_BAYER_ENABLE_STITCH_COMPRESS); + global.rgb_enables &= ~(PISP_BE_RGB_ENABLE_RESAMPLE0 + + PISP_BE_RGB_ENABLE_RESAMPLE1 + + PISP_BE_RGB_ENABLE_DOWNSCALE0 + + PISP_BE_RGB_ENABLE_DOWNSCALE1 + + PISP_BE_RGB_ENABLE_CSC0 + + PISP_BE_RGB_ENABLE_CSC1); + + global.bayer_enables |= PISP_BE_BAYER_ENABLE_INPUT; + global.bayer_order = toPiSPBayerOrder(cfeFormat.fourcc); + + ispOutputTotal_ = 1; /* Config buffer */ + if (PISP_IMAGE_FORMAT_COMPRESSED(inputFormat.format)) { + pisp_decompress_config decompress; + decompress.offset = DefaultCompressionOffset; + decompress.mode = (inputFormat.format & PISP_IMAGE_FORMAT_COMPRESSION_MASK) + / PISP_IMAGE_FORMAT_COMPRESSION_MODE_1; + global.bayer_enables |= PISP_BE_BAYER_ENABLE_DECOMPRESS; + be_->SetDecompress(decompress); + } + + if (global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT0) { + pisp_be_output_format_config outputFormat0 = {}; + V4L2DeviceFormat ispFormat0 = {}; + + isp_[Isp::Output0].dev()->getFormat(&ispFormat0); + outputFormat0.image = toPiSPImageFormat(ispFormat0); + + pisp_be_ccm_config csc; + if (calculateCscConfiguration(ispFormat0, csc)) { + global.rgb_enables |= PISP_BE_RGB_ENABLE_CSC0; + be_->SetCsc(0, csc); + } + + BackEnd::SmartResize resize = {}; + resize.width = ispFormat0.size.width; + resize.height = ispFormat0.size.height; + be_->SetSmartResize(0, resize); + + setupOutputClipping(ispFormat0, outputFormat0); + + be_->SetOutputFormat(0, outputFormat0); + ispOutputTotal_++; + } + + if (global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT1) { + pisp_be_output_format_config outputFormat1 = {}; + V4L2DeviceFormat ispFormat1 = {}; + + isp_[Isp::Output1].dev()->getFormat(&ispFormat1); + outputFormat1.image = toPiSPImageFormat(ispFormat1); + + pisp_be_ccm_config csc; + if (calculateCscConfiguration(ispFormat1, csc)) { + global.rgb_enables |= PISP_BE_RGB_ENABLE_CSC1; + be_->SetCsc(1, csc); + } + + BackEnd::SmartResize resize = {}; + resize.width = ispFormat1.size.width; + resize.height = ispFormat1.size.height; + be_->SetSmartResize(1, resize); + + setupOutputClipping(ispFormat1, outputFormat1); + + be_->SetOutputFormat(1, outputFormat1); + ispOutputTotal_++; + } + + /* Setup the TDN I/O blocks in case TDN gets turned on later. */ + V4L2DeviceFormat tdnV4L2Format; + isp_[Isp::TdnOutput].dev()->getFormat(&tdnV4L2Format); + pisp_image_format_config tdnFormat = toPiSPImageFormat(tdnV4L2Format); + be_->SetTdnOutputFormat(tdnFormat); + be_->SetTdnInputFormat(tdnFormat); + + if (PISP_IMAGE_FORMAT_COMPRESSED(tdnFormat.format)) { + pisp_decompress_config tdnDecompress; + pisp_compress_config tdnCompress; + + tdnDecompress.offset = tdnCompress.offset = DefaultCompressionOffset; + tdnDecompress.mode = tdnCompress.mode = DefaultCompressionMode; + be_->SetTdnDecompress(tdnDecompress); + be_->SetTdnCompress(tdnCompress); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS + + PISP_BE_BAYER_ENABLE_TDN_COMPRESS; + } + + /* Likewise for the Stitch block. */ + V4L2DeviceFormat stitchV4L2Format; + isp_[Isp::StitchOutput].dev()->getFormat(&stitchV4L2Format); + pisp_image_format_config stitchFormat = toPiSPImageFormat(stitchV4L2Format); + be_->SetStitchOutputFormat(stitchFormat); + be_->SetStitchInputFormat(stitchFormat); + + if (PISP_IMAGE_FORMAT_COMPRESSED(stitchFormat.format)) { + pisp_decompress_config stitchDecompress; + pisp_compress_config stitchCompress; + + /* Stitch block is after BLC, so compression offset should be 0. */ + stitchDecompress.offset = stitchCompress.offset = 0; + stitchDecompress.mode = stitchCompress.mode = DefaultCompressionMode; + be_->SetStitchDecompress(stitchDecompress); + be_->SetStitchCompress(stitchCompress); + global.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS + + PISP_BE_BAYER_ENABLE_STITCH_COMPRESS; + } + + /* + * For the bit of the pipeline where we go temporarily into YCbCr, we'll use the + * same flavour of YCbCr as dictated by the headline colour space. But there's + * no benefit from compressing and shifting the range, so we'll stick with the + * full range version of whatever that colour space is. + */ + if (yuvColorSpace) { + pisp_be_ccm_config ccm; + if (yuvColorSpace == ColorSpace::Sycc) { + be_->InitialiseYcbcr(ccm, "jpeg"); + be_->SetYcbcr(ccm); + be_->InitialiseYcbcrInverse(ccm, "jpeg"); + be_->SetYcbcrInverse(ccm); + } else if (yuvColorSpace == ColorSpace::Smpte170m) { + /* We want the full range version of smpte170m, aka. jpeg */ + be_->InitialiseYcbcr(ccm, "jpeg"); + be_->SetYcbcr(ccm); + be_->InitialiseYcbcrInverse(ccm, "jpeg"); + be_->SetYcbcrInverse(ccm); + } else if (yuvColorSpace == ColorSpace::Rec709) { + be_->InitialiseYcbcr(ccm, "rec709_full"); + be_->SetYcbcr(ccm); + be_->InitialiseYcbcrInverse(ccm, "rec709_full"); + be_->SetYcbcrInverse(ccm); + } else { + /* Validation should have ensured this can't happen. */ + LOG(RPI, Error) + << "Invalid colour space " + << ColorSpace::toString(yuvColorSpace); + ASSERT(0); + } + } else { + /* Again, validation should have prevented this. */ + LOG(RPI, Error) << "No YUV colour space"; + ASSERT(0); + } + + be_->SetGlobal(global); + be_->SetInputFormat(inputFormat); + + return 0; +} + +void PiSPCameraData::platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) +{ + pisp_be_crop_config beCrop = { + static_cast<uint16_t>(ispCrop.x), + static_cast<uint16_t>(ispCrop.y), + static_cast<uint16_t>(ispCrop.width), + static_cast<uint16_t>(ispCrop.height) + }; + + LOG(RPI, Debug) << "Output " << index << " " << ispCrop.toString(); + be_->SetCrop(index, beCrop); +} + +int PiSPCameraData::platformInitIpa(ipa::RPi::InitParams ¶ms) +{ + params.fe = fe_.fd(); + params.be = be_.fd(); + return 0; +} + +int PiSPCameraData::configureEntities(V4L2SubdeviceFormat sensorFormat, + V4L2SubdeviceFormat &embeddedFormat) +{ + int ret = 0; + + constexpr unsigned int csiVideoSinkPad = 0; + constexpr unsigned int csiVideoSourcePad = 1; + constexpr unsigned int csiMetaSourcePad = 2; + + constexpr unsigned int feVideoSinkPad = 0; + constexpr unsigned int feConfigSinkPad = 1; + constexpr unsigned int feVideo0SourcePad = 2; + constexpr unsigned int feVideo1SourcePad = 3; + constexpr unsigned int feStatsSourcePad = 4; + + const MediaEntity *csi2 = csi2Subdev_->entity(); + const MediaEntity *fe = feSubdev_->entity(); + + for (MediaLink *link : csi2->pads()[csiVideoSourcePad]->links()) { + if (link->sink()->entity()->name() == "rp1-cfe-csi2-ch0") + link->setEnabled(false); + else if (link->sink()->entity()->name() == "pisp-fe") + link->setEnabled(true); + } + + csi2->pads()[csiMetaSourcePad]->links()[0]->setEnabled(sensorMetadata_); + + fe->pads()[feConfigSinkPad]->links()[0]->setEnabled(true); + fe->pads()[feVideo0SourcePad]->links()[0]->setEnabled(true); + fe->pads()[feVideo1SourcePad]->links()[0]->setEnabled(false); + fe->pads()[feStatsSourcePad]->links()[0]->setEnabled(true); + + const V4L2Subdevice::Stream imageStream{ + csiVideoSinkPad, + sensor_->imageStream().stream + }; + const V4L2Subdevice::Stream embeddedDataStream{ + csiVideoSinkPad, + sensor_->embeddedDataStream().value_or(V4L2Subdevice::Stream{}).stream + }; + + V4L2Subdevice::Routing routing; + routing.emplace_back(imageStream, V4L2Subdevice::Stream{ csiVideoSourcePad, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); + + if (sensorMetadata_) + routing.emplace_back(embeddedDataStream, + V4L2Subdevice::Stream{ csiMetaSourcePad, 0 }, + V4L2_SUBDEV_ROUTE_FL_ACTIVE); + + ret = csi2Subdev_->setRouting(&routing); + if (ret) + return ret; + + ret = csi2Subdev_->setFormat(imageStream, &sensorFormat); + if (ret) + return ret; + + if (sensorMetadata_) { + ret = csi2Subdev_->setFormat(embeddedDataStream, &embeddedFormat); + if (ret) + return ret; + } + + V4L2SubdeviceFormat feFormat = sensorFormat; + feFormat.code = mbusCodeUnpacked16(sensorFormat.code); + ret = feSubdev_->setFormat(feVideoSinkPad, &feFormat); + if (ret) + return ret; + + ret = csi2Subdev_->setFormat(csiVideoSourcePad, &feFormat); + if (ret) + return ret; + + V4L2DeviceFormat feOutputFormat; + cfe_[Cfe::Output0].dev()->getFormat(&feOutputFormat); + BayerFormat feOutputBayer = BayerFormat::fromV4L2PixelFormat(feOutputFormat.fourcc); + + feFormat.code = bayerToMbusCode(feOutputBayer); + ret = feSubdev_->setFormat(feVideo0SourcePad, &feFormat); + + return ret; +} + +void PiSPCameraData::prepareCfe() +{ + /* Fetch an unused config buffer from the stream .*/ + const RPi::BufferObject &config = cfe_[Cfe::Config].acquireBuffer(); + ASSERT(config.mapped); + + { + std::scoped_lock<FrontEnd> l(*fe_); + Span<uint8_t> configBuffer = config.mapped->planes()[0]; + fe_->Prepare(reinterpret_cast<pisp_fe_config *>(configBuffer.data())); + } + + config.buffer->_d()->metadata().planes()[0].bytesused = sizeof(pisp_fe_config); + cfe_[Cfe::Config].queueBuffer(config.buffer); +} + +void PiSPCameraData::prepareBe(uint32_t bufferId, bool stitchSwapBuffers) +{ + ispOutputCount_ = 0; + + FrameBuffer *buffer = cfe_[Cfe::Output0].getBuffers().at(bufferId).buffer; + + LOG(RPI, Debug) << "Input re-queue to ISP, buffer id " << bufferId + << ", timestamp: " << buffer->metadata().timestamp; + + isp_[Isp::Input].queueBuffer(buffer); + + /* Ping-pong between input/output buffers for the TDN and Stitch nodes. */ + if (!config_.disableTdn) { + isp_[Isp::TdnInput].queueBuffer(tdnBuffers_[tdnInputIndex_]); + isp_[Isp::TdnOutput].queueBuffer(tdnBuffers_[tdnInputIndex_ ^ 1]); + tdnInputIndex_ ^= 1; + } + + if (!config_.disableHdr) { + if (stitchSwapBuffers) + stitchInputIndex_ ^= 1; + isp_[Isp::StitchInput].queueBuffer(stitchBuffers_[stitchInputIndex_]); + isp_[Isp::StitchOutput].queueBuffer(stitchBuffers_[stitchInputIndex_ ^ 1]); + } + + /* Fetch an unused config buffer from the stream .*/ + const RPi::BufferObject &config = isp_[Isp::Config].acquireBuffer(); + ASSERT(config.mapped); + + Span<uint8_t> configBufferSpan = config.mapped->planes()[0]; + pisp_be_tiles_config *configBuffer = reinterpret_cast<pisp_be_tiles_config *>(configBufferSpan.data()); + be_->Prepare(configBuffer); + + /* + * If the LIBCAMERA_RPI_PISP_CONFIG_DUMP environment variable is set, + * dump the Backend config to the given file. This is a one-shot + * operation, so log the filename that was provided and allow the + * application to change the filename for multiple dumps in a single + * run. + * + * \todo Using an environment variable is only a temporary solution + * until we have support for vendor specific controls in libcamera. + */ + const char *config_dump = utils::secure_getenv("LIBCAMERA_RPI_PISP_CONFIG_DUMP"); + if (config_dump && last_dump_file_ != config_dump) { + std::ofstream of(config_dump); + if (of.is_open()) { + of << be_->GetJsonConfig(configBuffer); + last_dump_file_ = config_dump; + } + } + + isp_[Isp::Config].queueBuffer(config.buffer); +} + +void PiSPCameraData::tryRunPipeline() +{ + /* If any of our request or buffer queues are empty, we cannot proceed. */ + if (state_ != State::Idle || requestQueue_.empty() || !cfeJobComplete()) + return; + + CfeJob &job = cfeJobQueue_.front(); + + /* Take the first request from the queue and action the IPA. */ + Request *request = requestQueue_.front(); + + /* See if a new ScalerCrop value needs to be applied. */ + applyScalerCrop(request->controls()); + + /* + * Clear the request metadata and fill it with some initial non-IPA + * related controls. We clear it first because the request metadata + * may have been populated if we have dropped the previous frame. + */ + request->metadata().clear(); + fillRequestMetadata(job.sensorControls, request); + + /* Set our state to say the pipeline is active. */ + state_ = State::Busy; + + unsigned int bayerId = cfe_[Cfe::Output0].getBufferId(job.buffers[&cfe_[Cfe::Output0]]); + unsigned int statsId = cfe_[Cfe::Stats].getBufferId(job.buffers[&cfe_[Cfe::Stats]]); + ASSERT(bayerId && statsId); + + std::stringstream ss; + ss << "Signalling IPA processStats and prepareIsp:" + << " Bayer buffer id: " << bayerId + << " Stats buffer id: " << statsId; + + ipa::RPi::PrepareParams params; + params.buffers.bayer = RPi::MaskBayerData | bayerId; + params.buffers.stats = RPi::MaskStats | statsId; + params.buffers.embedded = 0; + params.ipaContext = requestQueue_.front()->sequence(); + params.delayContext = job.delayContext; + params.sensorControls = std::move(job.sensorControls); + params.requestControls = request->controls(); + + if (sensorMetadata_) { + unsigned int embeddedId = + cfe_[Cfe::Embedded].getBufferId(job.buffers[&cfe_[Cfe::Embedded]]); + + ASSERT(embeddedId); + params.buffers.embedded = RPi::MaskEmbeddedData | embeddedId; + ss << " Embedded buffer id: " << embeddedId; + } + + LOG(RPI, Debug) << ss.str(); + + cfeJobQueue_.pop(); + ipa_->prepareIsp(params); +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerPiSP, "rpi/pisp") + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/vc4/data/meson.build b/src/libcamera/pipeline/rpi/vc4/data/meson.build index cca5e388..179feebc 100644 --- a/src/libcamera/pipeline/rpi/vc4/data/meson.build +++ b/src/libcamera/pipeline/rpi/vc4/data/meson.build @@ -5,4 +5,5 @@ conf_files = files([ ]) install_data(conf_files, - install_dir : pipeline_data_dir / 'rpi' / 'vc4') + install_dir : pipeline_data_dir / 'rpi' / 'vc4', + install_tag : 'runtime') diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp b/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp deleted file mode 100644 index 317b1fc1..00000000 --- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2020, Raspberry Pi Ltd - * - * dma_heaps.h - Helper class for dma-heap allocations. - */ - -#include "dma_heaps.h" - -#include <array> -#include <fcntl.h> -#include <linux/dma-buf.h> -#include <linux/dma-heap.h> -#include <sys/ioctl.h> -#include <unistd.h> - -#include <libcamera/base/log.h> - -/* - * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma - * to only have to worry about importing. - * - * Annoyingly, should the cma heap size be specified on the kernel command line - * instead of DT, the heap gets named "reserved" instead. - */ -static constexpr std::array<const char *, 2> heapNames = { - "/dev/dma_heap/linux,cma", - "/dev/dma_heap/reserved" -}; - -namespace libcamera { - -LOG_DECLARE_CATEGORY(RPI) - -namespace RPi { - -DmaHeap::DmaHeap() -{ - for (const char *name : heapNames) { - int ret = ::open(name, O_RDWR | O_CLOEXEC, 0); - if (ret < 0) { - ret = errno; - LOG(RPI, Debug) << "Failed to open " << name << ": " - << strerror(ret); - continue; - } - - dmaHeapHandle_ = UniqueFD(ret); - break; - } - - if (!dmaHeapHandle_.isValid()) - LOG(RPI, Error) << "Could not open any dmaHeap device"; -} - -DmaHeap::~DmaHeap() = default; - -UniqueFD DmaHeap::alloc(const char *name, std::size_t size) -{ - int ret; - - if (!name) - return {}; - - struct dma_heap_allocation_data alloc = {}; - - alloc.len = size; - alloc.fd_flags = O_CLOEXEC | O_RDWR; - - ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc); - if (ret < 0) { - LOG(RPI, Error) << "dmaHeap allocation failure for " - << name; - return {}; - } - - UniqueFD allocFd(alloc.fd); - ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name); - if (ret < 0) { - LOG(RPI, Error) << "dmaHeap naming failure for " - << name; - return {}; - } - - return allocFd; -} - -} /* namespace RPi */ - -} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h b/src/libcamera/pipeline/rpi/vc4/dma_heaps.h deleted file mode 100644 index 0a4a8d86..00000000 --- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2020, Raspberry Pi Ltd - * - * dma_heaps.h - Helper class for dma-heap allocations. - */ - -#pragma once - -#include <stddef.h> - -#include <libcamera/base/unique_fd.h> - -namespace libcamera { - -namespace RPi { - -class DmaHeap -{ -public: - DmaHeap(); - ~DmaHeap(); - bool isValid() const { return dmaHeapHandle_.isValid(); } - UniqueFD alloc(const char *name, std::size_t size); - -private: - UniqueFD dmaHeapHandle_; -}; - -} /* namespace RPi */ - -} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build index cdb049c5..9b37c2f0 100644 --- a/src/libcamera/pipeline/rpi/vc4/meson.build +++ b/src/libcamera/pipeline/rpi/vc4/meson.build @@ -1,7 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ - 'dma_heaps.cpp', +libcamera_internal_sources += files([ 'vc4.cpp', ]) diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp index 616e0bc9..fe910bdf 100644 --- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp +++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019-2023, Raspberry Pi Ltd * - * vc4.cpp - Pipeline handler for VC4-based Raspberry Pi devices + * Pipeline handler for VC4-based Raspberry Pi devices */ #include <linux/bcm2835-isp.h> @@ -12,12 +12,11 @@ #include <libcamera/formats.h> #include "libcamera/internal/device_enumerator.h" +#include "libcamera/internal/dma_buf_allocator.h" #include "../common/pipeline_base.h" #include "../common/rpi_stream.h" -#include "dma_heaps.h" - using namespace std::chrono_literals; namespace libcamera { @@ -87,7 +86,7 @@ public: RPi::Device<Isp, 4> isp_; /* DMAHEAP allocation helper. */ - RPi::DmaHeap dmaHeap_; + DmaBufAllocator dmaHeap_; SharedFD lsTable_; struct Config { @@ -110,9 +109,10 @@ public: Config config_; private: - void platformSetIspCrop() override + void platformSetIspCrop([[maybe_unused]] unsigned int index, const Rectangle &ispCrop) override { - isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &ispCrop_); + Rectangle crop = ispCrop; + isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &crop); } int platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) override; @@ -283,6 +283,9 @@ int PipelineHandlerVc4::prepareBuffers(Camera *camera) numBuffers = 1; } + LOG(RPI, Debug) << "Preparing " << numBuffers + << " buffers for stream " << stream->name(); + ret = stream->prepareBuffers(numBuffers); if (ret < 0) return ret; @@ -423,7 +426,7 @@ CameraConfiguration::Status Vc4CameraData::platformValidate(RPi::RPiCameraConfig BayerFormat rawBayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat); /* Apply the sensor bitdepth. */ - rawBayer.bitDepth = BayerFormat::fromMbusCode(rpiConfig->sensorFormat_.mbus_code).bitDepth; + rawBayer.bitDepth = BayerFormat::fromMbusCode(rpiConfig->sensorFormat_.code).bitDepth; /* Default to CSI2 packing if the user request is unsupported. */ if (rawBayer.packing != BayerFormat::Packing::CSI2 && @@ -431,6 +434,17 @@ CameraConfiguration::Status Vc4CameraData::platformValidate(RPi::RPiCameraConfig rawBayer.packing = BayerFormat::Packing::CSI2; PixelFormat rawFormat = rawBayer.toPixelFormat(); + + /* + * Try for an unpacked format if a packed one wasn't available. + * This catches 8 (and 16) bit formats which would otherwise + * fail. + */ + if (!rawFormat.isValid() && rawBayer.packing != BayerFormat::Packing::None) { + rawBayer.packing = BayerFormat::Packing::None; + rawFormat = rawBayer.toPixelFormat(); + } + if (rawStream->pixelFormat != rawFormat || rawStream->size != rpiConfig->sensorFormat_.size) { rawStream->pixelFormat = rawFormat; @@ -496,9 +510,9 @@ int Vc4CameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> & } std::optional<std::string> target = (*root)["target"].get<std::string>(); - if (!target || *target != "bcm2835") { + if (target != "bcm2835") { LOG(RPI, Error) << "Unexpected target reported: expected \"bcm2835\", got " - << *target; + << (target ? target->c_str() : "(unknown)"); return -EINVAL; } @@ -628,7 +642,7 @@ int Vc4CameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfi * \todo If Output 1 format is not YUV420, Output 1 ought to be disabled as * colour denoise will not run. */ - if (outStreams.size() == 1) { + if (outStreams.size() <= 1) { V4L2VideoDevice *dev = isp_[Isp::Output1].dev(); V4L2DeviceFormat output1Format; @@ -688,13 +702,19 @@ int Vc4CameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfi /* Figure out the smallest selection the ISP will allow. */ Rectangle testCrop(0, 0, 1, 1); isp_[Isp::Input].dev()->setSelection(V4L2_SEL_TGT_CROP, &testCrop); - ispMinCropSize_ = testCrop.size(); /* Adjust aspect ratio by providing crops on the input image. */ Size size = unicamFormat.size.boundedToAspectRatio(maxSize); - ispCrop_ = size.centeredTo(Rectangle(unicamFormat.size).center()); + Rectangle ispCrop = size.centeredTo(Rectangle(unicamFormat.size).center()); - platformSetIspCrop(); + platformSetIspCrop(0, ispCrop); + /* + * Set the scaler crop to the value we are using (scaled to native sensor + * coordinates). + */ + cropParams_.emplace(std::piecewise_construct, + std::forward_as_tuple(0), + std::forward_as_tuple(ispCrop, testCrop.size(), 0)); return 0; } @@ -789,7 +809,7 @@ void Vc4CameraData::ispInputDequeue(FrameBuffer *buffer) void Vc4CameraData::ispOutputDequeue(FrameBuffer *buffer) { RPi::Stream *stream = nullptr; - unsigned int index; + unsigned int index = 0; if (!isRunning()) return; @@ -942,6 +962,7 @@ void Vc4CameraData::tryRunPipeline() params.requestControls = request->controls(); params.ipaContext = request->sequence(); params.delayContext = bayerFrame.delayContext; + params.buffers.embedded = 0; if (embeddedBuffer) { unsigned int embeddedId = unicam_[Unicam::Embedded].getBufferId(embeddedBuffer); @@ -1004,6 +1025,6 @@ bool Vc4CameraData::findMatchingBuffers(BayerFrame &bayerFrame, FrameBuffer *&em return true; } -REGISTER_PIPELINE_HANDLER(PipelineHandlerVc4) +REGISTER_PIPELINE_HANDLER(PipelineHandlerVc4, "rpi/vc4") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build index 42b0896d..dda3de97 100644 --- a/src/libcamera/pipeline/simple/meson.build +++ b/src/libcamera/pipeline/simple/meson.build @@ -1,5 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'simple.cpp', ]) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 911051b2..efb07051 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2020, Laurent Pinchart * Copyright (C) 2019, Martijn Braam * - * simple.cpp - Pipeline handler for simple pipelines + * Pipeline handler for simple pipelines */ #include <algorithm> @@ -13,8 +13,9 @@ #include <memory> #include <queue> #include <set> -#include <string> +#include <stdint.h> #include <string.h> +#include <string> #include <unordered_map> #include <utility> #include <vector> @@ -30,14 +31,16 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/converter.h" +#include "libcamera/internal/delayed_controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/software_isp/software_isp.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" - namespace libcamera { LOG_DEFINE_CATEGORY(SimplePipeline) @@ -163,7 +166,7 @@ LOG_DEFINE_CATEGORY(SimplePipeline) * handler has no a priori knowledge of. The pipeline handler thus implements a * heuristic to handle sharing of hardware resources in a generic fashion. * - * Two cameras are considered to be mutually exclusive if their share common + * Two cameras are considered to be mutually exclusive if they share common * pads along the pipeline from the camera sensor to the video node. An entity * can thus be used concurrently by multiple cameras, as long as pads are * distinct. @@ -178,6 +181,56 @@ LOG_DEFINE_CATEGORY(SimplePipeline) class SimplePipelineHandler; +struct SimpleFrameInfo { + SimpleFrameInfo(uint32_t f, Request *r, bool m) + : frame(f), request(r), metadataRequired(m), metadataProcessed(false) + { + } + + uint32_t frame; + Request *request; + bool metadataRequired; + bool metadataProcessed; +}; + +class SimpleFrames +{ +public: + void create(Request *request, bool metadataRequested); + void destroy(uint32_t frame); + void clear(); + + SimpleFrameInfo *find(uint32_t frame); + +private: + std::map<uint32_t, SimpleFrameInfo> frameInfo_; +}; + +void SimpleFrames::create(Request *request, bool metadataRequired) +{ + const uint32_t frame = request->sequence(); + auto [it, inserted] = frameInfo_.try_emplace(frame, frame, request, metadataRequired); + ASSERT(inserted); +} + +void SimpleFrames::destroy(uint32_t frame) +{ + frameInfo_.erase(frame); +} + +void SimpleFrames::clear() +{ + frameInfo_.clear(); +} + +SimpleFrameInfo *SimpleFrames::find(uint32_t frame) +{ + auto info = frameInfo_.find(frame); + if (info == frameInfo_.end()) + return nullptr; + return &info->second; +} + struct SimplePipelineInfo { const char *driver; /* @@ -185,17 +238,25 @@ struct SimplePipelineInfo { * and the number of streams it supports. */ std::vector<std::pair<const char *, unsigned int>> converters; + /* + * Using Software ISP is to be enabled per driver. + * + * The Software ISP can't be used together with the converters. + */ + bool swIspEnabled; }; namespace { static const SimplePipelineInfo supportedDevices[] = { - { "dcmipp", {} }, - { "imx7-csi", { { "pxp", 1 } } }, - { "j721e-csi2rx", {} }, - { "mxc-isi", {} }, - { "qcom-camss", {} }, - { "sun6i-csi", {} }, + { "dcmipp", {}, false }, + { "imx7-csi", { { "pxp", 1 } }, false }, + { "intel-ipu6", {}, true }, + { "j721e-csi2rx", {}, true }, + { "mtk-seninf", { { "mtk-mdp", 3 } }, false }, + { "mxc-isi", {}, false }, + { "qcom-camss", {}, true }, + { "sun6i-csi", {}, false }, }; } /* namespace */ @@ -215,7 +276,8 @@ public: int setupFormats(V4L2SubdeviceFormat *format, V4L2Subdevice::Whence whence, Transform transform = Transform::Identity); - void bufferReady(FrameBuffer *buffer); + void imageBufferReady(FrameBuffer *buffer); + void clearIncompleteRequests(); unsigned int streamIndex(const Stream *stream) const { @@ -265,21 +327,36 @@ public: std::list<Entity> entities_; std::unique_ptr<CameraSensor> sensor_; V4L2VideoDevice *video_; + V4L2Subdevice *frameStartEmitter_; std::vector<Configuration> configs_; std::map<PixelFormat, std::vector<const Configuration *>> formats_; + std::unique_ptr<DelayedControls> delayedCtrls_; + + std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_; + struct RequestOutputs { + Request *request; + std::map<const Stream *, FrameBuffer *> outputs; + }; + std::queue<RequestOutputs> conversionQueue_; + bool useConversion_; + std::unique_ptr<Converter> converter_; - std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_; - bool useConverter_; - std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_; + std::unique_ptr<SoftwareIsp> swIsp_; + SimpleFrames frameInfo_; private: void tryPipeline(unsigned int code, const Size &size); static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink); - void converterInputDone(FrameBuffer *buffer); - void converterOutputDone(FrameBuffer *buffer); + void tryCompleteRequest(Request *request); + void conversionInputDone(FrameBuffer *buffer); + void conversionOutputDone(FrameBuffer *buffer); + + void ispStatsReady(uint32_t frame, uint32_t bufferId); + void metadataReady(uint32_t frame, const ControlList &metadata); + void setSensorControls(const ControlList &sensorControls); }; class SimpleCameraConfiguration : public CameraConfiguration @@ -331,6 +408,7 @@ public: V4L2VideoDevice *video(const MediaEntity *entity); V4L2Subdevice *subdev(const MediaEntity *entity); MediaDevice *converter() { return converter_; } + bool swIspEnabled() const { return swIspEnabled_; } protected: int queueRequestDevice(Camera *camera, Request *request) override; @@ -349,16 +427,16 @@ private: return static_cast<SimpleCameraData *>(camera->_d()); } - std::vector<MediaEntity *> locateSensors(); + std::vector<MediaEntity *> locateSensors(MediaDevice *media); static int resetRoutingTable(V4L2Subdevice *subdev); const MediaPad *acquirePipeline(SimpleCameraData *data); void releasePipeline(SimpleCameraData *data); - MediaDevice *media_; std::map<const MediaEntity *, EntityData> entities_; MediaDevice *converter_; + bool swIspEnabled_; }; /* ----------------------------------------------------------------------------- @@ -370,8 +448,6 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, MediaEntity *sensor) : Camera::Private(pipe), streams_(numStreams) { - int ret; - /* * Find the shortest path from the camera sensor to a video capture * device using the breadth-first search algorithm. This heuristic will @@ -462,12 +538,16 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, } /* Finally also remember the sensor. */ - sensor_ = std::make_unique<CameraSensor>(sensor); - ret = sensor_->init(); - if (ret) { - sensor_.reset(); + sensor_ = CameraSensorFactoryBase::create(sensor); + if (!sensor_) return; - } + + const CameraSensorProperties::SensorDelays &delays = sensor_->sensorDelays(); + std::unordered_map<uint32_t, DelayedControls::ControlParams> params = { + { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, + { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + }; + delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params); LOG(SimplePipeline, Debug) << "Found pipeline: " @@ -503,8 +583,26 @@ int SimpleCameraData::init() << "Failed to create converter, disabling format conversion"; converter_.reset(); } else { - converter_->inputBufferReady.connect(this, &SimpleCameraData::converterInputDone); - converter_->outputBufferReady.connect(this, &SimpleCameraData::converterOutputDone); + converter_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone); + converter_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone); + } + } + + /* + * Instantiate Soft ISP if this is enabled for the given driver and no converter is used. + */ + if (!converter_ && pipe->swIspEnabled()) { + swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get(), &controlInfo_); + if (!swIsp_->isValid()) { + LOG(SimplePipeline, Warning) + << "Failed to create software ISP, disabling software debayering"; + swIsp_.reset(); + } else { + swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone); + swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone); + swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady); + swIsp_->metadataReady.connect(this, &SimpleCameraData::metadataReady); + swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls); } } @@ -543,6 +641,20 @@ int SimpleCameraData::init() properties_ = sensor_->properties(); + /* Find the first subdev that can generate a frame start signal, if any. */ + frameStartEmitter_ = nullptr; + for (const Entity &entity : entities_) { + V4L2Subdevice *sd = pipe->subdev(entity.entity); + if (!sd || !sd->supportsFrameStartEvent()) + continue; + + LOG(SimplePipeline, Debug) + << "Using frameStart signal from '" + << entity.entity->name() << "'"; + frameStartEmitter_ = sd; + break; + } + return 0; } @@ -562,13 +674,13 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) * corresponding possible V4L2 pixel formats on the video node. */ V4L2SubdeviceFormat format{}; - format.mbus_code = code; + format.code = code; format.size = size; int ret = setupFormats(&format, V4L2Subdevice::TryFormat); if (ret < 0) { /* Pipeline configuration failed, skip this configuration. */ - format.mbus_code = code; + format.code = code; format.size = size; LOG(SimplePipeline, Debug) << "Sensor format " << format @@ -576,7 +688,7 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) return; } - V4L2VideoDevice::Formats videoFormats = video_->formats(format.mbus_code); + V4L2VideoDevice::Formats videoFormats = video_->formats(format.code); LOG(SimplePipeline, Debug) << "Adding configuration for " << format.size @@ -598,12 +710,20 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) config.captureFormat = pixelFormat; config.captureSize = format.size; - if (!converter_) { - config.outputFormats = { pixelFormat }; - config.outputSizes = config.captureSize; - } else { + if (converter_) { config.outputFormats = converter_->formats(pixelFormat); config.outputSizes = converter_->sizes(format.size); + } else if (swIsp_) { + config.outputFormats = swIsp_->formats(pixelFormat); + config.outputSizes = swIsp_->sizes(pixelFormat, format.size); + if (config.outputFormats.empty()) { + /* Do not use swIsp for unsupported pixelFormat's. */ + config.outputFormats = { pixelFormat }; + config.outputSizes = config.captureSize; + } + } else { + config.outputFormats = { pixelFormat }; + config.outputSizes = config.captureSize; } configs_.push_back(config); @@ -706,7 +826,7 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, if (ret < 0) return ret; - if (format->mbus_code != sourceFormat.mbus_code || + if (format->code != sourceFormat.code || format->size != sourceFormat.size) { LOG(SimplePipeline, Debug) << "Source '" << source->entity()->name() @@ -720,17 +840,14 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, } LOG(SimplePipeline, Debug) - << "Link '" << source->entity()->name() - << "':" << source->index() - << " -> '" << sink->entity()->name() - << "':" << sink->index() - << " configured with format " << *format; + << "Link " << *link << ": configured with format " + << *format; } return 0; } -void SimpleCameraData::bufferReady(FrameBuffer *buffer) +void SimpleCameraData::imageBufferReady(FrameBuffer *buffer) { SimplePipelineHandler *pipe = SimpleCameraData::pipe(); @@ -740,35 +857,34 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) * point converting an erroneous buffer. */ if (buffer->metadata().status != FrameMetadata::FrameSuccess) { - if (!useConverter_) { + if (!useConversion_) { /* No conversion, just complete the request. */ Request *request = buffer->request(); pipe->completeBuffer(request, buffer); - pipe->completeRequest(request); + tryCompleteRequest(request); return; } /* - * The converter is in use. Requeue the internal buffer for - * capture (unless the stream is being stopped), and complete - * the request with all the user-facing buffers. + * The converter or Software ISP is in use. Requeue the internal + * buffer for capture (unless the stream is being stopped), and + * complete the request with all the user-facing buffers. */ if (buffer->metadata().status != FrameMetadata::FrameCancelled) video_->queueBuffer(buffer); - if (converterQueue_.empty()) + if (conversionQueue_.empty()) return; - Request *request = nullptr; - for (auto &item : converterQueue_.front()) { - FrameBuffer *outputBuffer = item.second; - request = outputBuffer->request(); - pipe->completeBuffer(request, outputBuffer); - } - converterQueue_.pop(); + const RequestOutputs &outputs = conversionQueue_.front(); + for (auto &[stream, buf] : outputs.outputs) + pipe->completeBuffer(outputs.request, buf); + SimpleFrameInfo *info = frameInfo_.find(outputs.request->sequence()); + if (info) + info->metadataRequired = false; + tryCompleteRequest(outputs.request); + conversionQueue_.pop(); - if (request) - pipe->completeRequest(request); return; } @@ -782,9 +898,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) */ Request *request = buffer->request(); - if (useConverter_ && !converterQueue_.empty()) { - const std::map<unsigned int, FrameBuffer *> &outputs = - converterQueue_.front(); + if (useConversion_ && !conversionQueue_.empty()) { + const std::map<const Stream *, FrameBuffer *> &outputs = + conversionQueue_.front().outputs; if (!outputs.empty()) { FrameBuffer *outputBuffer = outputs.begin()->second; if (outputBuffer) @@ -797,40 +913,110 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) buffer->metadata().timestamp); /* - * Queue the captured and the request buffer to the converter if format - * conversion is needed. If there's no queued request, just requeue the - * captured buffer for capture. + * Queue the captured and the request buffer to the converter or Software + * ISP if format conversion is needed. If there's no queued request, just + * requeue the captured buffer for capture. */ - if (useConverter_) { - if (converterQueue_.empty()) { + if (useConversion_) { + if (conversionQueue_.empty()) { video_->queueBuffer(buffer); return; } - converter_->queueBuffers(buffer, converterQueue_.front()); - converterQueue_.pop(); + if (converter_) + converter_->queueBuffers(buffer, conversionQueue_.front().outputs); + else + /* + * request->sequence() cannot be retrieved from `buffer' inside + * queueBuffers because unique_ptr's make buffer->request() invalid + * already here. + */ + swIsp_->queueBuffers(request->sequence(), buffer, + conversionQueue_.front().outputs); + + conversionQueue_.pop(); return; } /* Otherwise simply complete the request. */ pipe->completeBuffer(request, buffer); - pipe->completeRequest(request); + tryCompleteRequest(request); +} + +void SimpleCameraData::clearIncompleteRequests() +{ + while (!conversionQueue_.empty()) { + pipe()->cancelRequest(conversionQueue_.front().request); + conversionQueue_.pop(); + } } -void SimpleCameraData::converterInputDone(FrameBuffer *buffer) +void SimpleCameraData::tryCompleteRequest(Request *request) +{ + if (request->hasPendingBuffers()) + return; + + SimpleFrameInfo *info = frameInfo_.find(request->sequence()); + if (!info) { + /* Something is really wrong, let's return. */ + return; + } + + if (info->metadataRequired && !info->metadataProcessed) + return; + + frameInfo_.destroy(info->frame); + pipe()->completeRequest(request); +} + +void SimpleCameraData::conversionInputDone(FrameBuffer *buffer) { /* Queue the input buffer back for capture. */ video_->queueBuffer(buffer); } -void SimpleCameraData::converterOutputDone(FrameBuffer *buffer) +void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) { SimplePipelineHandler *pipe = SimpleCameraData::pipe(); /* Complete the buffer and the request. */ Request *request = buffer->request(); if (pipe->completeBuffer(request, buffer)) - pipe->completeRequest(request); + tryCompleteRequest(request); +} + +void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId) +{ + swIsp_->processStats(frame, bufferId, + delayedCtrls_->get(frame)); +} + +void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata) +{ + SimpleFrameInfo *info = frameInfo_.find(frame); + if (!info) + return; + + info->request->metadata().merge(metadata); + info->metadataProcessed = true; + tryCompleteRequest(info->request); +} + +void SimpleCameraData::setSensorControls(const ControlList &sensorControls) +{ + delayedCtrls_->push(sensorControls); + /* + * Directly apply controls now if there is no frameStart signal. + * + * \todo Applying controls directly not only increases the risk of + * applying them to the wrong frame (or across a frame boundary), + * but it also bypasses delayedCtrls_, creating AGC regulation issues. + * Both problems should be fixed. + */ + if (!frameStartEmitter_) { + ControlList ctrls(sensorControls); + sensor_->setControls(&ctrls); + } } /* Retrieve all source pads connected to a sink pad through active routes. */ @@ -851,17 +1037,17 @@ std::vector<const MediaPad *> SimpleCameraData::routedSourcePads(MediaPad *sink) std::vector<const MediaPad *> pads; - for (const struct v4l2_subdev_route &route : routing) { - if (sink->index() != route.sink_pad || + for (const V4L2Subdevice::Route &route : routing) { + if (sink->index() != route.sink.pad || !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) continue; - const MediaPad *pad = entity->getPadByIndex(route.source_pad); + const MediaPad *pad = entity->getPadByIndex(route.source.pad); if (!pad) { LOG(SimplePipeline, Warning) << "Entity " << entity->name() << " has invalid route source pad " - << route.source_pad; + << route.source.pad; } pads.push_back(pad); @@ -881,6 +1067,33 @@ SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera, { } +namespace { + +static Size adjustSize(const Size &requestedSize, const SizeRange &supportedSizes) +{ + ASSERT(supportedSizes.min <= supportedSizes.max); + + if (supportedSizes.min == supportedSizes.max) + return supportedSizes.max; + + unsigned int hStep = supportedSizes.hStep; + unsigned int vStep = supportedSizes.vStep; + + if (hStep == 0) + hStep = supportedSizes.max.width - supportedSizes.min.width; + if (vStep == 0) + vStep = supportedSizes.max.height - supportedSizes.min.height; + + Size adjusted = requestedSize.boundedTo(supportedSizes.max) + .expandedTo(supportedSizes.min); + + return adjusted.shrunkBy(supportedSizes.min) + .alignedDownTo(hStep, vStep) + .grownBy(supportedSizes.min); +} + +} /* namespace */ + CameraConfiguration::Status SimpleCameraConfiguration::validate() { const CameraSensor *sensor = data_->sensor_.get(); @@ -997,10 +1210,19 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() } if (!pipeConfig_->outputSizes.contains(cfg.size)) { + Size adjustedSize = pipeConfig_->captureSize; + /* + * The converter (when present) may not be able to output + * a size identical to its input size. The capture size is thus + * not guaranteed to be a valid output size. In such cases, use + * the smaller valid output size closest to the requested. + */ + if (!pipeConfig_->outputSizes.contains(adjustedSize)) + adjustedSize = adjustSize(cfg.size, pipeConfig_->outputSizes); LOG(SimplePipeline, Debug) << "Adjusting size from " << cfg.size - << " to " << pipeConfig_->captureSize; - cfg.size = pipeConfig_->captureSize; + << " to " << adjustedSize; + cfg.size = adjustedSize; status = Adjusted; } @@ -1012,8 +1234,11 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() /* Set the stride, frameSize and bufferCount. */ if (needConversion_) { std::tie(cfg.stride, cfg.frameSize) = - data_->converter_->strideAndFrameSize(cfg.pixelFormat, - cfg.size); + data_->converter_ + ? data_->converter_->strideAndFrameSize(cfg.pixelFormat, + cfg.size) + : data_->swIsp_->strideAndFrameSize(cfg.pixelFormat, + cfg.size); if (cfg.stride == 0) return Invalid; } else { @@ -1029,7 +1254,7 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() cfg.frameSize = format.planes[0].size; } - cfg.bufferCount = 3; + cfg.bufferCount = 4; } return status; @@ -1120,7 +1345,7 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) const SimpleCameraData::Configuration *pipeConfig = config->pipeConfig(); V4L2SubdeviceFormat format{}; - format.mbus_code = pipeConfig->code; + format.code = pipeConfig->code; format.size = pipeConfig->sensorSize; ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat, @@ -1156,14 +1381,14 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) /* Configure the converter if needed. */ std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs; - data->useConverter_ = config->needConversion(); + data->useConversion_ = config->needConversion(); for (unsigned int i = 0; i < config->size(); ++i) { StreamConfiguration &cfg = config->at(i); cfg.setStream(&data->streams_[i]); - if (data->useConverter_) + if (data->useConversion_) outputCfgs.push_back(cfg); } @@ -1176,7 +1401,13 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) inputCfg.stride = captureFormat.planes[0].bpl; inputCfg.bufferCount = kNumInternalBuffers; - return data->converter_->configure(inputCfg, outputCfgs); + if (data->converter_) { + return data->converter_->configure(inputCfg, outputCfgs); + } else { + ipa::soft::IPAConfigInfo configInfo; + configInfo.sensorControls = data->sensor_->controls(); + return data->swIsp_->configure(inputCfg, outputCfgs, configInfo); + } } int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, @@ -1189,9 +1420,10 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, * Export buffers on the converter or capture video node, depending on * whether the converter is used or not. */ - if (data->useConverter_) - return data->converter_->exportBuffers(data->streamIndex(stream), - count, buffers); + if (data->useConversion_) + return data->converter_ + ? data->converter_->exportBuffers(stream, count, buffers) + : data->swIsp_->exportBuffers(stream, count, buffers); else return data->video_->exportBuffers(count, buffers); } @@ -1200,6 +1432,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL { SimpleCameraData *data = cameraData(camera); V4L2VideoDevice *video = data->video_; + V4L2Subdevice *frameStartEmitter = data->frameStartEmitter_; int ret; const MediaPad *pad = acquirePipeline(data); @@ -1210,13 +1443,13 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL return -EBUSY; } - if (data->useConverter_) { + if (data->useConversion_) { /* * When using the converter allocate a fixed number of internal * buffers. */ ret = video->allocateBuffers(kNumInternalBuffers, - &data->converterBuffers_); + &data->conversionBuffers_); } else { /* Otherwise, prepare for using buffers from the only stream. */ Stream *stream = &data->streams_[0]; @@ -1227,7 +1460,18 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL return ret; } - video->bufferReady.connect(data, &SimpleCameraData::bufferReady); + video->bufferReady.connect(data, &SimpleCameraData::imageBufferReady); + + data->delayedCtrls_->reset(); + if (frameStartEmitter) { + ret = frameStartEmitter->setFrameStartEnabled(true); + if (ret) { + stop(camera); + return ret; + } + frameStartEmitter->frameStart.connect(data->delayedCtrls_.get(), + &DelayedControls::applyControls); + } ret = video->streamOn(); if (ret < 0) { @@ -1235,15 +1479,21 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL return ret; } - if (data->useConverter_) { - ret = data->converter_->start(); + if (data->useConversion_) { + if (data->converter_) + ret = data->converter_->start(); + else if (data->swIsp_) + ret = data->swIsp_->start(); + else + ret = 0; + if (ret < 0) { stop(camera); return ret; } /* Queue all internal buffers for capture. */ - for (std::unique_ptr<FrameBuffer> &buffer : data->converterBuffers_) + for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_) video->queueBuffer(buffer.get()); } @@ -1254,16 +1504,29 @@ void SimplePipelineHandler::stopDevice(Camera *camera) { SimpleCameraData *data = cameraData(camera); V4L2VideoDevice *video = data->video_; + V4L2Subdevice *frameStartEmitter = data->frameStartEmitter_; + + if (frameStartEmitter) { + frameStartEmitter->setFrameStartEnabled(false); + frameStartEmitter->frameStart.disconnect(data->delayedCtrls_.get(), + &DelayedControls::applyControls); + } - if (data->useConverter_) - data->converter_->stop(); + if (data->useConversion_) { + if (data->converter_) + data->converter_->stop(); + else if (data->swIsp_) + data->swIsp_->stop(); + } video->streamOff(); video->releaseBuffers(); - video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady); + video->bufferReady.disconnect(data, &SimpleCameraData::imageBufferReady); - data->converterBuffers_.clear(); + data->frameInfo_.clear(); + data->clearIncompleteRequests(); + data->conversionBuffers_.clear(); releasePipeline(data); } @@ -1273,7 +1536,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) SimpleCameraData *data = cameraData(camera); int ret; - std::map<unsigned int, FrameBuffer *> buffers; + std::map<const Stream *, FrameBuffer *> buffers; for (auto &[stream, buffer] : request->buffers()) { /* @@ -1281,8 +1544,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) * queue, it will be handed to the converter in the capture * completion handler. */ - if (data->useConverter_) { - buffers.emplace(data->streamIndex(stream), buffer); + if (data->useConversion_) { + buffers.emplace(stream, buffer); } else { ret = data->video_->queueBuffer(buffer); if (ret < 0) @@ -1290,8 +1553,12 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) } } - if (data->useConverter_) - data->converterQueue_.push(std::move(buffers)); + data->frameInfo_.create(request, !!data->swIsp_); + if (data->useConversion_) { + data->conversionQueue_.push({ request, std::move(buffers) }); + if (data->swIsp_) + data->swIsp_->queueRequest(request->sequence(), request->controls()); + } return 0; } @@ -1300,7 +1567,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) * Match and Setup */ -std::vector<MediaEntity *> SimplePipelineHandler::locateSensors() +std::vector<MediaEntity *> +SimplePipelineHandler::locateSensors(MediaDevice *media) { std::vector<MediaEntity *> entities; @@ -1308,7 +1576,7 @@ std::vector<MediaEntity *> SimplePipelineHandler::locateSensors() * Gather all the camera sensor entities based on the function they * expose. */ - for (MediaEntity *entity : media_->entities()) { + for (MediaEntity *entity : media->entities()) { if (entity->function() == MEDIA_ENT_F_CAM_SENSOR) entities.push_back(entity); } @@ -1387,7 +1655,7 @@ int SimplePipelineHandler::resetRoutingTable(V4L2Subdevice *subdev) LOG(SimplePipeline, Debug) << "Routing table of " << subdev->deviceNode() - << " reset to " << routing.toString(); + << " reset to " << routing; return 0; } @@ -1396,17 +1664,18 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) { const SimplePipelineInfo *info = nullptr; unsigned int numStreams = 1; + MediaDevice *media; for (const SimplePipelineInfo &inf : supportedDevices) { DeviceMatch dm(inf.driver); - media_ = acquireMediaDevice(enumerator, dm); - if (media_) { + media = acquireMediaDevice(enumerator, dm); + if (media) { info = &inf; break; } } - if (!media_) + if (!media) return false; for (const auto &[name, streams] : info->converters) { @@ -1418,13 +1687,17 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) } } + swIspEnabled_ = info->swIspEnabled; + /* Locate the sensors. */ - std::vector<MediaEntity *> sensors = locateSensors(); + std::vector<MediaEntity *> sensors = locateSensors(media); if (sensors.empty()) { - LOG(SimplePipeline, Error) << "No sensor found"; + LOG(SimplePipeline, Info) << "No sensor found for " << media->deviceNode(); return false; } + LOG(SimplePipeline, Debug) << "Sensor found for " << media->deviceNode(); + /* * Create one camera data instance for each sensor and gather all * entities in all pipelines. @@ -1488,8 +1761,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) if (subdev->caps().hasStreams()) { /* * Reset the routing table to its default state - * to make sure entities are enumerate according - * to the defaul routing configuration. + * to make sure entities are enumerated according + * to the default routing configuration. */ ret = resetRoutingTable(subdev.get()); if (ret) { @@ -1604,6 +1877,6 @@ void SimplePipelineHandler::releasePipeline(SimpleCameraData *data) } } -REGISTER_PIPELINE_HANDLER(SimplePipelineHandler) +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler, "simple") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/uvcvideo/meson.build b/src/libcamera/pipeline/uvcvideo/meson.build index a3c2efd4..a3a91074 100644 --- a/src/libcamera/pipeline/uvcvideo/meson.build +++ b/src/libcamera/pipeline/uvcvideo/meson.build @@ -1,5 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'uvcvideo.cpp', ]) diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index ed9c7f88..58aa0eb4 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -2,17 +2,22 @@ /* * Copyright (C) 2019, Google Inc. * - * uvcvideo.cpp - Pipeline handler for uvcvideo devices + * Pipeline handler for uvcvideo devices */ #include <algorithm> +#include <bitset> +#include <cmath> #include <fstream> -#include <iomanip> -#include <math.h> +#include <map> #include <memory> -#include <tuple> +#include <optional> +#include <set> +#include <string> +#include <vector> #include <libcamera/base/log.h> +#include <libcamera/base/mutex.h> #include <libcamera/base/utils.h> #include <libcamera/camera.h> @@ -44,14 +49,18 @@ public: int init(MediaDevice *media); void addControl(uint32_t cid, const ControlInfo &v4l2info, ControlInfoMap::Map *ctrls); - void bufferReady(FrameBuffer *buffer); + void imageBufferReady(FrameBuffer *buffer); const std::string &id() const { return id_; } + Mutex openLock_; std::unique_ptr<V4L2VideoDevice> video_; Stream stream_; std::map<PixelFormat, std::vector<SizeRange>> formats_; + std::optional<v4l2_exposure_auto_type> autoExposureMode_; + std::optional<v4l2_exposure_auto_type> manualExposureMode_; + private: bool generateId(); @@ -89,16 +98,39 @@ public: bool match(DeviceEnumerator *enumerator) override; private: - int processControl(ControlList *controls, unsigned int id, - const ControlValue &value); + int processControl(const UVCCameraData *data, ControlList *controls, + unsigned int id, const ControlValue &value); int processControls(UVCCameraData *data, Request *request); + bool acquireDevice(Camera *camera) override; + void releaseDevice(Camera *camera) override; + UVCCameraData *cameraData(Camera *camera) { return static_cast<UVCCameraData *>(camera->_d()); } }; +namespace { + +std::optional<controls::ExposureTimeModeEnum> v4l2ToExposureMode(int32_t x) +{ + using namespace controls; + + switch (x) { + case V4L2_EXPOSURE_AUTO: + case V4L2_EXPOSURE_APERTURE_PRIORITY: + return ExposureTimeModeAuto; + case V4L2_EXPOSURE_MANUAL: + case V4L2_EXPOSURE_SHUTTER_PRIORITY: + return ExposureTimeModeManual; + default: + return {}; + } +} + +} /* namespace */ + UVCCameraConfiguration::UVCCameraConfiguration(UVCCameraData *data) : CameraConfiguration(), data_(data) { @@ -158,9 +190,29 @@ CameraConfiguration::Status UVCCameraConfiguration::validate() format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat); format.size = cfg.size; - int ret = data_->video_->tryFormat(&format); - if (ret) - return Invalid; + /* + * For power-consumption reasons video_ is closed when the camera is not + * acquired. Open it here if necessary. + */ + { + bool opened = false; + + MutexLocker locker(data_->openLock_); + + if (!data_->video_->isOpen()) { + int ret = data_->video_->open(); + if (ret) + return Invalid; + + opened = true; + } + + int ret = data_->video_->tryFormat(&format); + if (opened) + data_->video_->close(); + if (ret) + return Invalid; + } cfg.stride = format.planes[0].bpl; cfg.frameSize = format.planes[0].size; @@ -260,8 +312,8 @@ void PipelineHandlerUVC::stopDevice(Camera *camera) data->video_->releaseBuffers(); } -int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id, - const ControlValue &value) +int PipelineHandlerUVC::processControl(const UVCCameraData *data, ControlList *controls, + unsigned int id, const ControlValue &value) { uint32_t cid; @@ -271,12 +323,14 @@ int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id, cid = V4L2_CID_CONTRAST; else if (id == controls::Saturation) cid = V4L2_CID_SATURATION; - else if (id == controls::AeEnable) + else if (id == controls::ExposureTimeMode) cid = V4L2_CID_EXPOSURE_AUTO; else if (id == controls::ExposureTime) cid = V4L2_CID_EXPOSURE_ABSOLUTE; else if (id == controls::AnalogueGain) cid = V4L2_CID_GAIN; + else if (id == controls::Gamma) + cid = V4L2_CID_GAMMA; else return -EINVAL; @@ -293,22 +347,33 @@ int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id, case V4L2_CID_BRIGHTNESS: { float scale = std::max(max - def, def - min); float fvalue = value.get<float>() * scale + def; - controls->set(cid, static_cast<int32_t>(lroundf(fvalue))); + controls->set(cid, static_cast<int32_t>(std::lround(fvalue))); break; } case V4L2_CID_SATURATION: { float scale = def - min; float fvalue = value.get<float>() * scale + min; - controls->set(cid, static_cast<int32_t>(lroundf(fvalue))); + controls->set(cid, static_cast<int32_t>(std::lround(fvalue))); break; } case V4L2_CID_EXPOSURE_AUTO: { - int32_t ivalue = value.get<bool>() - ? V4L2_EXPOSURE_APERTURE_PRIORITY - : V4L2_EXPOSURE_MANUAL; - controls->set(V4L2_CID_EXPOSURE_AUTO, ivalue); + std::optional<v4l2_exposure_auto_type> mode; + + switch (value.get<int32_t>()) { + case controls::ExposureTimeModeAuto: + mode = data->autoExposureMode_; + break; + case controls::ExposureTimeModeManual: + mode = data->manualExposureMode_; + break; + } + + if (!mode) + return -EINVAL; + + controls->set(V4L2_CID_EXPOSURE_AUTO, static_cast<int32_t>(*mode)); break; } @@ -327,10 +392,14 @@ int PipelineHandlerUVC::processControl(ControlList *controls, unsigned int id, } float fvalue = (value.get<float>() - p) / m; - controls->set(cid, static_cast<int32_t>(lroundf(fvalue))); + controls->set(cid, static_cast<int32_t>(std::lround(fvalue))); break; } + case V4L2_CID_GAMMA: + controls->set(cid, static_cast<int32_t>(std::lround(value.get<float>() * 100))); + break; + default: { int32_t ivalue = value.get<int32_t>(); controls->set(cid, ivalue); @@ -346,7 +415,7 @@ int PipelineHandlerUVC::processControls(UVCCameraData *data, Request *request) ControlList controls(data->video_->controls()); for (const auto &[id, value] : request->controls()) - processControl(&controls, id, value); + processControl(data, &controls, id, value); for (const auto &ctrl : controls) LOG(UVC, Debug) @@ -411,6 +480,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator) return true; } +bool PipelineHandlerUVC::acquireDevice(Camera *camera) +{ + UVCCameraData *data = cameraData(camera); + + MutexLocker locker(data->openLock_); + + return data->video_->open() == 0; +} + +void PipelineHandlerUVC::releaseDevice(Camera *camera) +{ + UVCCameraData *data = cameraData(camera); + + MutexLocker locker(data->openLock_); + data->video_->close(); +} + int UVCCameraData::init(MediaDevice *media) { int ret; @@ -432,7 +518,7 @@ int UVCCameraData::init(MediaDevice *media) if (ret) return ret; - video_->bufferReady.connect(this, &UVCCameraData::bufferReady); + video_->bufferReady.connect(this, &UVCCameraData::imageBufferReady); /* Generate the camera ID. */ if (!generateId()) { @@ -510,8 +596,19 @@ int UVCCameraData::init(MediaDevice *media) addControl(cid, info, &ctrls); } + if (autoExposureMode_ && manualExposureMode_) { + /* \todo Move this to the Camera class */ + ctrls[&controls::AeEnable] = ControlInfo(false, true, true); + } + controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls); + /* + * Close to allow camera to go into runtime-suspend, video_ will be + * re-opened from acquireDevice() and validate(). + */ + video_->close(); + return 0; } @@ -597,7 +694,7 @@ void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, id = &controls::Saturation; break; case V4L2_CID_EXPOSURE_AUTO: - id = &controls::AeEnable; + id = &controls::ExposureTimeMode; break; case V4L2_CID_EXPOSURE_ABSOLUTE: id = &controls::ExposureTime; @@ -605,11 +702,15 @@ void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, case V4L2_CID_GAIN: id = &controls::AnalogueGain; break; + case V4L2_CID_GAMMA: + id = &controls::Gamma; + break; default: return; } /* Map the control info. */ + const std::vector<ControlValue> &v4l2Values = v4l2Info.values(); int32_t min = v4l2Info.min().get<int32_t>(); int32_t max = v4l2Info.max().get<int32_t>(); int32_t def = v4l2Info.def().get<int32_t>(); @@ -647,10 +748,79 @@ void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, }; break; - case V4L2_CID_EXPOSURE_AUTO: - info = ControlInfo{ false, true, true }; - break; + case V4L2_CID_EXPOSURE_AUTO: { + /* + * From the V4L2_CID_EXPOSURE_AUTO documentation: + * + * ------------------------------------------------------------ + * V4L2_EXPOSURE_AUTO: + * Automatic exposure time, automatic iris aperture. + * + * V4L2_EXPOSURE_MANUAL: + * Manual exposure time, manual iris. + * + * V4L2_EXPOSURE_SHUTTER_PRIORITY: + * Manual exposure time, auto iris. + * + * V4L2_EXPOSURE_APERTURE_PRIORITY: + * Auto exposure time, manual iris. + *------------------------------------------------------------- + * + * ExposureTimeModeAuto = { V4L2_EXPOSURE_AUTO, + * V4L2_EXPOSURE_APERTURE_PRIORITY } + * + * + * ExposureTimeModeManual = { V4L2_EXPOSURE_MANUAL, + * V4L2_EXPOSURE_SHUTTER_PRIORITY } + */ + + std::bitset< + std::max(V4L2_EXPOSURE_AUTO, + std::max(V4L2_EXPOSURE_APERTURE_PRIORITY, + std::max(V4L2_EXPOSURE_MANUAL, + V4L2_EXPOSURE_SHUTTER_PRIORITY))) + 1 + > exposureModes; + std::optional<controls::ExposureTimeModeEnum> lcDef; + + for (const ControlValue &value : v4l2Values) { + const auto x = value.get<int32_t>(); + if (0 <= x && static_cast<std::size_t>(x) < exposureModes.size()) { + exposureModes[x] = true; + + if (x == def) + lcDef = v4l2ToExposureMode(x); + } + } + + if (exposureModes[V4L2_EXPOSURE_AUTO]) + autoExposureMode_ = V4L2_EXPOSURE_AUTO; + else if (exposureModes[V4L2_EXPOSURE_APERTURE_PRIORITY]) + autoExposureMode_ = V4L2_EXPOSURE_APERTURE_PRIORITY; + + if (exposureModes[V4L2_EXPOSURE_SHUTTER_PRIORITY]) + manualExposureMode_ = V4L2_EXPOSURE_SHUTTER_PRIORITY; + else if (exposureModes[V4L2_EXPOSURE_MANUAL]) + manualExposureMode_ = V4L2_EXPOSURE_MANUAL; + + std::array<ControlValue, 2> values; + std::size_t count = 0; + + if (autoExposureMode_) + values[count++] = controls::ExposureTimeModeAuto; + + if (manualExposureMode_) + values[count++] = controls::ExposureTimeModeManual; + + if (count == 0) + return; + + info = ControlInfo{ + Span<const ControlValue>{ values.data(), count }, + !lcDef ? values.front() : *lcDef, + }; + break; + } case V4L2_CID_EXPOSURE_ABSOLUTE: /* * ExposureTime is in units of 1 µs, and UVC expects @@ -689,6 +859,15 @@ void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, break; } + case V4L2_CID_GAMMA: + /* UVC gamma is in units of 1/100 gamma. */ + info = ControlInfo{ + { min / 100.0f }, + { max / 100.0f }, + { def / 100.0f } + }; + break; + default: info = v4l2Info; break; @@ -697,7 +876,7 @@ void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, ctrls->emplace(id, info); } -void UVCCameraData::bufferReady(FrameBuffer *buffer) +void UVCCameraData::imageBufferReady(FrameBuffer *buffer) { Request *request = buffer->request(); @@ -709,6 +888,6 @@ void UVCCameraData::bufferReady(FrameBuffer *buffer) pipe()->completeRequest(request); } -REGISTER_PIPELINE_HANDLER(PipelineHandlerUVC) +REGISTER_PIPELINE_HANDLER(PipelineHandlerUVC, "uvcvideo") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/vimc/meson.build b/src/libcamera/pipeline/vimc/meson.build index 290eefb5..868e2546 100644 --- a/src/libcamera/pipeline/vimc/meson.build +++ b/src/libcamera/pipeline/vimc/meson.build @@ -1,5 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -libcamera_sources += files([ +libcamera_internal_sources += files([ 'vimc.cpp', ]) diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index 5536941a..07273bd2 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -2,18 +2,19 @@ /* * Copyright (C) 2018, Google Inc. * - * vimc.cpp - Pipeline handler for the vimc device + * Pipeline handler for the vimc device */ #include <algorithm> +#include <cmath> #include <iomanip> #include <map> -#include <math.h> #include <tuple> #include <linux/media-bus-format.h> #include <linux/version.h> +#include <libcamera/base/flags.h> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> @@ -21,6 +22,8 @@ #include <libcamera/control_ids.h> #include <libcamera/controls.h> #include <libcamera/formats.h> +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> #include <libcamera/request.h> #include <libcamera/stream.h> @@ -53,8 +56,8 @@ public: int init(); int allocateMockIPABuffers(); - void bufferReady(FrameBuffer *buffer); - void paramsBufferReady(unsigned int id, const Flags<ipa::vimc::TestFlag> flags); + void imageBufferReady(FrameBuffer *buffer); + void paramsComputed(unsigned int id, const Flags<ipa::vimc::TestFlag> flags); MediaDevice *media_; std::unique_ptr<CameraSensor> sensor_; @@ -114,6 +117,9 @@ static const std::map<PixelFormat, uint32_t> pixelformats{ { formats::BGR888, MEDIA_BUS_FMT_RGB888_1X24 }, }; +static constexpr Size kMinSize{ 16, 16 }; +static constexpr Size kMaxSize{ 4096, 2160 }; + } /* namespace */ VimcCameraConfiguration::VimcCameraConfiguration(VimcCameraData *data) @@ -153,14 +159,20 @@ CameraConfiguration::Status VimcCameraConfiguration::validate() const Size size = cfg.size; /* - * The scaler hardcodes a x3 scale-up ratio, and the sensor output size - * is aligned to two pixels in both directions. The output width and - * height thus have to be multiples of 6. + * The sensor output size is aligned to two pixels in both directions. + * Additionally, prior to v5.16, the scaler hardcodes a x3 scale-up + * ratio, requiring the output width and height to be multiples of 6. */ - cfg.size.width = std::max(48U, std::min(4096U, cfg.size.width)); - cfg.size.height = std::max(48U, std::min(2160U, cfg.size.height)); - cfg.size.width -= cfg.size.width % 6; - cfg.size.height -= cfg.size.height % 6; + Size minSize{ kMinSize }; + unsigned int alignment = 2; + + if (data_->media_->version() < KERNEL_VERSION(5, 16, 0)) { + minSize *= 3; + alignment *= 3; + } + + cfg.size.expandTo(minSize).boundTo(kMaxSize) + .alignDownTo(alignment, alignment); if (cfg.size != size) { LOG(VIMC, Debug) @@ -216,10 +228,12 @@ PipelineHandlerVimc::generateConfiguration(Camera *camera, } } - /* The scaler hardcodes a x3 scale-up ratio. */ - std::vector<SizeRange> sizes{ - SizeRange{ { 48, 48 }, { 4096, 2160 } } - }; + /* Prior to v5.16, the scaler hardcodes a x3 scale-up ratio. */ + Size minSize{ kMinSize }; + if (data->media_->version() < KERNEL_VERSION(5, 16, 0)) + minSize *= 3; + + std::vector<SizeRange> sizes{ { minSize, kMaxSize } }; formats[pixelformat.first] = sizes; } @@ -242,10 +256,18 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config) StreamConfiguration &cfg = config->at(0); int ret; - /* The scaler hardcodes a x3 scale-up ratio. */ + /* + * Prior to v5.16, the scaler hardcodes a x3 scale-up ratio. For newer + * kernels, use a sensor resolution of 1920x1080 and let the scaler + * produce the requested stream size. + */ + Size sensorSize{ 1920, 1080 }; + if (data->media_->version() < KERNEL_VERSION(5, 16, 0)) + sensorSize = { cfg.size.width / 3, cfg.size.height / 3 }; + V4L2SubdeviceFormat subformat = {}; - subformat.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8; - subformat.size = { cfg.size.width / 3, cfg.size.height / 3 }; + subformat.code = MEDIA_BUS_FMT_SGRBG8_1X8; + subformat.size = sensorSize; ret = data->sensor_->setFormat(&subformat); if (ret) @@ -255,7 +277,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config) if (ret) return ret; - subformat.mbus_code = pixelformats.find(cfg.pixelFormat)->second; + subformat.code = pixelformats.find(cfg.pixelFormat)->second; ret = data->debayer_->setFormat(1, &subformat); if (ret) return ret; @@ -293,7 +315,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config) * vimc driver will fail pipeline validation. */ format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8); - format.size = { cfg.size.width / 3, cfg.size.height / 3 }; + format.size = sensorSize; ret = data->raw_->setFormat(&format); if (ret) @@ -398,7 +420,7 @@ int PipelineHandlerVimc::processControls(VimcCameraData *data, Request *request) continue; } - int32_t value = lroundf(it.second.get<float>() * 128 + offset); + int32_t value = std::lround(it.second.get<float>() * 128 + offset); controls.set(cid, std::clamp(value, 0, 255)); } @@ -470,7 +492,7 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator) return false; } - data->ipa_->paramsBufferReady.connect(data.get(), &VimcCameraData::paramsBufferReady); + data->ipa_->paramsComputed.connect(data.get(), &VimcCameraData::paramsComputed); std::string conf = data->ipa_->configurationFile("vimc.conf"); Flags<ipa::vimc::TestFlag> inFlags = ipa::vimc::TestFlag::Flag2; @@ -510,10 +532,9 @@ int VimcCameraData::init() return ret; /* Create and open the camera sensor, debayer, scaler and video device. */ - sensor_ = std::make_unique<CameraSensor>(media_->getEntityByName("Sensor B")); - ret = sensor_->init(); - if (ret) - return ret; + sensor_ = CameraSensorFactoryBase::create(media_->getEntityByName("Sensor B")); + if (!sensor_) + return -ENODEV; debayer_ = V4L2Subdevice::fromEntityName(media_, "Debayer B"); if (debayer_->open()) @@ -527,7 +548,7 @@ int VimcCameraData::init() if (video_->open()) return -ENODEV; - video_->bufferReady.connect(this, &VimcCameraData::bufferReady); + video_->bufferReady.connect(this, &VimcCameraData::imageBufferReady); raw_ = V4L2VideoDevice::fromEntityName(media_, "Raw Capture 1"); if (raw_->open()) @@ -575,7 +596,7 @@ int VimcCameraData::init() return 0; } -void VimcCameraData::bufferReady(FrameBuffer *buffer) +void VimcCameraData::imageBufferReady(FrameBuffer *buffer) { PipelineHandlerVimc *pipe = static_cast<PipelineHandlerVimc *>(this->pipe()); @@ -600,7 +621,7 @@ void VimcCameraData::bufferReady(FrameBuffer *buffer) pipe->completeBuffer(request, buffer); pipe->completeRequest(request); - ipa_->fillParamsBuffer(request->sequence(), mockIPABufs_[0]->cookie()); + ipa_->computeParams(request->sequence(), mockIPABufs_[0]->cookie()); } int VimcCameraData::allocateMockIPABuffers() @@ -618,11 +639,11 @@ int VimcCameraData::allocateMockIPABuffers() return video_->exportBuffers(kBufCount, &mockIPABufs_); } -void VimcCameraData::paramsBufferReady([[maybe_unused]] unsigned int id, - [[maybe_unused]] const Flags<ipa::vimc::TestFlag> flags) +void VimcCameraData::paramsComputed([[maybe_unused]] unsigned int id, + [[maybe_unused]] const Flags<ipa::vimc::TestFlag> flags) { } -REGISTER_PIPELINE_HANDLER(PipelineHandlerVimc) +REGISTER_PIPELINE_HANDLER(PipelineHandlerVimc, "vimc") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md new file mode 100644 index 00000000..a9f39c15 --- /dev/null +++ b/src/libcamera/pipeline/virtual/README.md @@ -0,0 +1,65 @@ +# Virtual Pipeline Handler + +Virtual pipeline handler emulates fake external camera(s) for testing. + +## Parse config file and register cameras + +- A sample config file is located at `src/libcamera/pipeline/virtual/data/virtual.yaml`. +- If libcamera is installed, the config file should be installed at + `share/libcamera/pipeline/virtual/virtual.yaml`. + +### Config File Format +The config file contains the information about cameras' properties to register. +The config file should be a yaml file with dictionary of the cameraIds +associated with their properties as top level. The default value will be applied +when any property is empty. + +Each camera block is a dictionary, containing the following keys: +- `supported_formats` (list of `VirtualCameraData::Resolution`, optional): + List of supported resolution and frame rates of the emulated camera + - `width` (`unsigned int`, default=1920): Width of the window resolution. + This needs to be even. + - `height` (`unsigned int`, default=1080): Height of the window resolution. + - `frame_rates` (list of `int`, default=`[30,60]` ): Range of the frame + rate (per second). If the list contains one value, it's the lower bound + and the upper bound. If the list contains two values, the first is the + lower bound and the second is the upper bound. No other number of values + is allowed. +- `test_pattern` (`string`): Which test pattern to use as frames. The options + are "bars", "lines". Cannot be set with `frames`. + - The test patterns are "bars" which means color bars, and "lines" which means + diagonal lines. +- `frames` (dictionary): + - `path` (`string`): Path to an image, or path to a directory of a series of + images. Cannot be set with `test_pattern`. + - The path to an image has ".jpg" extension. + - The path to a directory ends with "/". The name of the images in the + directory are "{n}.jpg" with {n} is the sequence of images starting with 0. +- `location` (`string`, default="front"): The location of the camera. Support + "CameraLocationFront", "CameraLocationBack", and "CameraLocationExternal". +- `model` (`string`, default="Unknown"): The model name of the camera. + +Check `data/virtual.yaml` as the sample config file. + +### Implementation + +`Parser` class provides methods to parse the config file to register cameras +in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in +Virtual Pipeline Handler. + +This is the procedure of the Parser class: +1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`. + - Parse the top level of config file which are the camera ids and look into + each camera properties. +2. For each camera, `parseCameraConfigData()` returns a camera with the configuration. + - The methods in the next step fill the data with the pointer to the Camera object. + - If the config file contains invalid configuration, this method returns + nullptr. The camera will be skipped. +3. Parse each property and register the data. + - `parseSupportedFormats()`: Parses `supported_formats` in the config, which + contains resolutions and frame rates. + - `parseFrameGenerator()`: Parses `test_pattern` or `frames` in the config. + - `parseLocation()`: Parses `location` in the config. + - `parseModel()`: Parses `model` in the config. +4. Back to `parseConfigFile()` and append the camera configuration. +5. Returns a list of camera configurations. diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp new file mode 100644 index 00000000..1d3d9ba8 --- /dev/null +++ b/src/libcamera/pipeline/virtual/config_parser.cpp @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Virtual cameras helper to parse config file + */ + +#include "config_parser.h" + +#include <string.h> +#include <utility> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> +#include <libcamera/property_ids.h> + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +std::vector<std::unique_ptr<VirtualCameraData>> +ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe) +{ + std::vector<std::unique_ptr<VirtualCameraData>> configurations; + + std::unique_ptr<YamlObject> cameras = YamlParser::parse(file); + if (!cameras) { + LOG(Virtual, Error) << "Failed to parse config file."; + return configurations; + } + + if (!cameras->isDictionary()) { + LOG(Virtual, Error) << "Config file is not a dictionary at the top level."; + return configurations; + } + + /* Look into the configuration of each camera */ + for (const auto &[cameraId, cameraConfigData] : cameras->asDict()) { + std::unique_ptr<VirtualCameraData> data = + parseCameraConfigData(cameraConfigData, pipe); + /* Parse configData to data */ + if (!data) { + /* Skip the camera if it has invalid config */ + LOG(Virtual, Error) << "Failed to parse config of the camera: " + << cameraId; + continue; + } + + data->config_.id = cameraId; + ControlInfoMap::Map controls; + /* \todo Check which resolution's frame rate to be reported */ + controls[&controls::FrameDurationLimits] = + ControlInfo(1000000 / data->config_.resolutions[0].frameRates[1], + 1000000 / data->config_.resolutions[0].frameRates[0]); + + std::vector<ControlValue> supportedFaceDetectModes{ + static_cast<int32_t>(controls::draft::FaceDetectModeOff), + }; + controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes); + + data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + configurations.push_back(std::move(data)); + } + + return configurations; +} + +std::unique_ptr<VirtualCameraData> +ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData, + PipelineHandler *pipe) +{ + std::vector<VirtualCameraData::Resolution> resolutions; + if (parseSupportedFormats(cameraConfigData, &resolutions)) + return nullptr; + + std::unique_ptr<VirtualCameraData> data = + std::make_unique<VirtualCameraData>(pipe, resolutions); + + if (parseFrameGenerator(cameraConfigData, data.get())) + return nullptr; + + if (parseLocation(cameraConfigData, data.get())) + return nullptr; + + if (parseModel(cameraConfigData, data.get())) + return nullptr; + + return data; +} + +int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData, + std::vector<VirtualCameraData::Resolution> *resolutions) +{ + if (cameraConfigData.contains("supported_formats")) { + const YamlObject &supportedResolutions = cameraConfigData["supported_formats"]; + + for (const YamlObject &supportedResolution : supportedResolutions.asList()) { + unsigned int width = supportedResolution["width"].get<unsigned int>(1920); + unsigned int height = supportedResolution["height"].get<unsigned int>(1080); + if (width == 0 || height == 0) { + LOG(Virtual, Error) << "Invalid width or/and height"; + return -EINVAL; + } + if (width % 2 != 0) { + LOG(Virtual, Error) << "Invalid width: width needs to be even"; + return -EINVAL; + } + + std::vector<int64_t> frameRates; + if (supportedResolution.contains("frame_rates")) { + auto frameRatesList = + supportedResolution["frame_rates"].getList<int>(); + if (!frameRatesList || (frameRatesList->size() != 1 && + frameRatesList->size() != 2)) { + LOG(Virtual, Error) << "Invalid frame_rates: either one or two values"; + return -EINVAL; + } + + if (frameRatesList->size() == 2 && + frameRatesList.value()[0] > frameRatesList.value()[1]) { + LOG(Virtual, Error) << "frame_rates's first value(lower bound)" + << " is higher than the second value(upper bound)"; + return -EINVAL; + } + /* + * Push the min and max framerates. A + * single rate is duplicated. + */ + frameRates.push_back(frameRatesList.value().front()); + frameRates.push_back(frameRatesList.value().back()); + } else { + frameRates.push_back(30); + frameRates.push_back(60); + } + + resolutions->emplace_back( + VirtualCameraData::Resolution{ Size{ width, height }, + frameRates }); + } + } else { + resolutions->emplace_back( + VirtualCameraData::Resolution{ Size{ 1920, 1080 }, + { 30, 60 } }); + } + + return 0; +} + +int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + const std::string testPatternKey = "test_pattern"; + const std::string framesKey = "frames"; + if (cameraConfigData.contains(testPatternKey)) { + if (cameraConfigData.contains(framesKey)) { + LOG(Virtual, Error) << "A camera should use either " + << testPatternKey << " or " << framesKey; + return -EINVAL; + } + + auto testPattern = cameraConfigData[testPatternKey].get<std::string>(""); + + if (testPattern == "bars") { + data->config_.frame = TestPattern::ColorBars; + } else if (testPattern == "lines") { + data->config_.frame = TestPattern::DiagonalLines; + } else { + LOG(Virtual, Debug) << "Test pattern: " << testPattern + << " is not supported"; + return -EINVAL; + } + + return 0; + } + + const YamlObject &frames = cameraConfigData[framesKey]; + + /* When there is no frames provided in the config file, use color bar test pattern */ + if (!frames) { + data->config_.frame = TestPattern::ColorBars; + return 0; + } + + if (!frames.isDictionary()) { + LOG(Virtual, Error) << "'frames' is not a dictionary."; + return -EINVAL; + } + + auto path = frames["path"].get<std::string>(); + + if (!path) { + LOG(Virtual, Error) << "Test pattern or path should be specified."; + return -EINVAL; + } + + std::vector<std::filesystem::path> files; + + switch (std::filesystem::symlink_status(*path).type()) { + case std::filesystem::file_type::regular: + files.push_back(*path); + break; + + case std::filesystem::file_type::directory: + for (const auto &dentry : std::filesystem::directory_iterator{ *path }) { + if (dentry.is_regular_file()) + files.push_back(dentry.path()); + } + + std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) { + return ::strverscmp(a.c_str(), b.c_str()) < 0; + }); + + if (files.empty()) { + LOG(Virtual, Error) << "Directory has no files: " << *path; + return -EINVAL; + } + break; + + default: + LOG(Virtual, Error) << "Frame: " << *path << " is not supported"; + return -EINVAL; + } + + data->config_.frame = ImageFrames{ std::move(files) }; + + return 0; +} + +int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + /* Default value is properties::CameraLocationFront */ + int32_t location = properties::CameraLocationFront; + + if (auto l = cameraConfigData["location"].get<std::string>()) { + auto it = properties::LocationNameValueMap.find(*l); + if (it == properties::LocationNameValueMap.end()) { + LOG(Virtual, Error) + << "location: " << *l << " is not supported"; + return -EINVAL; + } + + location = it->second; + } + + data->properties_.set(properties::Location, location); + + return 0; +} + +int ConfigParser::parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string model = cameraConfigData["model"].get<std::string>("Unknown"); + + data->properties_.set(properties::Model, model); + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/config_parser.h b/src/libcamera/pipeline/virtual/config_parser.h new file mode 100644 index 00000000..d2000de9 --- /dev/null +++ b/src/libcamera/pipeline/virtual/config_parser.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Virtual cameras helper to parse config file + */ + +#pragma once + +#include <memory> +#include <vector> + +#include <libcamera/base/file.h> + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +class ConfigParser +{ +public: + std::vector<std::unique_ptr<VirtualCameraData>> + parseConfigFile(File &file, PipelineHandler *pipe); + +private: + std::unique_ptr<VirtualCameraData> + parseCameraConfigData(const YamlObject &cameraConfigData, PipelineHandler *pipe); + + int parseSupportedFormats(const YamlObject &cameraConfigData, + std::vector<VirtualCameraData::Resolution> *resolutions); + int parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data); +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/data/meson.build b/src/libcamera/pipeline/virtual/data/meson.build new file mode 100644 index 00000000..ce63f9a2 --- /dev/null +++ b/src/libcamera/pipeline/virtual/data/meson.build @@ -0,0 +1,4 @@ +install_data('virtual.yaml', + install_dir : pipeline_data_dir / 'virtual', + install_tag : 'runtime', + rename: 'virtual.yaml.example') diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml new file mode 100644 index 00000000..20471bb9 --- /dev/null +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +"Virtual0": + supported_formats: + - width: 1920 + height: 1080 + frame_rates: + - 30 + - 60 + - width: 1680 + height: 1050 + frame_rates: + - 70 + - 80 + test_pattern: "lines" + location: "CameraLocationFront" + model: "Virtual Video Device" +"Virtual1": + supported_formats: + - width: 800 + height: 600 + frame_rates: + - 60 + test_pattern: "bars" + location: "CameraLocationBack" + model: "Virtual Video Device1" +"Virtual2": + supported_formats: + - width: 400 + height: 300 + test_pattern: "lines" + location: "CameraLocationFront" + model: "Virtual Video Device2" +"Virtual3": + test_pattern: "bars" diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h new file mode 100644 index 00000000..a0658c45 --- /dev/null +++ b/src/libcamera/pipeline/virtual/frame_generator.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Virtual cameras helper to generate frames + */ + +#pragma once + +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + +namespace libcamera { + +class FrameGenerator +{ +public: + virtual ~FrameGenerator() = default; + + virtual void configure(const Size &size) = 0; + + virtual int generateFrame(const Size &size, + const FrameBuffer *buffer) = 0; + +protected: + FrameGenerator() {} +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp new file mode 100644 index 00000000..d1545b5d --- /dev/null +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Derived class of FrameGenerator for generating frames from images + */ + +#include "image_frame_generator.h" + +#include <string> + +#include <libcamera/base/file.h> +#include <libcamera/base/log.h> + +#include <libcamera/framebuffer.h> + +#include "libcamera/internal/mapped_framebuffer.h" + +#include "libyuv/convert.h" +#include "libyuv/scale.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +/* + * Factory function to create an ImageFrameGenerator object. + * Read the images and convert them to buffers in NV12 format. + * Store the pointers to the buffers to a list (imageFrameDatas) + */ +std::unique_ptr<ImageFrameGenerator> +ImageFrameGenerator::create(ImageFrames &imageFrames) +{ + std::unique_ptr<ImageFrameGenerator> imageFrameGenerator = + std::make_unique<ImageFrameGenerator>(); + imageFrameGenerator->imageFrames_ = &imageFrames; + + /* + * For each file in the directory, load the image, + * convert it to NV12, and store the pointer. + */ + for (const auto &path : imageFrames.files) { + File file(path); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + LOG(Virtual, Error) << "Failed to open image file " << file.fileName() + << ": " << strerror(file.error()); + return nullptr; + } + + /* Read the image file to data */ + auto fileSize = file.size(); + auto buffer = std::make_unique<uint8_t[]>(fileSize); + if (file.read({ buffer.get(), static_cast<size_t>(fileSize) }) != fileSize) { + LOG(Virtual, Error) << "Failed to read file " << file.fileName() + << ": " << strerror(file.error()); + return nullptr; + } + + /* Get the width and height of the image */ + int width, height; + if (libyuv::MJPGSize(buffer.get(), fileSize, &width, &height)) { + LOG(Virtual, Error) << "Failed to get the size of the image file: " + << file.fileName(); + return nullptr; + } + + std::unique_ptr<uint8_t[]> dstY = + std::make_unique<uint8_t[]>(width * height); + std::unique_ptr<uint8_t[]> dstUV = + std::make_unique<uint8_t[]>(width * height / 2); + int ret = libyuv::MJPGToNV12(buffer.get(), fileSize, + dstY.get(), width, dstUV.get(), + width, width, height, width, height); + if (ret != 0) + LOG(Virtual, Error) << "MJPGToNV12() failed with " << ret; + + imageFrameGenerator->imageFrameDatas_.emplace_back( + ImageFrameData{ std::move(dstY), std::move(dstUV), + Size(width, height) }); + } + + ASSERT(!imageFrameGenerator->imageFrameDatas_.empty()); + + return imageFrameGenerator; +} + +/* + * \var ImageFrameGenerator::frameRepeat + * \brief Number of frames to repeat before proceeding to the next frame + */ + +/* Scale the buffers for image frames. */ +void ImageFrameGenerator::configure(const Size &size) +{ + /* Reset the source images to prevent multiple configuration calls */ + scaledFrameDatas_.clear(); + frameIndex_ = 0; + parameter_ = 0; + + for (unsigned int i = 0; i < imageFrameDatas_.size(); i++) { + /* Scale the imageFrameDatas_ to scaledY and scaledUV */ + unsigned int halfSizeWidth = (size.width + 1) / 2; + unsigned int halfSizeHeight = (size.height + 1) / 2; + std::unique_ptr<uint8_t[]> scaledY = + std::make_unique<uint8_t[]>(size.width * size.height); + std::unique_ptr<uint8_t[]> scaledUV = + std::make_unique<uint8_t[]>(halfSizeWidth * halfSizeHeight * 2); + auto &src = imageFrameDatas_[i]; + + /* + * \todo Some platforms might enforce stride due to GPU. + * The width needs to be a multiple of the stride to work + * properly for now. + */ + libyuv::NV12Scale(src.Y.get(), src.size.width, + src.UV.get(), src.size.width, + src.size.width, src.size.height, + scaledY.get(), size.width, scaledUV.get(), size.width, + size.width, size.height, libyuv::FilterMode::kFilterBilinear); + + scaledFrameDatas_.emplace_back( + ImageFrameData{ std::move(scaledY), std::move(scaledUV), size }); + } +} + +int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) +{ + ASSERT(!scaledFrameDatas_.empty()); + + MappedFrameBuffer mappedFrameBuffer(buffer, MappedFrameBuffer::MapFlag::Write); + + const auto &planes = mappedFrameBuffer.planes(); + + /* Loop only around the number of images available */ + frameIndex_ %= imageFrameDatas_.size(); + + /* Write the scaledY and scaledUV to the mapped frame buffer */ + libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, + scaledFrameDatas_[frameIndex_].UV.get(), size.width, planes[0].begin(), + size.width, planes[1].begin(), size.width, + size.width, size.height); + + /* Proceed to the next image every 4 frames */ + /* \todo Consider setting the frameRepeat in the config file */ + parameter_++; + if (parameter_ % frameRepeat == 0) + frameIndex_++; + + return 0; +} + +/* + * \var ImageFrameGenerator::imageFrameDatas_ + * \brief List of pointers to the not scaled image buffers + */ + +/* + * \var ImageFrameGenerator::scaledFrameDatas_ + * \brief List of pointers to the scaled image buffers + */ + +/* + * \var ImageFrameGenerator::imageFrames_ + * \brief Pointer to the imageFrames_ in VirtualCameraData + */ + +/* + * \var ImageFrameGenerator::parameter_ + * \brief Speed parameter. Change to the next image every parameter_ frames + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h new file mode 100644 index 00000000..42a077ba --- /dev/null +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Derived class of FrameGenerator for generating frames from images + */ + +#pragma once + +#include <filesystem> +#include <memory> +#include <stdint.h> +#include <sys/types.h> +#include <vector> + +#include "frame_generator.h" + +namespace libcamera { + +/* Frame configuration provided by the config file */ +struct ImageFrames { + std::vector<std::filesystem::path> files; +}; + +class ImageFrameGenerator : public FrameGenerator +{ +public: + static std::unique_ptr<ImageFrameGenerator> create(ImageFrames &imageFrames); + +private: + static constexpr unsigned int frameRepeat = 4; + + struct ImageFrameData { + std::unique_ptr<uint8_t[]> Y; + std::unique_ptr<uint8_t[]> UV; + Size size; + }; + + void configure(const Size &size) override; + int generateFrame(const Size &size, const FrameBuffer *buffer) override; + + std::vector<ImageFrameData> imageFrameDatas_; + std::vector<ImageFrameData> scaledFrameDatas_; + ImageFrames *imageFrames_; + unsigned int frameIndex_; + unsigned int parameter_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build new file mode 100644 index 00000000..c8434593 --- /dev/null +++ b/src/libcamera/pipeline/virtual/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_sources += files([ + 'config_parser.cpp', + 'image_frame_generator.cpp', + 'test_pattern_generator.cpp', + 'virtual.cpp', +]) + +libjpeg = dependency('libjpeg', required : true) + +libcamera_deps += [libyuv_dep] +libcamera_deps += [libjpeg] + +subdir('data') diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp new file mode 100644 index 00000000..745be83b --- /dev/null +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Derived class of FrameGenerator for generating test patterns + */ + +#include "test_pattern_generator.h" + +#include <string.h> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/mapped_framebuffer.h" + +#include <libyuv/convert_from_argb.h> + +namespace { + +template<size_t SampleSize> +void rotateLeft1Column(const libcamera::Size &size, uint8_t *image) +{ + if (size.width < 2) + return; + + const size_t stride = size.width * SampleSize; + uint8_t first[SampleSize]; + + for (size_t i = 0; i < size.height; i++, image += stride) { + memcpy(first, &image[0], SampleSize); + memmove(&image[0], &image[SampleSize], stride - SampleSize); + memcpy(&image[stride - SampleSize], first, SampleSize); + } +} + +} /* namespace */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +static const unsigned int kARGBSize = 4; + +int TestPatternGenerator::generateFrame(const Size &size, + const FrameBuffer *buffer) +{ + MappedFrameBuffer mappedFrameBuffer(buffer, + MappedFrameBuffer::MapFlag::Write); + + const auto &planes = mappedFrameBuffer.planes(); + + rotateLeft1Column<kARGBSize>(size, template_.get()); + + /* Convert the template_ to the frame buffer */ + int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, + planes[0].begin(), size.width, + planes[1].begin(), size.width, + size.width, size.height); + if (ret != 0) + LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; + + return ret; +} + +void ColorBarsGenerator::configure(const Size &size) +{ + constexpr uint8_t kColorBar[8][3] = { + /* R, G, B */ + { 0xff, 0xff, 0xff }, /* White */ + { 0xff, 0xff, 0x00 }, /* Yellow */ + { 0x00, 0xff, 0xff }, /* Cyan */ + { 0x00, 0xff, 0x00 }, /* Green */ + { 0xff, 0x00, 0xff }, /* Magenta */ + { 0xff, 0x00, 0x00 }, /* Red */ + { 0x00, 0x00, 0xff }, /* Blue */ + { 0x00, 0x00, 0x00 }, /* Black */ + }; + + template_ = std::make_unique<uint8_t[]>( + size.width * size.height * kARGBSize); + + unsigned int colorBarWidth = size.width / std::size(kColorBar); + + uint8_t *buf = template_.get(); + for (size_t h = 0; h < size.height; h++) { + for (size_t w = 0; w < size.width; w++) { + /* Repeat when the width is exceed */ + unsigned int index = (w / colorBarWidth) % std::size(kColorBar); + + *buf++ = kColorBar[index][2]; /* B */ + *buf++ = kColorBar[index][1]; /* G */ + *buf++ = kColorBar[index][0]; /* R */ + *buf++ = 0x00; /* A */ + } + } +} + +void DiagonalLinesGenerator::configure(const Size &size) +{ + constexpr uint8_t kColorBar[2][3] = { + /* R, G, B */ + { 0xff, 0xff, 0xff }, /* White */ + { 0x00, 0x00, 0x00 }, /* Black */ + }; + + template_ = std::make_unique<uint8_t[]>( + size.width * size.height * kARGBSize); + + unsigned int lineWidth = size.width / 10; + + uint8_t *buf = template_.get(); + for (size_t h = 0; h < size.height; h++) { + for (size_t w = 0; w < size.width; w++) { + /* Repeat when the width is exceed */ + int index = ((w + h) / lineWidth) % 2; + + *buf++ = kColorBar[index][2]; /* B */ + *buf++ = kColorBar[index][1]; /* G */ + *buf++ = kColorBar[index][0]; /* R */ + *buf++ = 0x00; /* A */ + } + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h new file mode 100644 index 00000000..2a51bd31 --- /dev/null +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Derived class of FrameGenerator for generating test patterns + */ + +#pragma once + +#include <memory> + +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + +#include "frame_generator.h" + +namespace libcamera { + +enum class TestPattern : char { + ColorBars = 0, + DiagonalLines = 1, +}; + +class TestPatternGenerator : public FrameGenerator +{ +public: + int generateFrame(const Size &size, const FrameBuffer *buffer) override; + +protected: + /* Buffer of test pattern template */ + std::unique_ptr<uint8_t[]> template_; +}; + +class ColorBarsGenerator : public TestPatternGenerator +{ +public: + /* Generate a template buffer of the color bar test pattern. */ + void configure(const Size &size) override; +}; + +class DiagonalLinesGenerator : public TestPatternGenerator +{ +public: + /* Generate a template buffer of the diagonal lines test pattern. */ + void configure(const Size &size) override; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp new file mode 100644 index 00000000..049ebcba --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Pipeline handler for virtual cameras + */ + +#include "virtual.h" + +#include <algorithm> +#include <array> +#include <chrono> +#include <errno.h> +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <stdint.h> +#include <string> +#include <time.h> +#include <utility> +#include <vector> + +#include <libcamera/base/flags.h> +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> +#include <libcamera/formats.h> +#include <libcamera/pixel_format.h> +#include <libcamera/property_ids.h> +#include <libcamera/request.h> + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/formats.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "pipeline/virtual/config_parser.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Virtual) + +namespace { + +uint64_t currentTimestamp() +{ + const auto now = std::chrono::steady_clock::now(); + auto nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>( + now.time_since_epoch()); + + return nsecs.count(); +} + +} /* namespace */ + +template<class... Ts> +struct overloaded : Ts... { + using Ts::operator()...; +}; +template<class... Ts> +overloaded(Ts...) -> overloaded<Ts...>; + +class VirtualCameraConfiguration : public CameraConfiguration +{ +public: + static constexpr unsigned int kBufferCount = 4; + + VirtualCameraConfiguration(VirtualCameraData *data); + + Status validate() override; + +private: + const VirtualCameraData *data_; +}; + +class PipelineHandlerVirtual : public PipelineHandler +{ +public: + PipelineHandlerVirtual(CameraManager *manager); + ~PipelineHandlerVirtual(); + + std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera, + Span<const StreamRole> roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; + + int start(Camera *camera, const ControlList *controls) override; + void stopDevice(Camera *camera) override; + + int queueRequestDevice(Camera *camera, Request *request) override; + + bool match(DeviceEnumerator *enumerator) override; + +private: + static bool created_; + + VirtualCameraData *cameraData(Camera *camera) + { + return static_cast<VirtualCameraData *>(camera->_d()); + } + + bool initFrameGenerator(Camera *camera); + + DmaBufAllocator dmaBufAllocator_; + + bool resetCreated_ = false; +}; + +VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, + const std::vector<Resolution> &supportedResolutions) + : Camera::Private(pipe) +{ + config_.resolutions = supportedResolutions; + for (const auto &resolution : config_.resolutions) { + if (config_.minResolutionSize.isNull() || config_.minResolutionSize > resolution.size) + config_.minResolutionSize = resolution.size; + + config_.maxResolutionSize = std::max(config_.maxResolutionSize, resolution.size); + } + + properties_.set(properties::PixelArrayActiveAreas, + { Rectangle(config_.maxResolutionSize) }); + + /* \todo Support multiple streams and pass multi_stream_test */ + streamConfigs_.resize(kMaxStream); +} + +VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data) + : CameraConfiguration(), data_(data) +{ +} + +CameraConfiguration::Status VirtualCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) { + LOG(Virtual, Error) << "Empty config"; + return Invalid; + } + + /* Only one stream is supported */ + if (config_.size() > VirtualCameraData::kMaxStream) { + config_.resize(VirtualCameraData::kMaxStream); + status = Adjusted; + } + + for (StreamConfiguration &cfg : config_) { + bool adjusted = false; + bool found = false; + for (const auto &resolution : data_->config_.resolutions) { + if (resolution.size.width == cfg.size.width && + resolution.size.height == cfg.size.height) { + found = true; + break; + } + } + + if (!found) { + /* + * \todo It's a pipeline's decision to choose a + * resolution when the exact one is not supported. + * Defining the default logic in PipelineHandler to + * find the closest resolution would be nice. + */ + cfg.size = data_->config_.maxResolutionSize; + status = Adjusted; + adjusted = true; + } + + if (cfg.pixelFormat != formats::NV12) { + cfg.pixelFormat = formats::NV12; + status = Adjusted; + adjusted = true; + } + + if (adjusted) + LOG(Virtual, Info) + << "Stream configuration adjusted to " << cfg.toString(); + + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); + cfg.stride = info.stride(cfg.size.width, 0, 1); + cfg.frameSize = info.frameSize(cfg.size, 1); + + cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; + } + + return status; +} + +/* static */ +bool PipelineHandlerVirtual::created_ = false; + +PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager) + : PipelineHandler(manager), + dmaBufAllocator_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap | + DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | + DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) +{ +} + +PipelineHandlerVirtual::~PipelineHandlerVirtual() +{ + if (resetCreated_) + created_ = false; +} + +std::unique_ptr<CameraConfiguration> +PipelineHandlerVirtual::generateConfiguration(Camera *camera, + Span<const StreamRole> roles) +{ + VirtualCameraData *data = cameraData(camera); + auto config = std::make_unique<VirtualCameraConfiguration>(data); + + if (roles.empty()) + return config; + + for (const StreamRole role : roles) { + switch (role) { + case StreamRole::StillCapture: + case StreamRole::VideoRecording: + case StreamRole::Viewfinder: + break; + + case StreamRole::Raw: + default: + LOG(Virtual, Error) + << "Requested stream role not supported: " << role; + return {}; + } + + std::map<PixelFormat, std::vector<SizeRange>> streamFormats; + PixelFormat pixelFormat = formats::NV12; + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, + data->config_.maxResolutionSize } }; + StreamFormats formats(streamFormats); + StreamConfiguration cfg(formats); + cfg.pixelFormat = pixelFormat; + cfg.size = data->config_.maxResolutionSize; + cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; + + config->addConfiguration(cfg); + } + + ASSERT(config->validate() != CameraConfiguration::Invalid); + + return config; +} + +int PipelineHandlerVirtual::configure(Camera *camera, + CameraConfiguration *config) +{ + VirtualCameraData *data = cameraData(camera); + for (auto [i, c] : utils::enumerate(*config)) { + c.setStream(&data->streamConfigs_[i].stream); + /* Start reading the images/generating test patterns */ + data->streamConfigs_[i].frameGenerator->configure(c.size); + } + + return 0; +} + +int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera, + Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) +{ + if (!dmaBufAllocator_.isValid()) + return -ENOBUFS; + + const StreamConfiguration &config = stream->configuration(); + const PixelFormatInfo &info = PixelFormatInfo::info(config.pixelFormat); + + std::vector<unsigned int> planeSizes; + for (size_t i = 0; i < info.numPlanes(); ++i) + planeSizes.push_back(info.planeSize(config.size, i)); + + return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); +} + +int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, + [[maybe_unused]] const ControlList *controls) +{ + VirtualCameraData *data = cameraData(camera); + + for (auto &s : data->streamConfigs_) + s.seq = 0; + + return 0; +} + +void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) +{ +} + +int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, + Request *request) +{ + VirtualCameraData *data = cameraData(camera); + const auto timestamp = currentTimestamp(); + + for (auto const &[stream, buffer] : request->buffers()) { + bool found = false; + /* map buffer and fill test patterns */ + for (auto &streamConfig : data->streamConfigs_) { + if (stream == &streamConfig.stream) { + FrameMetadata &fmd = buffer->_d()->metadata(); + + fmd.status = FrameMetadata::Status::FrameSuccess; + fmd.sequence = streamConfig.seq++; + fmd.timestamp = timestamp; + + for (const auto [i, p] : utils::enumerate(buffer->planes())) + fmd.planes()[i].bytesused = p.length; + + found = true; + + if (streamConfig.frameGenerator->generateFrame( + stream->configuration().size, buffer)) + fmd.status = FrameMetadata::Status::FrameError; + + completeBuffer(request, buffer); + break; + } + } + ASSERT(found); + } + + request->metadata().set(controls::SensorTimestamp, timestamp); + completeRequest(request); + + return 0; +} + +bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator) +{ + if (created_) + return false; + + created_ = true; + + std::string configFile = configurationFile("virtual", "virtual.yaml", true); + if (configFile.empty()) { + LOG(Virtual, Debug) + << "Configuration file not found, skipping virtual cameras"; + return false; + } + + File file(configFile); + if (!file.open(File::OpenModeFlag::ReadOnly)) { + LOG(Virtual, Error) + << "Failed to open config file `" << file.fileName() << "`"; + return false; + } + + ConfigParser parser; + auto configData = parser.parseConfigFile(file, this); + if (configData.size() == 0) { + LOG(Virtual, Error) << "Failed to parse any cameras from the config file: " + << file.fileName(); + return false; + } + + /* Configure and register cameras with configData */ + for (auto &data : configData) { + std::set<Stream *> streams; + for (auto &streamConfig : data->streamConfigs_) + streams.insert(&streamConfig.stream); + std::string id = data->config_.id; + std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams); + + if (!initFrameGenerator(camera.get())) { + LOG(Virtual, Error) << "Failed to initialize frame " + << "generator for camera: " << id; + continue; + } + + registerCamera(std::move(camera)); + } + + resetCreated_ = true; + + return true; +} + +bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera) +{ + auto data = cameraData(camera); + auto &frame = data->config_.frame; + std::visit(overloaded{ + [&](TestPattern &testPattern) { + for (auto &streamConfig : data->streamConfigs_) { + if (testPattern == TestPattern::DiagonalLines) + streamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>(); + else + streamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>(); + } + }, + [&](ImageFrames &imageFrames) { + for (auto &streamConfig : data->streamConfigs_) + streamConfig.frameGenerator = ImageFrameGenerator::create(imageFrames); + } }, + frame); + + for (auto &streamConfig : data->streamConfigs_) + if (!streamConfig.frameGenerator) + return false; + + return true; +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual") + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h new file mode 100644 index 00000000..683cb82b --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * Pipeline handler for virtual cameras + */ + +#pragma once + +#include <string> +#include <variant> +#include <vector> + +#include <libcamera/geometry.h> +#include <libcamera/stream.h> + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/pipeline_handler.h" + +#include "frame_generator.h" +#include "image_frame_generator.h" +#include "test_pattern_generator.h" + +namespace libcamera { + +using VirtualFrame = std::variant<TestPattern, ImageFrames>; + +class VirtualCameraData : public Camera::Private +{ +public: + const static unsigned int kMaxStream = 3; + + struct Resolution { + Size size; + std::vector<int64_t> frameRates; + }; + struct StreamConfig { + Stream stream; + std::unique_ptr<FrameGenerator> frameGenerator; + unsigned int seq = 0; + }; + /* The config file is parsed to the Configuration struct */ + struct Configuration { + std::string id; + std::vector<Resolution> resolutions; + VirtualFrame frame; + + Size maxResolutionSize; + Size minResolutionSize; + }; + + VirtualCameraData(PipelineHandler *pipe, + const std::vector<Resolution> &supportedResolutions); + + ~VirtualCameraData() = default; + + Configuration config_; + + std::vector<StreamConfig> streamConfigs_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 9c74c6cf..d84dff3c 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * pipeline_handler.cpp - Pipeline handler infrastructure + * Pipeline handler infrastructure */ #include "libcamera/internal/pipeline_handler.h" @@ -22,7 +22,6 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_manager.h" #include "libcamera/internal/device_enumerator.h" -#include "libcamera/internal/framebuffer.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/request.h" #include "libcamera/internal/tracepoints.h" @@ -75,7 +74,7 @@ PipelineHandler::PipelineHandler(CameraManager *manager) PipelineHandler::~PipelineHandler() { - for (std::shared_ptr<MediaDevice> media : mediaDevices_) + for (std::shared_ptr<MediaDevice> &media : mediaDevices_) media->release(); } @@ -157,26 +156,28 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator, * Pipeline handlers shall not call this function directly as the Camera class * handles access internally. * - * \context This function is \threadsafe. + * \context This function is called from the CameraManager thread. * * \return True if the pipeline handler was acquired, false if another process * has already acquired it * \sa release() */ -bool PipelineHandler::acquire() +bool PipelineHandler::acquire(Camera *camera) { - MutexLocker locker(lock_); - - if (useCount_) { - ++useCount_; - return true; + if (useCount_ == 0) { + for (std::shared_ptr<MediaDevice> &media : mediaDevices_) { + if (!media->lock()) { + unlockMediaDevices(); + return false; + } + } } - for (std::shared_ptr<MediaDevice> &media : mediaDevices_) { - if (!media->lock()) { + if (!acquireDevice(camera)) { + if (useCount_ == 0) unlockMediaDevices(); - return false; - } + + return false; } ++useCount_; @@ -195,29 +196,60 @@ bool PipelineHandler::acquire() * Pipeline handlers shall not call this function directly as the Camera class * handles access internally. * - * \context This function is \threadsafe. + * \context This function is called from the CameraManager thread. * * \sa acquire() */ void PipelineHandler::release(Camera *camera) { - MutexLocker locker(lock_); - ASSERT(useCount_); - unlockMediaDevices(); - releaseDevice(camera); + if (useCount_ == 1) + unlockMediaDevices(); + --useCount_; } /** + * \brief Acquire resources associated with this camera + * \param[in] camera The camera for which to acquire resources + * + * Pipeline handlers may override this in order to get resources such as opening + * devices and allocating buffers when a camera is acquired. + * + * This is used by the uvcvideo pipeline handler to delay opening /dev/video# + * until the camera is acquired to avoid excess power consumption. The delayed + * opening of /dev/video# is a special case because the kernel uvcvideo driver + * powers on the USB device as soon as /dev/video# is opened. This behavior + * should *not* be copied by other pipeline handlers. + * + * \context This function is called from the CameraManager thread. + * + * \return True on success, false on failure + * \sa releaseDevice() + */ +bool PipelineHandler::acquireDevice([[maybe_unused]] Camera *camera) +{ + return true; +} + +/** * \brief Release resources associated with this camera * \param[in] camera The camera for which to release resources * * Pipeline handlers may override this in order to perform cleanup operations * when a camera is released, such as freeing memory. + * + * This is called once for every camera that is released. If there are resources + * shared by multiple cameras then the pipeline handler must take care to not + * release them until releaseDevice() has been called for all previously + * acquired cameras. + * + * \context This function is called from the CameraManager thread. + * + * \sa acquireDevice() */ void PipelineHandler::releaseDevice([[maybe_unused]] Camera *camera) { @@ -335,9 +367,7 @@ void PipelineHandler::stop(Camera *camera) while (!waitingRequests_.empty()) { Request *request = waitingRequests_.front(); waitingRequests_.pop(); - - request->_d()->cancel(); - completeRequest(request); + cancelRequest(request); } /* Make sure no requests are pending. */ @@ -438,10 +468,8 @@ void PipelineHandler::doQueueRequest(Request *request) } int ret = queueRequestDevice(camera, request); - if (ret) { - request->_d()->cancel(); - completeRequest(request); - } + if (ret) + cancelRequest(request); } /** @@ -537,9 +565,23 @@ void PipelineHandler::completeRequest(Request *request) } /** + * \brief Cancel request and signal its completion + * \param[in] request The request to cancel + * + * This function cancels and completes the request. The same rules as for + * completeRequest() apply. + */ +void PipelineHandler::cancelRequest(Request *request) +{ + request->_d()->cancel(); + completeRequest(request); +} + +/** * \brief Retrieve the absolute path to a platform configuration file * \param[in] subdir The pipeline handler specific subdirectory name * \param[in] name The configuration file name + * \param[in] silent Disable error messages * * This function locates a named platform configuration file and returns * its absolute path to the pipeline handler. It searches the following @@ -555,7 +597,8 @@ void PipelineHandler::completeRequest(Request *request) * string if no configuration file can be found */ std::string PipelineHandler::configurationFile(const std::string &subdir, - const std::string &name) const + const std::string &name, + bool silent) const { std::string confPath; struct stat statbuf; @@ -585,9 +628,11 @@ std::string PipelineHandler::configurationFile(const std::string &subdir, if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG) return confPath; - LOG(Pipeline, Error) - << "Configuration file '" << confPath - << "' not found for pipeline handler '" << PipelineHandler::name() << "'"; + if (!silent) + LOG(Pipeline, Error) + << "Configuration file '" << confPath + << "' not found for pipeline handler '" + << PipelineHandler::name() << "'"; return std::string(); } @@ -605,9 +650,14 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera) { cameras_.push_back(camera); - if (mediaDevices_.empty()) - LOG(Pipeline, Fatal) - << "Registering camera with no media devices!"; + if (mediaDevices_.empty()) { + /* + * For virtual devices with no MediaDevice, there are no system + * devices to register. + */ + manager_->_d()->addCamera(std::move(camera)); + return; + } /* * Walk the entity list and map the devnums of all capture video nodes @@ -649,7 +699,7 @@ void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera) */ void PipelineHandler::hotplugMediaDevice(MediaDevice *media) { - media->disconnected.connect(this, [=]() { mediaDeviceDisconnected(media); }); + media->disconnected.connect(this, [this, media] { mediaDeviceDisconnected(media); }); } /** @@ -720,6 +770,13 @@ void PipelineHandler::disconnect() */ /** + * \fn PipelineHandler::cameraManager() const + * \brief Retrieve the CameraManager that this pipeline handler belongs to + * \context This function is \threadsafe. + * \return The CameraManager for this pipeline handler + */ + +/** * \class PipelineHandlerFactoryBase * \brief Base class for pipeline handler factories * @@ -795,6 +852,28 @@ std::vector<PipelineHandlerFactoryBase *> &PipelineHandlerFactoryBase::factories } /** + * \brief Return the factory for the pipeline handler with name \a name + * \param[in] name The pipeline handler name + * \return The factory of the pipeline with name \a name, or nullptr if not found + */ +const PipelineHandlerFactoryBase *PipelineHandlerFactoryBase::getFactoryByName(const std::string &name) +{ + const std::vector<PipelineHandlerFactoryBase *> &factories = + PipelineHandlerFactoryBase::factories(); + + auto iter = std::find_if(factories.begin(), + factories.end(), + [&name](const PipelineHandlerFactoryBase *f) { + return f->name() == name; + }); + + if (iter != factories.end()) + return *iter; + + return nullptr; +} + +/** * \class PipelineHandlerFactory * \brief Registration of PipelineHandler classes and creation of instances * \tparam _PipelineHandler The pipeline handler class type for this factory @@ -830,6 +909,8 @@ std::vector<PipelineHandlerFactoryBase *> &PipelineHandlerFactoryBase::factories * \def REGISTER_PIPELINE_HANDLER * \brief Register a pipeline handler with the pipeline handler factory * \param[in] handler Class name of PipelineHandler derived class to register + * \param[in] name Name assigned to the pipeline handler, matching the pipeline + * subdirectory name in the source tree. * * Register a PipelineHandler subclass with the factory and make it available to * try and match devices. diff --git a/src/libcamera/pixel_format.cpp b/src/libcamera/pixel_format.cpp index 80c22072..314179a8 100644 --- a/src/libcamera/pixel_format.cpp +++ b/src/libcamera/pixel_format.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * pixel_format.cpp - libcamera Pixel Format + * libcamera Pixel Format */ #include <libcamera/formats.h> diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp index 86a382fb..68fad327 100644 --- a/src/libcamera/process.cpp +++ b/src/libcamera/process.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * process.cpp - Process object + * Process object */ #include "libcamera/internal/process.h" @@ -10,7 +10,6 @@ #include <algorithm> #include <dirent.h> #include <fcntl.h> -#include <iostream> #include <list> #include <signal.h> #include <string.h> @@ -76,7 +75,7 @@ void ProcessManager::sighandler() return; } - for (auto it = processes_.begin(); it != processes_.end(); ) { + for (auto it = processes_.begin(); it != processes_.end();) { Process *process = *it; int wstatus; @@ -189,7 +188,6 @@ const struct sigaction &ProcessManager::oldsa() const return oldsa_; } - /** * \class Process * \brief Process object @@ -271,8 +269,8 @@ int Process::start(const std::string &path, unsigned int len = args.size(); argv[0] = path.c_str(); for (unsigned int i = 0; i < len; i++) - argv[i+1] = args[i].c_str(); - argv[len+1] = nullptr; + argv[i + 1] = args[i].c_str(); + argv[len + 1] = nullptr; execv(path.c_str(), (char **)argv); diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in deleted file mode 100644 index f917e334..00000000 --- a/src/libcamera/property_ids.cpp.in +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * property_ids.cpp : Property ID list - * - * This file is auto-generated. Do not edit. - */ - -#include <libcamera/property_ids.h> - -/** - * \file property_ids.h - * \brief Camera property identifiers - */ - -namespace libcamera { - -/** - * \brief Namespace for libcamera properties - */ -namespace properties { - -${controls_doc} - -/** - * \brief Namespace for libcamera draft properties - */ -namespace draft { - -${draft_controls_doc} - -} /* namespace draft */ - -#ifndef __DOXYGEN__ -/* - * Keep the properties definitions hidden from doxygen as it incorrectly parses - * them as functions. - */ -${controls_def} - -namespace draft { - -${draft_controls_def} - -} /* namespace draft */ -#endif - -/** - * \brief List of all supported libcamera properties - */ -extern const ControlIdMap properties { -${controls_map} -}; - -} /* namespace properties */ - -} /* namespace libcamera */ diff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids_core.yaml index f3556384..834454a4 100644 --- a/src/libcamera/property_ids.yaml +++ b/src/libcamera/property_ids_core.yaml @@ -4,6 +4,7 @@ # %YAML 1.1 --- +vendor: libcamera controls: - Location: type: int32_t @@ -700,37 +701,4 @@ controls: Different cameras may report identical devices. - # ---------------------------------------------------------------------------- - # Draft properties section - - - ColorFilterArrangement: - type: int32_t - draft: true - description: | - The arrangement of color filters on sensor; represents the colors in the - top-left 2x2 section of the sensor, in reading order. Currently - identical to ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT. - enum: - - name: RGGB - value: 0 - description: RGGB Bayer pattern - - name: GRBG - value: 1 - description: GRBG Bayer pattern - - name: GBRG - value: 2 - description: GBRG Bayer pattern - - name: BGGR - value: 3 - description: BGGR Bayer pattern - - name: RGB - value: 4 - description: | - Sensor is not Bayer; output has 3 16-bit values for each pixel, - instead of just 1 16-bit value per pixel. - - name: MONO - value: 5 - description: | - Sensor is not Bayer; output consists of a single colour channel. - ... diff --git a/src/libcamera/property_ids_draft.yaml b/src/libcamera/property_ids_draft.yaml new file mode 100644 index 00000000..62f0e242 --- /dev/null +++ b/src/libcamera/property_ids_draft.yaml @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Copyright (C) 2019, Google Inc. +# +%YAML 1.1 +--- +vendor: draft +controls: + - ColorFilterArrangement: + type: int32_t + vendor: draft + description: | + The arrangement of color filters on sensor; represents the colors in the + top-left 2x2 section of the sensor, in reading order. Currently + identical to ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT. + enum: + - name: RGGB + value: 0 + description: RGGB Bayer pattern + - name: GRBG + value: 1 + description: GRBG Bayer pattern + - name: GBRG + value: 2 + description: GBRG Bayer pattern + - name: BGGR + value: 3 + description: BGGR Bayer pattern + - name: RGB + value: 4 + description: | + Sensor is not Bayer; output has 3 16-bit values for each pixel, + instead of just 1 16-bit value per pixel. + - name: MONO + value: 5 + description: | + Sensor is not Bayer; output consists of a single colour channel. + +... diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build index 00ae5a8f..8bd1b135 100644 --- a/src/libcamera/proxy/meson.build +++ b/src/libcamera/proxy/meson.build @@ -13,7 +13,8 @@ foreach mojom : ipa_mojoms '--libcamera_generate_proxy_cpp', '--libcamera_output_path=@OUTPUT@', './' + '@INPUT@' - ]) + ], + env : py_build_env) - libcamera_sources += proxy + libcamera_internal_sources += proxy endforeach diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build index aa4d9cd7..8c54a2e2 100644 --- a/src/libcamera/proxy/worker/meson.build +++ b/src/libcamera/proxy/worker/meson.build @@ -15,10 +15,10 @@ foreach mojom : ipa_mojoms '--libcamera_generate_proxy_worker', '--libcamera_output_path=@OUTPUT@', './' + '@INPUT@' - ]) + ], + env : py_build_env) - proxy = executable(mojom['name'] + '_ipa_proxy', - [worker, libcamera_generated_ipa_headers], + proxy = executable(mojom['name'] + '_ipa_proxy', worker, install : true, install_dir : proxy_install_dir, dependencies : libcamera_private) diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp index 64dfa234..f1d73a5c 100644 --- a/src/libcamera/pub_key.cpp +++ b/src/libcamera/pub_key.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * pub_key.cpp - Public key signature verification + * Public key signature verification */ #include "libcamera/internal/pub_key.h" diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 949c556f..7f1e11e8 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * request.cpp - Capture request handling + * Capture request handling */ #include "libcamera/internal/request.h" @@ -28,10 +28,17 @@ * \brief Describes a frame capture request to be processed by a camera */ +/** + * \internal + * \file libcamera/internal/request.h + * \brief Internal support for request handling + */ + namespace libcamera { LOG_DEFINE_CATEGORY(Request) +#ifndef __DOXYGEN_PUBLIC__ /** * \class Request::Private * \brief Request private data @@ -300,6 +307,7 @@ void Request::Private::timeout() emitPrepareCompleted(); } +#endif /* __DOXYGEN_PUBLIC__ */ /** * \enum Request::Status @@ -467,16 +475,6 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer, return -EINVAL; } - auto it = bufferMap_.find(stream); - if (it != bufferMap_.end()) { - LOG(Request, Error) << "FrameBuffer already set for stream"; - return -EEXIST; - } - - buffer->_d()->setRequest(this); - _d()->pending_.insert(buffer); - bufferMap_[stream] = buffer; - /* * Make sure the fence has been extracted from the buffer * to avoid waiting on a stale fence. @@ -486,6 +484,15 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer, return -EEXIST; } + auto [it, inserted] = bufferMap_.try_emplace(stream, buffer); + if (!inserted) { + LOG(Request, Error) << "FrameBuffer already set for stream"; + return -EEXIST; + } + + buffer->_d()->setRequest(this); + _d()->pending_.insert(buffer); + if (fence && fence->isValid()) buffer->_d()->setFence(std::move(fence)); diff --git a/src/libcamera/sensor/camera_sensor.cpp b/src/libcamera/sensor/camera_sensor.cpp new file mode 100644 index 00000000..d19b5e2e --- /dev/null +++ b/src/libcamera/sensor/camera_sensor.cpp @@ -0,0 +1,583 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * A camera sensor + */ + +#include "libcamera/internal/camera_sensor.h" + +#include <memory> +#include <vector> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/media_object.h" + +/** + * \file camera_sensor.h + * \brief A camera sensor + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(CameraSensor) + +/** + * \class CameraSensor + * \brief A abstract camera sensor + * + * The CameraSensor class eases handling of sensors for pipeline handlers by + * hiding the details of the kernel API and caching sensor information. + */ + +/** + * \brief Destroy a CameraSensor + */ +CameraSensor::~CameraSensor() = default; + +/** + * \fn CameraSensor::model() + * \brief Retrieve the sensor model name + * + * The sensor model name is a free-formed string that uniquely identifies the + * sensor model. + * + * \return The sensor model name + */ + +/** + * \fn CameraSensor::id() + * \brief Retrieve the sensor ID + * + * The sensor ID is a free-form string that uniquely identifies the sensor in + * the system. The ID satisfies the requirements to be used as a camera ID. + * + * \return The sensor ID + */ + +/** + * \fn CameraSensor::entity() + * \brief Retrieve the sensor media entity + * \return The sensor media entity + */ + +/** + * \fn CameraSensor::device() + * \brief Retrieve the camera sensor device + * \todo Remove this function by integrating DelayedControl with CameraSensor + * \return The camera sensor device + */ + +/** + * \fn CameraSensor::focusLens() + * \brief Retrieve the focus lens controller + * + * \return The focus lens controller. nullptr if no focus lens controller is + * connected to the sensor + */ + +/** + * \fn CameraSensor::mbusCodes() + * \brief Retrieve the media bus codes supported by the camera sensor + * + * Any Bayer formats are listed using the sensor's native Bayer order, + * that is, with the effect of V4L2_CID_HFLIP and V4L2_CID_VFLIP undone + * (where these controls exist). + * + * \return The supported media bus codes sorted in increasing order + */ + +/** + * \fn CameraSensor::sizes() + * \brief Retrieve the supported frame sizes for a media bus code + * \param[in] mbusCode The media bus code for which sizes are requested + * + * \return The supported frame sizes for \a mbusCode sorted in increasing order + */ + +/** + * \fn CameraSensor::resolution() + * \brief Retrieve the camera sensor resolution + * + * The camera sensor resolution is the active pixel area size, clamped to the + * maximum frame size the sensor can produce if it is smaller than the active + * pixel area. + * + * \todo Consider if it desirable to distinguish between the maximum resolution + * the sensor can produce (also including upscaled ones) and the actual pixel + * array size by splitting this function in two. + * + * \return The camera sensor resolution in pixels + */ + +/** + * \fn CameraSensor::getFormat() + * \brief Retrieve the best sensor format for a desired output + * \param[in] mbusCodes The list of acceptable media bus codes + * \param[in] size The desired size + * \param[in] maxSize The maximum size + * + * Media bus codes are selected from \a mbusCodes, which lists all acceptable + * codes in decreasing order of preference. Media bus codes supported by the + * sensor but not listed in \a mbusCodes are ignored. If none of the desired + * codes is supported, it returns an error. + * + * \a size indicates the desired size at the output of the sensor. This function + * selects the best media bus code and size supported by the sensor according + * to the following criteria. + * + * - The desired \a size shall fit in the sensor output size to avoid the need + * to up-scale. + * - The sensor output size shall match the desired aspect ratio to avoid the + * need to crop the field of view. + * - The sensor output size shall be as small as possible to lower the required + * bandwidth. + * - The desired \a size shall be supported by one of the media bus code listed + * in \a mbusCodes. + * - The desired \a size shall fit into the maximum size \a maxSize if it is not + * null. + * + * When multiple media bus codes can produce the same size, the code at the + * lowest position in \a mbusCodes is selected. + * + * The use of this function is optional, as the above criteria may not match the + * needs of all pipeline handlers. Pipeline handlers may implement custom + * sensor format selection when needed. + * + * The returned sensor output format is guaranteed to be acceptable by the + * setFormat() function without any modification. + * + * \return The best sensor output format matching the desired media bus codes + * and size on success, or an empty format otherwise. + */ + +/** + * \fn CameraSensor::setFormat() + * \brief Set the sensor output format + * \param[in] format The desired sensor output format + * \param[in] transform The transform to be applied on the sensor. + * Defaults to Identity. + * + * If flips are writable they are configured according to the desired Transform. + * Transform::Identity always corresponds to H/V flip being disabled if the + * controls are writable. Flips are set before the new format is applied as + * they can effectively change the Bayer pattern ordering. + * + * The ranges of any controls associated with the sensor are also updated. + * + * \return 0 on success or a negative error code otherwise + */ + +/** + * \fn CameraSensor::tryFormat() + * \brief Try the sensor output format + * \param[in] format The desired sensor output format + * + * The ranges of any controls associated with the sensor are not updated. + * + * \todo Add support for Transform by changing the format's Bayer ordering + * before calling subdev_->setFormat(). + * + * \return 0 on success or a negative error code otherwise + */ + +/** + * \fn CameraSensor::applyConfiguration() + * \brief Apply a sensor configuration to the camera sensor + * \param[in] config The sensor configuration + * \param[in] transform The transform to be applied on the sensor. + * Defaults to Identity + * \param[out] sensorFormat Format applied to the sensor (optional) + * + * Apply to the camera sensor the configuration \a config. + * + * \todo The configuration shall be fully populated and if any of the fields + * specified cannot be applied exactly, an error code is returned. + * + * \return 0 if \a config is applied correctly to the camera sensor, a negative + * error code otherwise + */ + +/** + * \brief Retrieve the image source stream + * + * Sensors that produce multiple streams do not guarantee that the image stream + * is always assigned number 0. This function allows callers to retrieve the + * image stream on the sensor's source pad, in order to configure the receiving + * side accordingly. + * + * \return The image source stream + */ +V4L2Subdevice::Stream CameraSensor::imageStream() const +{ + return { 0, 0 }; +} + +/** + * \brief Retrieve the embedded data source stream + * + * Some sensors produce embedded data in a stream separate from the image + * stream. This function indicates if the sensor supports this feature by + * returning the embedded data stream on the sensor's source pad if available, + * or an std::optional<> without a value otheriwse. + * + * \return The embedded data source stream + */ +std::optional<V4L2Subdevice::Stream> CameraSensor::embeddedDataStream() const +{ + return {}; +} + +/** + * \brief Retrieve the format on the embedded data stream + * + * When an embedded data stream is available, this function returns the + * corresponding format on the sensor's source pad. The format may vary with + * the image stream format, and should therefore be retrieved after configuring + * the image stream. + * + * If the sensor doesn't support embedded data, this function returns a + * default-constructed format. + * + * \return The format on the embedded data stream + */ +V4L2SubdeviceFormat CameraSensor::embeddedDataFormat() const +{ + return {}; +} + +/** + * \brief Enable or disable the embedded data stream + * \param[in] enable True to enable the embedded data stream, false to disable it + * + * For sensors that support embedded data, this function enables or disables + * generation of embedded data. Some of such sensors always produce embedded + * data, in which case this function return -EISCONN if the caller attempts to + * disable embedded data. + * + * If the sensor doesn't support embedded data, this function returns 0 when \a + * enable is false, and -ENOSTR otherwise. + * + * \return 0 on success, or a negative error code otherwise + */ +int CameraSensor::setEmbeddedDataEnabled(bool enable) +{ + return enable ? -ENOSTR : 0; +} + +/** + * \fn CameraSensor::properties() + * \brief Retrieve the camera sensor properties + * \return The list of camera sensor properties + */ + +/** + * \fn CameraSensor::sensorInfo() + * \brief Assemble and return the camera sensor info + * \param[out] info The camera sensor info + * + * This function fills \a info with information that describes the camera sensor + * and its current configuration. The information combines static data (such as + * the the sensor model or active pixel array size) and data specific to the + * current sensor configuration (such as the line length and pixel rate). + * + * Sensor information is only available for raw sensors. When called for a YUV + * sensor, this function returns -EINVAL. + * + * \return 0 on success, a negative error code otherwise + */ + +/** + * \fn CameraSensor::computeTransform() + * \brief Compute the Transform that gives the requested \a orientation + * \param[inout] orientation The desired image orientation + * + * This function computes the Transform that the pipeline handler should apply + * to the CameraSensor to obtain the requested \a orientation. + * + * The intended caller of this function is the validate() implementation of + * pipeline handlers, that pass in the application requested + * CameraConfiguration::orientation and obtain a Transform to apply to the + * camera sensor, likely at configure() time. + * + * If the requested \a orientation cannot be obtained, the \a orientation + * parameter is adjusted to report the current image orientation and + * Transform::Identity is returned. + * + * If the requested \a orientation can be obtained, the function computes a + * Transform and does not adjust \a orientation. + * + * Pipeline handlers are expected to verify if \a orientation has been + * adjusted by this function and set the CameraConfiguration::status to + * Adjusted accordingly. + * + * \return A Transform instance that applied to the CameraSensor produces images + * with \a orientation + */ + +/** + * \fn CameraSensor::bayerOrder() + * \brief Compute the Bayer order that results from the given Transform + * \param[in] t The Transform to apply to the sensor + * + * Some sensors change their Bayer order when they are h-flipped or v-flipped. + * This function computes and returns the Bayer order that would result from the + * given transform applied to the sensor. + * + * This function is valid only when the sensor produces raw Bayer formats. + * + * \return The Bayer order produced by the sensor when the Transform is applied + */ + +/** + * \fn CameraSensor::controls() + * \brief Retrieve the supported V4L2 controls and their information + * + * Control information is updated automatically to reflect the current sensor + * configuration when the setFormat() function is called, without invalidating + * any iterator on the ControlInfoMap. + * + * \return A map of the V4L2 controls supported by the sensor + */ + +/** + * \fn CameraSensor::getControls() + * \brief Read V4L2 controls from the sensor + * \param[in] ids The list of controls to read, specified by their ID + * + * This function reads the value of all controls contained in \a ids, and + * returns their values as a ControlList. The control identifiers are defined by + * the V4L2 specification (V4L2_CID_*). + * + * If any control in \a ids is not supported by the device, is disabled (i.e. + * has the V4L2_CTRL_FLAG_DISABLED flag set), or if any other error occurs + * during validation of the requested controls, no control is read and this + * function returns an empty control list. + * + * \sa V4L2Device::getControls() + * + * \return The control values in a ControlList on success, or an empty list on + * error + */ + +/** + * \fn CameraSensor::setControls() + * \brief Write V4L2 controls to the sensor + * \param[in] ctrls The list of controls to write + * + * This function writes the value of all controls contained in \a ctrls, and + * stores the values actually applied to the device in the corresponding \a + * ctrls entry. The control identifiers are defined by the V4L2 specification + * (V4L2_CID_*). + * + * If any control in \a ctrls is not supported by the device, is disabled (i.e. + * has the V4L2_CTRL_FLAG_DISABLED flag set), is read-only, or if any other + * error occurs during validation of the requested controls, no control is + * written and this function returns -EINVAL. + * + * If an error occurs while writing the controls, the index of the first + * control that couldn't be written is returned. All controls below that index + * are written and their values are updated in \a ctrls, while all other + * controls are not written and their values are not changed. + * + * \sa V4L2Device::setControls() + * + * \return 0 on success or an error code otherwise + * \retval -EINVAL One of the control is not supported or not accessible + * \retval i The index of the control that failed + */ + +/** + * \fn CameraSensor::testPatternModes() + * \brief Retrieve all the supported test pattern modes of the camera sensor + * The test pattern mode values correspond to the controls::TestPattern control. + * + * \return The list of test pattern modes + */ + +/** + * \fn CameraSensor::setTestPatternMode() + * \brief Set the test pattern mode for the camera sensor + * \param[in] mode The test pattern mode + * + * The new \a mode is applied to the sensor if it differs from the active test + * pattern mode. Otherwise, this function is a no-op. Setting the same test + * pattern mode for every frame thus incurs no performance penalty. + */ + +/** + * \fn CameraSensor::sensorDelays() + * \brief Fetch the sensor delay values + * + * This function retrieves the delays that the sensor applies to controls. If + * the static properties database doesn't specifiy control delay values for the + * sensor, default delays that may be suitable are returned and a warning is + * logged. + * + * \return The sensor delay values + */ + +/** + * \class CameraSensorFactoryBase + * \brief Base class for camera sensor factories + * + * The CameraSensorFactoryBase class is the base of all specializations of + * the CameraSensorFactory class template. It implements the factory + * registration, maintains a registry of factories, and provides access to the + * registered factories. + */ + +/** + * \brief Construct a camera sensor factory base + * \param[in] name The camera sensor factory name + * \param[in] priority Priority order for factory selection + * + * Creating an instance of the factory base registers it with the global list of + * factories, accessible through the factories() function. + */ +CameraSensorFactoryBase::CameraSensorFactoryBase(const char *name, int priority) + : name_(name), priority_(priority) +{ + registerFactory(this); +} + +/** + * \brief Create an instance of the CameraSensor corresponding to a media entity + * \param[in] entity The media entity on the source end of the sensor + * + * When multiple factories match the same \a entity, this function selects the + * matching factory with the highest priority as specified to the + * REGISTER_CAMERA_SENSOR() macro at factory registration time. If multiple + * matching factories have the same highest priority value, which factory gets + * selected is undefined and may vary between runs. + * + * \return A unique pointer to a new instance of the CameraSensor subclass + * matching the entity, or a null pointer if no such factory exists + */ +std::unique_ptr<CameraSensor> CameraSensorFactoryBase::create(MediaEntity *entity) +{ + const std::vector<CameraSensorFactoryBase *> &factories = + CameraSensorFactoryBase::factories(); + + for (const CameraSensorFactoryBase *factory : factories) { + std::variant<std::unique_ptr<CameraSensor>, int> result = + factory->match(entity); + + if (std::holds_alternative<std::unique_ptr<CameraSensor>>(result)) { + LOG(CameraSensor, Debug) + << "Entity '" << entity->name() << "' matched by " + << factory->name(); + return std::get<std::unique_ptr<CameraSensor>>(std::move(result)); + } + + if (std::get<int>(result)) { + LOG(CameraSensor, Error) + << "Failed to create sensor for '" + << entity->name() << ": " << std::get<int>(result); + return nullptr; + } + } + + return nullptr; +} + +/** + * \fn CameraSensorFactoryBase::name() + * \brief Retrieve the camera sensor factory name + * \return The name of the factory + */ + +/** + * \fn CameraSensorFactoryBase::priority() + * \brief Retrieve the priority value for the factory + * \return The priority value for the factory + */ + +/** + * \brief Retrieve the list of all camera sensor factories + * + * The factories are sorted in decreasing priority order. + * + * \return The list of camera sensor factories + */ +std::vector<CameraSensorFactoryBase *> &CameraSensorFactoryBase::factories() +{ + /* + * The static factories map is defined inside the function to ensure + * it gets initialized on first use, without any dependency on link + * order. + */ + static std::vector<CameraSensorFactoryBase *> factories; + return factories; +} + +/** + * \brief Add a camera sensor class to the registry + * \param[in] factory Factory to use to construct the camera sensor + */ +void CameraSensorFactoryBase::registerFactory(CameraSensorFactoryBase *factory) +{ + std::vector<CameraSensorFactoryBase *> &factories = + CameraSensorFactoryBase::factories(); + + auto pos = std::upper_bound(factories.begin(), factories.end(), factory, + [](const CameraSensorFactoryBase *value, + const CameraSensorFactoryBase *elem) { + return value->priority() > elem->priority(); + }); + factories.insert(pos, factory); +} + +/** + * \class CameraSensorFactory + * \brief Registration of CameraSensorFactory classes and creation of instances + * \tparam _CameraSensor The camera sensor class type for this factory + * + * To facilitate discovery and instantiation of CameraSensor classes, the + * CameraSensorFactory class implements auto-registration of camera sensors. + * Each CameraSensor subclass shall register itself using the + * REGISTER_CAMERA_SENSOR() macro, which will create a corresponding instance + * of a CameraSensorFactory subclass and register it with the static list of + * factories. + */ + +/** + * \fn CameraSensorFactory::CameraSensorFactory() + * \brief Construct a camera sensor factory + * + * Creating an instance of the factory registers it with the global list of + * factories, accessible through the CameraSensorFactoryBase::factories() + * function. + */ + +/** + * \def REGISTER_CAMERA_SENSOR(sensor, priority) + * \brief Register a camera sensor type to the sensor factory + * \param[in] sensor Class name of the CameraSensor derived class to register + * \param[in] priority Priority order for factory selection + * + * Register a CameraSensor subclass with the factory and make it available to + * try and match sensors. The subclass needs to implement a static match + * function: + * + * \code{.cpp} + * static std::variant<std::unique_ptr<CameraSensor>, int> match(MediaEntity *entity); + * \endcode + * + * The function tests if the sensor class supports the camera sensor identified + * by a MediaEntity. If so, it creates a new instance of the sensor class. The + * return value is a variant that contains + * + * - A new instance of the camera sensor class if the entity matched and + * creation succeeded ; + * - A non-zero error code if the entity matched and the creation failed ; or + * - A zero error code if the entity didn't match. + * + * When multiple factories can support the same MediaEntity (as in the match() + * function of multiple factories returning true for the same entity), the \a + * priority argument selects which factory will be used. See + * CameraSensorFactoryBase::create() for more information. + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/sensor/camera_sensor_legacy.cpp index d9261672..32989c19 100644 --- a/src/libcamera/camera_sensor.cpp +++ b/src/libcamera/sensor/camera_sensor_legacy.cpp @@ -2,82 +2,177 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_sensor.cpp - A camera sensor + * camera_sensor_legacy.cpp - A V4L2-backed camera sensor */ -#include "libcamera/internal/camera_sensor.h" -#include "libcamera/internal/media_device.h" - #include <algorithm> +#include <cmath> #include <float.h> #include <iomanip> #include <limits.h> -#include <math.h> +#include <map> +#include <memory> #include <string.h> +#include <string> +#include <vector> + +#include <libcamera/base/class.h> +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> #include <libcamera/camera.h> +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> +#include <libcamera/geometry.h> #include <libcamera/orientation.h> #include <libcamera/property_ids.h> +#include <libcamera/transform.h> -#include <libcamera/base/utils.h> +#include <libcamera/ipa/core_ipa_interface.h> #include "libcamera/internal/bayer_format.h" #include "libcamera/internal/camera_lens.h" +#include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/formats.h" +#include "libcamera/internal/media_device.h" #include "libcamera/internal/sysfs.h" - -/** - * \file camera_sensor.h - * \brief A camera sensor - */ +#include "libcamera/internal/v4l2_subdevice.h" namespace libcamera { -LOG_DEFINE_CATEGORY(CameraSensor) +class BayerFormat; +class CameraLens; +class MediaEntity; +class SensorConfiguration; + +struct CameraSensorProperties; + +enum class Orientation; + +LOG_DECLARE_CATEGORY(CameraSensor) + +class CameraSensorLegacy : public CameraSensor, protected Loggable +{ +public: + CameraSensorLegacy(const MediaEntity *entity); + ~CameraSensorLegacy(); + + static std::variant<std::unique_ptr<CameraSensor>, int> + match(MediaEntity *entity); + + const std::string &model() const override { return model_; } + const std::string &id() const override { return id_; } + + const MediaEntity *entity() const override { return entity_; } + V4L2Subdevice *device() override { return subdev_.get(); } + + CameraLens *focusLens() override { return focusLens_.get(); } + + const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; } + std::vector<Size> sizes(unsigned int mbusCode) const override; + Size resolution() const override; + + V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes, + const Size &size, + const Size maxSize) const override; + int setFormat(V4L2SubdeviceFormat *format, + Transform transform = Transform::Identity) override; + int tryFormat(V4L2SubdeviceFormat *format) const override; + + int applyConfiguration(const SensorConfiguration &config, + Transform transform = Transform::Identity, + V4L2SubdeviceFormat *sensorFormat = nullptr) override; + + const ControlList &properties() const override { return properties_; } + int sensorInfo(IPACameraSensorInfo *info) const override; + Transform computeTransform(Orientation *orientation) const override; + BayerFormat::Order bayerOrder(Transform t) const override; + + const ControlInfoMap &controls() const override; + ControlList getControls(const std::vector<uint32_t> &ids) override; + int setControls(ControlList *ctrls) override; + + const std::vector<controls::draft::TestPatternModeEnum> & + testPatternModes() const override { return testPatternModes_; } + int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override; + const CameraSensorProperties::SensorDelays &sensorDelays() override; + +protected: + std::string logPrefix() const override; + +private: + LIBCAMERA_DISABLE_COPY(CameraSensorLegacy) + + int init(); + int generateId(); + int validateSensorDriver(); + void initVimcDefaultProperties(); + void initStaticProperties(); + void initTestPatternModes(); + int initProperties(); + int applyTestPatternMode(controls::draft::TestPatternModeEnum mode); + int discoverAncillaryDevices(); + + const MediaEntity *entity_; + std::unique_ptr<V4L2Subdevice> subdev_; + unsigned int pad_; + + const CameraSensorProperties *staticProps_; + + std::string model_; + std::string id_; + + V4L2Subdevice::Formats formats_; + std::vector<unsigned int> mbusCodes_; + std::vector<Size> sizes_; + std::vector<controls::draft::TestPatternModeEnum> testPatternModes_; + controls::draft::TestPatternModeEnum testPatternMode_; + + Size pixelArraySize_; + Rectangle activeArea_; + const BayerFormat *bayerFormat_; + bool supportFlips_; + bool flipsAlterBayerOrder_; + Orientation mountingOrientation_; + + ControlList properties_; + + std::unique_ptr<CameraLens> focusLens_; +}; /** - * \class CameraSensor + * \class CameraSensorLegacy * \brief A camera sensor based on V4L2 subdevices * - * The CameraSensor class eases handling of sensors for pipeline handlers by - * hiding the details of the V4L2 subdevice kernel API and caching sensor - * information. - * * The implementation is currently limited to sensors that expose a single V4L2 * subdevice with a single pad. It will be extended to support more complex * devices as the needs arise. */ -/** - * \brief Construct a CameraSensor - * \param[in] entity The media entity backing the camera sensor - * - * Once constructed the instance must be initialized with init(). - */ -CameraSensor::CameraSensor(const MediaEntity *entity) +CameraSensorLegacy::CameraSensorLegacy(const MediaEntity *entity) : entity_(entity), pad_(UINT_MAX), staticProps_(nullptr), bayerFormat_(nullptr), supportFlips_(false), - properties_(properties::properties) + flipsAlterBayerOrder_(false), properties_(properties::properties) { } -/** - * \brief Destroy a CameraSensor - */ -CameraSensor::~CameraSensor() +CameraSensorLegacy::~CameraSensorLegacy() = default; + +std::variant<std::unique_ptr<CameraSensor>, int> +CameraSensorLegacy::match(MediaEntity *entity) { + std::unique_ptr<CameraSensorLegacy> sensor = + std::make_unique<CameraSensorLegacy>(entity); + + int ret = sensor->init(); + if (ret) + return { ret }; + + return { std::move(sensor) }; } -/** - * \brief Initialize the camera sensor instance - * - * This function performs the initialisation steps of the CameraSensor that may - * fail. It shall be called once and only once after constructing the instance. - * - * \return 0 on success or a negative error code otherwise - */ -int CameraSensor::init() +int CameraSensorLegacy::init() { for (const MediaPad *pad : entity_->pads()) { if (pad->flags() & MEDIA_PAD_FL_SOURCE) { @@ -188,23 +283,12 @@ int CameraSensor::init() * Set HBLANK to the minimum to start with a well-defined line length, * allowing IPA modules that do not modify HBLANK to use the sensor * minimum line length in their calculations. - * - * At present, there is no way of knowing if a control is read-only. - * As a workaround, assume that if the minimum and maximum values of - * the V4L2_CID_HBLANK control are the same, it implies the control - * is read-only. - * - * \todo The control API ought to have a flag to specify if a control - * is read-only which could be used below. */ - const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK); - const int32_t hblankMin = hblank.min().get<int32_t>(); - const int32_t hblankMax = hblank.max().get<int32_t>(); - - if (hblankMin != hblankMax) { + const struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK); + if (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { ControlList ctrl(subdev_->controls()); - ctrl.set(V4L2_CID_HBLANK, hblankMin); + ctrl.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblankInfo->minimum)); ret = subdev_->setControls(&ctrl); if (ret) return ret; @@ -213,7 +297,31 @@ int CameraSensor::init() return applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff); } -int CameraSensor::validateSensorDriver() +int CameraSensorLegacy::generateId() +{ + const std::string devPath = subdev_->devicePath(); + + /* Try to get ID from firmware description. */ + id_ = sysfs::firmwareNodePath(devPath); + if (!id_.empty()) + return 0; + + /* + * Virtual sensors not described in firmware + * + * Verify it's a platform device and construct ID from the device path + * and model of sensor. + */ + if (devPath.find("/sys/devices/platform/", 0) == 0) { + id_ = devPath.substr(strlen("/sys/devices/")) + " " + model(); + return 0; + } + + LOG(CameraSensor, Error) << "Can't generate sensor ID"; + return -EINVAL; +} + +int CameraSensorLegacy::validateSensorDriver() { int err = 0; @@ -258,9 +366,14 @@ int CameraSensor::validateSensorDriver() const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP); const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP); if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) && - vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) + vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { supportFlips_ = true; + if (hflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT || + vflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT) + flipsAlterBayerOrder_ = true; + } + if (!supportFlips_) LOG(CameraSensor, Debug) << "Camera sensor does not support horizontal/vertical flip"; @@ -352,18 +465,14 @@ int CameraSensor::validateSensorDriver() return 0; } -/* - * \brief Initialize properties that cannot be intialized by the - * regular initProperties() function for VIMC - */ -void CameraSensor::initVimcDefaultProperties() +void CameraSensorLegacy::initVimcDefaultProperties() { /* Use the largest supported size. */ pixelArraySize_ = sizes_.back(); activeArea_ = Rectangle(pixelArraySize_); } -void CameraSensor::initStaticProperties() +void CameraSensorLegacy::initStaticProperties() { staticProps_ = CameraSensorProperties::get(model_); if (!staticProps_) @@ -375,7 +484,31 @@ void CameraSensor::initStaticProperties() initTestPatternModes(); } -void CameraSensor::initTestPatternModes() +const CameraSensorProperties::SensorDelays &CameraSensorLegacy::sensorDelays() +{ + static constexpr CameraSensorProperties::SensorDelays defaultSensorDelays = { + .exposureDelay = 2, + .gainDelay = 1, + .vblankDelay = 2, + .hblankDelay = 2, + }; + + if (!staticProps_ || + (!staticProps_->sensorDelays.exposureDelay && + !staticProps_->sensorDelays.gainDelay && + !staticProps_->sensorDelays.vblankDelay && + !staticProps_->sensorDelays.hblankDelay)) { + LOG(CameraSensor, Warning) + << "No sensor delays found in static properties. " + "Assuming unverified defaults."; + + return defaultSensorDelays; + } + + return staticProps_->sensorDelays; +} + +void CameraSensorLegacy::initTestPatternModes() { const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN); if (v4l2TestPattern == controls().end()) { @@ -419,7 +552,7 @@ void CameraSensor::initTestPatternModes() } } -int CameraSensor::initProperties() +int CameraSensorLegacy::initProperties() { model_ = subdev_->model(); properties_.set(properties::Model, utils::toAscii(model_)); @@ -516,16 +649,7 @@ int CameraSensor::initProperties() return 0; } -/** - * \brief Check for and initialise any ancillary devices - * - * Sensors sometimes have ancillary devices such as a Lens or Flash that could - * be linked to their MediaEntity by the kernel. Search for and handle any - * such device. - * - * \todo Handle MEDIA_ENT_F_FLASH too. - */ -int CameraSensor::discoverAncillaryDevices() +int CameraSensorLegacy::discoverAncillaryDevices() { int ret; @@ -552,50 +676,7 @@ int CameraSensor::discoverAncillaryDevices() return 0; } -/** - * \fn CameraSensor::model() - * \brief Retrieve the sensor model name - * - * The sensor model name is a free-formed string that uniquely identifies the - * sensor model. - * - * \return The sensor model name - */ - -/** - * \fn CameraSensor::id() - * \brief Retrieve the sensor ID - * - * The sensor ID is a free-form string that uniquely identifies the sensor in - * the system. The ID satisfies the requirements to be used as a camera ID. - * - * \return The sensor ID - */ - -/** - * \fn CameraSensor::entity() - * \brief Retrieve the sensor media entity - * \return The sensor media entity - */ - -/** - * \fn CameraSensor::mbusCodes() - * \brief Retrieve the media bus codes supported by the camera sensor - * - * Any Bayer formats are listed using the sensor's native Bayer order, - * that is, with the effect of V4L2_CID_HFLIP and V4L2_CID_VFLIP undone - * (where these controls exist). - * - * \return The supported media bus codes sorted in increasing order - */ - -/** - * \brief Retrieve the supported frame sizes for a media bus code - * \param[in] mbusCode The media bus code for which sizes are requested - * - * \return The supported frame sizes for \a mbusCode sorted in increasing order - */ -std::vector<Size> CameraSensor::sizes(unsigned int mbusCode) const +std::vector<Size> CameraSensorLegacy::sizes(unsigned int mbusCode) const { std::vector<Size> sizes; @@ -612,120 +693,14 @@ std::vector<Size> CameraSensor::sizes(unsigned int mbusCode) const return sizes; } -/** - * \brief Retrieve the camera sensor resolution - * - * The camera sensor resolution is the active pixel area size, clamped to the - * maximum frame size the sensor can produce if it is smaller than the active - * pixel area. - * - * \todo Consider if it desirable to distinguish between the maximum resolution - * the sensor can produce (also including upscaled ones) and the actual pixel - * array size by splitting this function in two. - * - * \return The camera sensor resolution in pixels - */ -Size CameraSensor::resolution() const +Size CameraSensorLegacy::resolution() const { return std::min(sizes_.back(), activeArea_.size()); } -/** - * \fn CameraSensor::testPatternModes() - * \brief Retrieve all the supported test pattern modes of the camera sensor - * The test pattern mode values correspond to the controls::TestPattern control. - * - * \return The list of test pattern modes - */ - -/** - * \brief Set the test pattern mode for the camera sensor - * \param[in] mode The test pattern mode - * - * The new \a mode is applied to the sensor if it differs from the active test - * pattern mode. Otherwise, this function is a no-op. Setting the same test - * pattern mode for every frame thus incurs no performance penalty. - */ -int CameraSensor::setTestPatternMode(controls::draft::TestPatternModeEnum mode) -{ - if (testPatternMode_ == mode) - return 0; - - if (testPatternModes_.empty()) { - LOG(CameraSensor, Error) - << "Camera sensor does not support test pattern modes."; - return -EINVAL; - } - - return applyTestPatternMode(mode); -} - -int CameraSensor::applyTestPatternMode(controls::draft::TestPatternModeEnum mode) -{ - if (testPatternModes_.empty()) - return 0; - - auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(), - mode); - if (it == testPatternModes_.end()) { - LOG(CameraSensor, Error) << "Unsupported test pattern mode " - << mode; - return -EINVAL; - } - - LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode; - - int32_t index = staticProps_->testPatternModes.at(mode); - ControlList ctrls{ controls() }; - ctrls.set(V4L2_CID_TEST_PATTERN, index); - - int ret = setControls(&ctrls); - if (ret) - return ret; - - testPatternMode_ = mode; - - return 0; -} - -/** - * \brief Retrieve the best sensor format for a desired output - * \param[in] mbusCodes The list of acceptable media bus codes - * \param[in] size The desired size - * - * Media bus codes are selected from \a mbusCodes, which lists all acceptable - * codes in decreasing order of preference. Media bus codes supported by the - * sensor but not listed in \a mbusCodes are ignored. If none of the desired - * codes is supported, it returns an error. - * - * \a size indicates the desired size at the output of the sensor. This function - * selects the best media bus code and size supported by the sensor according - * to the following criteria. - * - * - The desired \a size shall fit in the sensor output size to avoid the need - * to up-scale. - * - The sensor output size shall match the desired aspect ratio to avoid the - * need to crop the field of view. - * - The sensor output size shall be as small as possible to lower the required - * bandwidth. - * - The desired \a size shall be supported by one of the media bus code listed - * in \a mbusCodes. - * - * When multiple media bus codes can produce the same size, the code at the - * lowest position in \a mbusCodes is selected. - * - * The use of this function is optional, as the above criteria may not match the - * needs of all pipeline handlers. Pipeline handlers may implement custom - * sensor format selection when needed. - * - * The returned sensor output format is guaranteed to be acceptable by the - * setFormat() function without any modification. - * - * \return The best sensor output format matching the desired media bus codes - * and size on success, or an empty format otherwise. - */ -V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbusCodes, - const Size &size) const +V4L2SubdeviceFormat +CameraSensorLegacy::getFormat(const std::vector<unsigned int> &mbusCodes, + const Size &size, Size maxSize) const { unsigned int desiredArea = size.width * size.height; unsigned int bestArea = UINT_MAX; @@ -742,11 +717,15 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu for (const SizeRange &range : formats->second) { const Size &sz = range.max; + if (!maxSize.isNull() && + (sz.width > maxSize.width || sz.height > maxSize.height)) + continue; + if (sz.width < size.width || sz.height < size.height) continue; float ratio = static_cast<float>(sz.width) / sz.height; - float ratioDiff = fabsf(ratio - desiredRatio); + float ratioDiff = std::abs(ratio - desiredRatio); unsigned int area = sz.width * sz.height; unsigned int areaDiff = area - desiredArea; @@ -768,7 +747,7 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu } V4L2SubdeviceFormat format{ - .mbus_code = bestCode, + .code = bestCode, .size = *bestSize, .colorSpace = ColorSpace::Raw, }; @@ -776,22 +755,7 @@ V4L2SubdeviceFormat CameraSensor::getFormat(const std::vector<unsigned int> &mbu return format; } -/** - * \brief Set the sensor output format - * \param[in] format The desired sensor output format - * \param[in] transform The transform to be applied on the sensor. - * Defaults to Identity. - * - * If flips are writable they are configured according to the desired Transform. - * Transform::Identity always corresponds to H/V flip being disabled if the - * controls are writable. Flips are set before the new format is applied as - * they can effectively change the Bayer pattern ordering. - * - * The ranges of any controls associated with the sensor are also updated. - * - * \return 0 on success or a negative error code otherwise - */ -int CameraSensor::setFormat(V4L2SubdeviceFormat *format, Transform transform) +int CameraSensorLegacy::setFormat(V4L2SubdeviceFormat *format, Transform transform) { /* Configure flips if the sensor supports that. */ if (supportFlips_) { @@ -812,45 +776,19 @@ int CameraSensor::setFormat(V4L2SubdeviceFormat *format, Transform transform) if (ret) return ret; - updateControlInfo(); + subdev_->updateControlInfo(); return 0; } -/** - * \brief Try the sensor output format - * \param[in] format The desired sensor output format - * - * The ranges of any controls associated with the sensor are not updated. - * - * \todo Add support for Transform by changing the format's Bayer ordering - * before calling subdev_->setFormat(). - * - * \return 0 on success or a negative error code otherwise - */ -int CameraSensor::tryFormat(V4L2SubdeviceFormat *format) const +int CameraSensorLegacy::tryFormat(V4L2SubdeviceFormat *format) const { return subdev_->setFormat(pad_, format, V4L2Subdevice::Whence::TryFormat); } -/** - * \brief Apply a sensor configuration to the camera sensor - * \param[in] config The sensor configuration - * \param[in] transform The transform to be applied on the sensor. - * Defaults to Identity - * \param[out] sensorFormat Format applied to the sensor (optional) - * - * Apply to the camera sensor the configuration \a config. - * - * \todo The configuration shall be fully populated and if any of the fields - * specified cannot be applied exactly, an error code is returned. - * - * \return 0 if \a config is applied correctly to the camera sensor, a negative - * error code otherwise - */ -int CameraSensor::applyConfiguration(const SensorConfiguration &config, - Transform transform, - V4L2SubdeviceFormat *sensorFormat) +int CameraSensorLegacy::applyConfiguration(const SensorConfiguration &config, + Transform transform, + V4L2SubdeviceFormat *sensorFormat) { if (!config.isValid()) { LOG(CameraSensor, Error) << "Invalid sensor configuration"; @@ -890,12 +828,12 @@ int CameraSensor::applyConfiguration(const SensorConfiguration &config, size.height != config.outputSize.height) continue; - subdevFormat.mbus_code = code; + subdevFormat.code = code; subdevFormat.size = size; break; } } - if (!subdevFormat.mbus_code) { + if (!subdevFormat.code) { LOG(CameraSensor, Error) << "Invalid output size in sensor configuration"; return -EINVAL; } @@ -917,107 +855,7 @@ int CameraSensor::applyConfiguration(const SensorConfiguration &config, return 0; } -/** - * \brief Retrieve the supported V4L2 controls and their information - * - * Control information is updated automatically to reflect the current sensor - * configuration when the setFormat() function is called, without invalidating - * any iterator on the ControlInfoMap. A manual update can also be forced by - * calling the updateControlInfo() function for pipeline handlers that change - * the sensor configuration wihtout using setFormat(). - * - * \return A map of the V4L2 controls supported by the sensor - */ -const ControlInfoMap &CameraSensor::controls() const -{ - return subdev_->controls(); -} - -/** - * \brief Read V4L2 controls from the sensor - * \param[in] ids The list of controls to read, specified by their ID - * - * This function reads the value of all controls contained in \a ids, and - * returns their values as a ControlList. The control identifiers are defined by - * the V4L2 specification (V4L2_CID_*). - * - * If any control in \a ids is not supported by the device, is disabled (i.e. - * has the V4L2_CTRL_FLAG_DISABLED flag set), or if any other error occurs - * during validation of the requested controls, no control is read and this - * function returns an empty control list. - * - * \sa V4L2Device::getControls() - * - * \return The control values in a ControlList on success, or an empty list on - * error - */ -ControlList CameraSensor::getControls(const std::vector<uint32_t> &ids) -{ - return subdev_->getControls(ids); -} - -/** - * \brief Write V4L2 controls to the sensor - * \param[in] ctrls The list of controls to write - * - * This function writes the value of all controls contained in \a ctrls, and - * stores the values actually applied to the device in the corresponding \a - * ctrls entry. The control identifiers are defined by the V4L2 specification - * (V4L2_CID_*). - * - * If any control in \a ctrls is not supported by the device, is disabled (i.e. - * has the V4L2_CTRL_FLAG_DISABLED flag set), is read-only, or if any other - * error occurs during validation of the requested controls, no control is - * written and this function returns -EINVAL. - * - * If an error occurs while writing the controls, the index of the first - * control that couldn't be written is returned. All controls below that index - * are written and their values are updated in \a ctrls, while all other - * controls are not written and their values are not changed. - * - * \sa V4L2Device::setControls() - * - * \return 0 on success or an error code otherwise - * \retval -EINVAL One of the control is not supported or not accessible - * \retval i The index of the control that failed - */ -int CameraSensor::setControls(ControlList *ctrls) -{ - return subdev_->setControls(ctrls); -} - -/** - * \fn CameraSensor::device() - * \brief Retrieve the camera sensor device - * \todo Remove this function by integrating DelayedControl with CameraSensor - * \return The camera sensor device - */ - -/** - * \fn CameraSensor::properties() - * \brief Retrieve the camera sensor properties - * \return The list of camera sensor properties - */ - -/** - * \brief Assemble and return the camera sensor info - * \param[out] info The camera sensor info - * - * This function fills \a info with information that describes the camera sensor - * and its current configuration. The information combines static data (such as - * the the sensor model or active pixel array size) and data specific to the - * current sensor configuration (such as the line length and pixel rate). - * - * Sensor information is only available for raw sensors. When called for a YUV - * sensor, this function returns -EINVAL. - * - * Pipeline handlers that do not change the sensor format using the setFormat() - * function may need to call updateControlInfo() beforehand, to ensure all the - * control ranges are up to date. - * - * \return 0 on success, a negative error code otherwise - */ -int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const +int CameraSensorLegacy::sensorInfo(IPACameraSensorInfo *info) const { if (!bayerFormat_) return -EINVAL; @@ -1058,7 +896,7 @@ int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const ret = subdev_->getFormat(pad_, &format); if (ret) return ret; - info->bitsPerPixel = format.bitsPerPixel(); + info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; info->outputSize = format.size; std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement); @@ -1091,51 +929,7 @@ int CameraSensor::sensorInfo(IPACameraSensorInfo *info) const return 0; } -/** - * \fn void CameraSensor::updateControlInfo() - * \brief Update the sensor's ControlInfoMap in case they have changed - * \sa V4L2Device::updateControlInfo() - */ -void CameraSensor::updateControlInfo() -{ - subdev_->updateControlInfo(); -} - -/** - * \fn CameraSensor::focusLens() - * \brief Retrieve the focus lens controller - * - * \return The focus lens controller. nullptr if no focus lens controller is - * connected to the sensor - */ - -/** - * \brief Compute the Transform that gives the requested \a orientation - * \param[inout] orientation The desired image orientation - * - * This function computes the Transform that the pipeline handler should apply - * to the CameraSensor to obtain the requested \a orientation. - * - * The intended caller of this function is the validate() implementation of - * pipeline handlers, that pass in the application requested - * CameraConfiguration::orientation and obtain a Transform to apply to the - * camera sensor, likely at configure() time. - * - * If the requested \a orientation cannot be obtained, the \a orientation - * parameter is adjusted to report the current image orientation and - * Transform::Identity is returned. - * - * If the requested \a orientation can be obtained, the function computes a - * Transform and does not adjust \a orientation. - * - * Pipeline handlers are expected to verify if \a orientation has been - * adjusted by this function and set the CameraConfiguration::status to - * Adjusted accordingly. - * - * \return A Transform instance that applied to the CameraSensor produces images - * with \a orientation - */ -Transform CameraSensor::computeTransform(Orientation *orientation) const +Transform CameraSensorLegacy::computeTransform(Orientation *orientation) const { /* * If we cannot do any flips we cannot change the native camera mounting @@ -1168,33 +962,84 @@ Transform CameraSensor::computeTransform(Orientation *orientation) const return transform; } -std::string CameraSensor::logPrefix() const +BayerFormat::Order CameraSensorLegacy::bayerOrder(Transform t) const { - return "'" + entity_->name() + "'"; + /* Return a defined by meaningless value for non-Bayer sensors. */ + if (!bayerFormat_) + return BayerFormat::Order::BGGR; + + if (!flipsAlterBayerOrder_) + return bayerFormat_->order; + + /* + * Apply the transform to the native (i.e. untransformed) Bayer order, + * using the rest of the Bayer format supplied by the caller. + */ + return bayerFormat_->transform(t).order; } -int CameraSensor::generateId() +const ControlInfoMap &CameraSensorLegacy::controls() const { - const std::string devPath = subdev_->devicePath(); + return subdev_->controls(); +} - /* Try to get ID from firmware description. */ - id_ = sysfs::firmwareNodePath(devPath); - if (!id_.empty()) +ControlList CameraSensorLegacy::getControls(const std::vector<uint32_t> &ids) +{ + return subdev_->getControls(ids); +} + +int CameraSensorLegacy::setControls(ControlList *ctrls) +{ + return subdev_->setControls(ctrls); +} + +int CameraSensorLegacy::setTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternMode_ == mode) return 0; - /* - * Virtual sensors not described in firmware - * - * Verify it's a platform device and construct ID from the device path - * and model of sensor. - */ - if (devPath.find("/sys/devices/platform/", 0) == 0) { - id_ = devPath.substr(strlen("/sys/devices/")) + " " + model(); + if (testPatternModes_.empty()) { + LOG(CameraSensor, Error) + << "Camera sensor does not support test pattern modes."; + return -EINVAL; + } + + return applyTestPatternMode(mode); +} + +int CameraSensorLegacy::applyTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternModes_.empty()) return 0; + + auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(), + mode); + if (it == testPatternModes_.end()) { + LOG(CameraSensor, Error) << "Unsupported test pattern mode " + << mode; + return -EINVAL; } - LOG(CameraSensor, Error) << "Can't generate sensor ID"; - return -EINVAL; + LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode; + + int32_t index = staticProps_->testPatternModes.at(mode); + ControlList ctrls{ controls() }; + ctrls.set(V4L2_CID_TEST_PATTERN, index); + + int ret = setControls(&ctrls); + if (ret) + return ret; + + testPatternMode_ = mode; + + return 0; } +std::string CameraSensorLegacy::logPrefix() const +{ + return "'" + entity_->name() + "'"; +} + +REGISTER_CAMERA_SENSOR(CameraSensorLegacy, -100) + } /* namespace libcamera */ diff --git a/src/libcamera/camera_sensor_properties.cpp b/src/libcamera/sensor/camera_sensor_properties.cpp index 27d6799a..c9e9e148 100644 --- a/src/libcamera/camera_sensor_properties.cpp +++ b/src/libcamera/sensor/camera_sensor_properties.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * camera_sensor_properties.cpp - Database of camera sensor properties + * Database of camera sensor properties */ #include "libcamera/internal/camera_sensor_properties.h" @@ -41,6 +41,35 @@ LOG_DEFINE_CATEGORY(CameraSensorProperties) * \brief Map that associates the TestPattern control value with the indexes of * the corresponding sensor test pattern modes as returned by * V4L2_CID_TEST_PATTERN. + * + * \var CameraSensorProperties::sensorDelays + * \brief Sensor control application delays + * + * This structure may be defined as empty if the actual sensor delays are not + * available or have not been measured. + */ + +/** + * \struct CameraSensorProperties::SensorDelays + * \brief Sensor control application delay values + * + * This structure holds delay values, expressed in number of frames, between the + * time a control value is applied to the sensor and the time that value is + * reflected in the output. For example "2 frames delay" means that parameters + * set during frame N will take effect for frame N+2 (and by extension a delay + * of 0 would mean the parameter is applied immediately to the current frame). + * + * \var CameraSensorProperties::SensorDelays::exposureDelay + * \brief Number of frames between application of exposure control and effect + * + * \var CameraSensorProperties::SensorDelays::gainDelay + * \brief Number of frames between application of analogue gain control and effect + * + * \var CameraSensorProperties::SensorDelays::vblankDelay + * \brief Number of frames between application of vblank control and effect + * + * \var CameraSensorProperties::SensorDelays::hblankDelay + * \brief Number of frames between application of hblank control and effect */ /** @@ -52,6 +81,21 @@ LOG_DEFINE_CATEGORY(CameraSensorProperties) const CameraSensorProperties *CameraSensorProperties::get(const std::string &sensor) { static const std::map<std::string, const CameraSensorProperties> sensorProps = { + { "ar0144", { + .unitCellSize = { 3000, 3000 }, + .testPatternModes = { + { controls::draft::TestPatternModeOff, 0 }, + { controls::draft::TestPatternModeSolidColor, 1 }, + { controls::draft::TestPatternModeColorBars, 2 }, + { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, + }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, + } }, { "ar0521", { .unitCellSize = { 2200, 2200 }, .testPatternModes = { @@ -60,6 +104,33 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeColorBars, 2 }, { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, }, + .sensorDelays = { }, + } }, + { "gc05a2", { + .unitCellSize = { 1120, 1120 }, + .testPatternModes = { + { controls::draft::TestPatternModeOff, 0 }, + { controls::draft::TestPatternModeColorBars, 1 }, + }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, + } }, + { "gc08a3", { + .unitCellSize = { 1120, 1120 }, + .testPatternModes = { + { controls::draft::TestPatternModeOff, 0 }, + { controls::draft::TestPatternModeColorBars, 2 }, + }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "hi846", { .unitCellSize = { 1120, 1120 }, @@ -78,6 +149,18 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * 9: "Resolution Pattern" */ }, + .sensorDelays = { }, + } }, + { "imx214", { + .unitCellSize = { 1120, 1120 }, + .testPatternModes = { + { controls::draft::TestPatternModeOff, 0 }, + { controls::draft::TestPatternModeColorBars, 1 }, + { controls::draft::TestPatternModeSolidColor, 2 }, + { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, + { controls::draft::TestPatternModePn9, 4 }, + }, + .sensorDelays = { }, } }, { "imx219", { .unitCellSize = { 1120, 1120 }, @@ -88,6 +171,12 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, { controls::draft::TestPatternModePn9, 4 }, }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 1, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "imx258", { .unitCellSize = { 1120, 1120 }, @@ -98,22 +187,72 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, { controls::draft::TestPatternModePn9, 4 }, }, + .sensorDelays = { }, + } }, + { "imx283", { + .unitCellSize = { 2400, 2400 }, + .testPatternModes = {}, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 1, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "imx290", { .unitCellSize = { 2900, 2900 }, .testPatternModes = {}, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "imx296", { .unitCellSize = { 3450, 3450 }, .testPatternModes = {}, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "imx327", { .unitCellSize = { 2900, 2900 }, .testPatternModes = {}, + .sensorDelays = { }, + } }, + { "imx335", { + .unitCellSize = { 2000, 2000 }, + .testPatternModes = {}, + .sensorDelays = { }, + } }, + { "imx415", { + .unitCellSize = { 1450, 1450 }, + .testPatternModes = {}, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, + } }, + { "imx462", { + .unitCellSize = { 2900, 2900 }, + .testPatternModes = {}, + .sensorDelays = { }, } }, { "imx477", { .unitCellSize = { 1550, 1550 }, .testPatternModes = {}, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 3, + .hblankDelay = 3 + }, } }, { "imx519", { .unitCellSize = { 1220, 1220 }, @@ -126,6 +265,12 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * these two patterns do not comply with MIPI CCS v1.1 (Section 10.1). */ }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 3, + .hblankDelay = 3 + }, } }, { "imx708", { .unitCellSize = { 1400, 1400 }, @@ -136,6 +281,12 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeColorBarsFadeToGray, 3 }, { controls::draft::TestPatternModePn9, 4 }, }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 3, + .hblankDelay = 3 + }, } }, { "ov2685", { .unitCellSize = { 1750, 1750 }, @@ -150,6 +301,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * 5: "Color Square" */ }, + .sensorDelays = { }, } }, { "ov2740", { .unitCellSize = { 1400, 1400 }, @@ -157,6 +309,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeOff, 0 }, { controls::draft::TestPatternModeColorBars, 1}, }, + .sensorDelays = { }, } }, { "ov4689", { .unitCellSize = { 2000, 2000 }, @@ -170,6 +323,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * colorBarType2 and colorBarType3. */ }, + .sensorDelays = { }, } }, { "ov5640", { .unitCellSize = { 1400, 1400 }, @@ -177,10 +331,25 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeOff, 0 }, { controls::draft::TestPatternModeColorBars, 1 }, }, + .sensorDelays = { }, } }, { "ov5647", { .unitCellSize = { 1400, 1400 }, .testPatternModes = {}, + /* + * We run this sensor in a mode where the gain delay is + * bumped up to 2. It seems to be the only way to make + * the delays "predictable". + * + * \todo Verify these delays properly, as the upstream + * driver appears to configure _no_ delay. + */ + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "ov5670", { .unitCellSize = { 1120, 1120 }, @@ -188,6 +357,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeOff, 0 }, { controls::draft::TestPatternModeColorBars, 1 }, }, + .sensorDelays = { }, } }, { "ov5675", { .unitCellSize = { 1120, 1120 }, @@ -195,6 +365,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeOff, 0 }, { controls::draft::TestPatternModeColorBars, 1 }, }, + .sensorDelays = { }, } }, { "ov5693", { .unitCellSize = { 1400, 1400 }, @@ -207,6 +378,36 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * Rolling Bar". */ }, + .sensorDelays = { }, + } }, + { "ov64a40", { + .unitCellSize = { 1008, 1008 }, + .testPatternModes = { + { controls::draft::TestPatternModeOff, 0 }, + { controls::draft::TestPatternModeColorBars, 1 }, + { controls::draft::TestPatternModeColorBarsFadeToGray, 2 }, + /* + * No corresponding test patter mode + * 3: "Vertical Color Bar Type 3", + * 4: "Vertical Color Bar Type 4" + */ + }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, + } }, + { "ov7251", { + .unitCellSize = { 3000, 3000 }, + .testPatternModes = { }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "ov8858", { .unitCellSize = { 1120, 1120 }, @@ -220,6 +421,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * 4: "Vertical Color Bar Type 4" */ }, + .sensorDelays = { }, } }, { "ov8865", { .unitCellSize = { 1400, 1400 }, @@ -234,6 +436,17 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen * 5: "Color squares with rolling bar" */ }, + .sensorDelays = { }, + } }, + { "ov9281", { + .unitCellSize = { 3000, 3000 }, + .testPatternModes = { }, + .sensorDelays = { + .exposureDelay = 2, + .gainDelay = 2, + .vblankDelay = 2, + .hblankDelay = 2 + }, } }, { "ov13858", { .unitCellSize = { 1120, 1120 }, @@ -241,6 +454,7 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen { controls::draft::TestPatternModeOff, 0 }, { controls::draft::TestPatternModeColorBars, 1 }, }, + .sensorDelays = { }, } }, }; diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp new file mode 100644 index 00000000..ab75b1f8 --- /dev/null +++ b/src/libcamera/sensor/camera_sensor_raw.cpp @@ -0,0 +1,1157 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy. + * + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API + */ + +#include <algorithm> +#include <cmath> +#include <float.h> +#include <iomanip> +#include <limits.h> +#include <map> +#include <memory> +#include <optional> +#include <string.h> +#include <string> +#include <vector> + +#include <libcamera/base/class.h> +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include <libcamera/camera.h> +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> +#include <libcamera/geometry.h> +#include <libcamera/orientation.h> +#include <libcamera/property_ids.h> +#include <libcamera/transform.h> + +#include <libcamera/ipa/core_ipa_interface.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/camera_lens.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" +#include "libcamera/internal/formats.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/sysfs.h" +#include "libcamera/internal/v4l2_subdevice.h" + +namespace libcamera { + +class BayerFormat; +class CameraLens; +class MediaEntity; +class SensorConfiguration; + +struct CameraSensorProperties; + +enum class Orientation; + +LOG_DECLARE_CATEGORY(CameraSensor) + +class CameraSensorRaw : public CameraSensor, protected Loggable +{ +public: + CameraSensorRaw(const MediaEntity *entity); + ~CameraSensorRaw(); + + static std::variant<std::unique_ptr<CameraSensor>, int> + match(MediaEntity *entity); + + const std::string &model() const override { return model_; } + const std::string &id() const override { return id_; } + + const MediaEntity *entity() const override { return entity_; } + V4L2Subdevice *device() override { return subdev_.get(); } + + CameraLens *focusLens() override { return focusLens_.get(); } + + const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; } + std::vector<Size> sizes(unsigned int mbusCode) const override; + Size resolution() const override; + + V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes, + const Size &size, + const Size maxSize) const override; + int setFormat(V4L2SubdeviceFormat *format, + Transform transform = Transform::Identity) override; + int tryFormat(V4L2SubdeviceFormat *format) const override; + + int applyConfiguration(const SensorConfiguration &config, + Transform transform = Transform::Identity, + V4L2SubdeviceFormat *sensorFormat = nullptr) override; + + V4L2Subdevice::Stream imageStream() const override; + std::optional<V4L2Subdevice::Stream> embeddedDataStream() const override; + V4L2SubdeviceFormat embeddedDataFormat() const override; + int setEmbeddedDataEnabled(bool enable) override; + + const ControlList &properties() const override { return properties_; } + int sensorInfo(IPACameraSensorInfo *info) const override; + Transform computeTransform(Orientation *orientation) const override; + BayerFormat::Order bayerOrder(Transform t) const override; + + const ControlInfoMap &controls() const override; + ControlList getControls(const std::vector<uint32_t> &ids) override; + int setControls(ControlList *ctrls) override; + + const std::vector<controls::draft::TestPatternModeEnum> & + testPatternModes() const override { return testPatternModes_; } + int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override; + const CameraSensorProperties::SensorDelays &sensorDelays() override; + +protected: + std::string logPrefix() const override; + +private: + LIBCAMERA_DISABLE_COPY(CameraSensorRaw) + + std::optional<int> init(); + int initProperties(); + void initStaticProperties(); + void initTestPatternModes(); + int applyTestPatternMode(controls::draft::TestPatternModeEnum mode); + + const MediaEntity *entity_; + std::unique_ptr<V4L2Subdevice> subdev_; + + struct Streams { + V4L2Subdevice::Stream sink; + V4L2Subdevice::Stream source; + }; + + struct { + Streams image; + std::optional<Streams> edata; + } streams_; + + const CameraSensorProperties *staticProps_; + + std::string model_; + std::string id_; + + V4L2Subdevice::Formats formats_; + std::vector<unsigned int> mbusCodes_; + std::vector<Size> sizes_; + std::vector<controls::draft::TestPatternModeEnum> testPatternModes_; + controls::draft::TestPatternModeEnum testPatternMode_; + + Size pixelArraySize_; + Rectangle activeArea_; + BayerFormat::Order cfaPattern_; + bool supportFlips_; + bool flipsAlterBayerOrder_; + Orientation mountingOrientation_; + + ControlList properties_; + + std::unique_ptr<CameraLens> focusLens_; +}; + +/** + * \class CameraSensorRaw + * \brief A camera sensor based on V4L2 subdevices + * + * This class supports single-subdev sensors with a single source pad and one + * or two internal sink pads (for the image and embedded data streams). + */ + +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity) + : entity_(entity), staticProps_(nullptr), supportFlips_(false), + flipsAlterBayerOrder_(false), properties_(properties::properties) +{ +} + +CameraSensorRaw::~CameraSensorRaw() = default; + +std::variant<std::unique_ptr<CameraSensor>, int> +CameraSensorRaw::match(MediaEntity *entity) +{ + /* Check the entity type. */ + if (entity->type() != MediaEntity::Type::V4L2Subdevice || + entity->function() != MEDIA_ENT_F_CAM_SENSOR) { + libcamera::LOG(CameraSensor, Debug) + << entity->name() << ": unsupported entity type (" + << utils::to_underlying(entity->type()) + << ") or function (" << utils::hex(entity->function()) << ")"; + return { 0 }; + } + + /* Count and check the number of pads. */ + static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_INTERNAL; + unsigned int numSinks = 0; + unsigned int numSources = 0; + + for (const MediaPad *pad : entity->pads()) { + switch (pad->flags() & kPadFlagsMask) { + case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL: + numSinks++; + break; + + case MEDIA_PAD_FL_SOURCE: + numSources++; + break; + + default: + libcamera::LOG(CameraSensor, Debug) + << entity->name() << ": unsupported pad " << pad->index() + << " type " << utils::hex(pad->flags()); + return { 0 }; + } + } + + if (numSinks < 1 || numSinks > 2 || numSources != 1) { + libcamera::LOG(CameraSensor, Debug) + << entity->name() << ": unsupported number of sinks (" + << numSinks << ") or sources (" << numSources << ")"; + return { 0 }; + } + + /* + * The entity matches. Create the camera sensor and initialize it. The + * init() function will perform further match checks. + */ + std::unique_ptr<CameraSensorRaw> sensor = + std::make_unique<CameraSensorRaw>(entity); + + std::optional<int> err = sensor->init(); + if (err) + return { *err }; + + return { std::move(sensor) }; +} + +std::optional<int> CameraSensorRaw::init() +{ + /* Create and open the subdev. */ + subdev_ = std::make_unique<V4L2Subdevice>(entity_); + int ret = subdev_->open(); + if (ret) + return { ret }; + + /* + * 1. Identify the pads. + */ + + /* + * First locate the source pad. The match() function guarantees there + * is one and only one source pad. + */ + unsigned int sourcePad = UINT_MAX; + + for (const MediaPad *pad : entity_->pads()) { + if (pad->flags() & MEDIA_PAD_FL_SOURCE) { + sourcePad = pad->index(); + break; + } + } + + /* + * Iterate over the routes to identify the streams on the source pad, + * and the internal sink pads. + */ + V4L2Subdevice::Routing routing = {}; + ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat); + if (ret) + return { ret }; + + bool imageStreamFound = false; + + for (const V4L2Subdevice::Route &route : routing) { + if (route.source.pad != sourcePad) { + LOG(CameraSensor, Error) << "Invalid route " << route; + return { -EINVAL }; + } + + /* Identify the stream type based on the supported formats. */ + V4L2Subdevice::Formats formats = subdev_->formats(route.source); + + std::optional<MediaBusFormatInfo::Type> type; + + for (const auto &[code, sizes] : formats) { + const MediaBusFormatInfo &info = + MediaBusFormatInfo::info(code); + if (info.isValid()) { + type = info.type; + break; + } + } + + if (!type) { + LOG(CameraSensor, Warning) + << "No known format on pad " << route.source; + continue; + } + + switch (*type) { + case MediaBusFormatInfo::Type::Image: + if (imageStreamFound) { + LOG(CameraSensor, Error) + << "Multiple internal image streams (" + << streams_.image.sink << " and " + << route.sink << ")"; + return { -EINVAL }; + } + + imageStreamFound = true; + streams_.image.sink = route.sink; + streams_.image.source = route.source; + break; + + case MediaBusFormatInfo::Type::Metadata: + /* + * Skip metadata streams that are not sensor embedded + * data. The source stream reports a generic metadata + * format, check the sink stream for the exact format. + */ + formats = subdev_->formats(route.sink); + if (formats.size() != 1) + continue; + + if (MediaBusFormatInfo::info(formats.cbegin()->first).type != + MediaBusFormatInfo::Type::EmbeddedData) + continue; + + if (streams_.edata) { + LOG(CameraSensor, Error) + << "Multiple internal embedded data streams (" + << streams_.edata->sink << " and " + << route.sink << ")"; + return { -EINVAL }; + } + + streams_.edata = { route.sink, route.source }; + break; + + default: + break; + } + } + + if (!imageStreamFound) { + LOG(CameraSensor, Error) << "No image stream found"; + return { -EINVAL }; + } + + LOG(CameraSensor, Debug) + << "Found image stream " << streams_.image.sink + << " -> " << streams_.image.source; + + if (streams_.edata) + LOG(CameraSensor, Debug) + << "Found embedded data stream " << streams_.edata->sink + << " -> " << streams_.edata->source; + + /* + * 2. Enumerate and cache the media bus codes, sizes and colour filter + * array order for the image stream. + */ + + /* + * Get the native sensor CFA pattern. It is simpler to retrieve it from + * the internal image sink pad as it is guaranteed to expose a single + * format, and is not affected by flips. + */ + V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink); + if (formats.size() != 1) { + LOG(CameraSensor, Error) + << "Image pad has " << formats.size() + << " formats, expected 1"; + return { -EINVAL }; + } + + uint32_t nativeFormat = formats.cbegin()->first; + const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat); + if (!bayerFormat.isValid()) { + LOG(CameraSensor, Error) + << "Invalid native format " << nativeFormat; + return { 0 }; + } + + cfaPattern_ = bayerFormat.order; + + /* + * Retrieve and cache the media bus codes and sizes on the source image + * stream. + */ + formats_ = subdev_->formats(streams_.image.source); + if (formats_.empty()) { + LOG(CameraSensor, Error) << "No image format found"; + return { -EINVAL }; + } + + /* Populate and sort the media bus codes and the sizes. */ + for (const auto &[code, ranges] : formats_) { + /* Drop non-raw formats (in case we have a hybrid sensor). */ + const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code); + if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW) + continue; + + mbusCodes_.push_back(code); + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_), + [](const SizeRange &range) { return range.max; }); + } + + if (mbusCodes_.empty()) { + LOG(CameraSensor, Debug) << "No raw image formats found"; + return { 0 }; + } + + std::sort(mbusCodes_.begin(), mbusCodes_.end()); + std::sort(sizes_.begin(), sizes_.end()); + + /* + * Remove duplicate sizes. There are no duplicate media bus codes as + * they are the keys in the formats map. + */ + auto last = std::unique(sizes_.begin(), sizes_.end()); + sizes_.erase(last, sizes_.end()); + + /* + * 3. Query selection rectangles. Retrieve properties, and verify that + * all the expected selection rectangles are supported. + */ + + Rectangle rect; + ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS, + &rect); + if (ret) { + LOG(CameraSensor, Error) << "No pixel array crop bounds"; + return { ret }; + } + + pixelArraySize_ = rect.size(); + + ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT, + &activeArea_); + if (ret) { + LOG(CameraSensor, Error) << "No pixel array crop default"; + return { ret }; + } + + ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, + &rect); + if (ret) { + LOG(CameraSensor, Error) << "No pixel array crop rectangle"; + return { ret }; + } + + /* + * 4. Verify that all required controls are present. + */ + + const ControlIdMap &controls = subdev_->controls().idmap(); + + static constexpr uint32_t mandatoryControls[] = { + V4L2_CID_ANALOGUE_GAIN, + V4L2_CID_CAMERA_ORIENTATION, + V4L2_CID_EXPOSURE, + V4L2_CID_HBLANK, + V4L2_CID_PIXEL_RATE, + V4L2_CID_VBLANK, + }; + + ret = 0; + + for (uint32_t ctrl : mandatoryControls) { + if (!controls.count(ctrl)) { + LOG(CameraSensor, Error) + << "Mandatory V4L2 control " << utils::hex(ctrl) + << " not available"; + ret = -EINVAL; + } + } + + if (ret) { + LOG(CameraSensor, Error) + << "The sensor kernel driver needs to be fixed"; + LOG(CameraSensor, Error) + << "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information"; + return { ret }; + } + + /* + * Verify if sensor supports horizontal/vertical flips + * + * \todo Handle horizontal and vertical flips independently. + */ + const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP); + const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP); + if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) && + vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { + supportFlips_ = true; + + if (hflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT || + vflipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT) + flipsAlterBayerOrder_ = true; + } + + if (!supportFlips_) + LOG(CameraSensor, Debug) + << "Camera sensor does not support horizontal/vertical flip"; + + /* + * 5. Discover ancillary devices. + * + * \todo This code may be shared by different V4L2 sensor classes. + */ + for (MediaEntity *ancillary : entity_->ancillaryEntities()) { + switch (ancillary->function()) { + case MEDIA_ENT_F_LENS: + focusLens_ = std::make_unique<CameraLens>(ancillary); + ret = focusLens_->init(); + if (ret) { + LOG(CameraSensor, Error) + << "Lens initialisation failed, lens disabled"; + focusLens_.reset(); + } + break; + + default: + LOG(CameraSensor, Warning) + << "Unsupported ancillary entity function " + << ancillary->function(); + break; + } + } + + /* + * 6. Initialize properties. + */ + + ret = initProperties(); + if (ret) + return { ret }; + + /* + * 7. Initialize controls. + */ + + /* + * Set HBLANK to the minimum to start with a well-defined line length, + * allowing IPA modules that do not modify HBLANK to use the sensor + * minimum line length in their calculations. + */ + const struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK); + if (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { + ControlList ctrl(subdev_->controls()); + + ctrl.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblankInfo->minimum)); + ret = subdev_->setControls(&ctrl); + if (ret) + return ret; + } + + ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff); + if (ret) + return { ret }; + + return {}; +} + +int CameraSensorRaw::initProperties() +{ + model_ = subdev_->model(); + properties_.set(properties::Model, utils::toAscii(model_)); + + /* Generate a unique ID for the sensor. */ + id_ = sysfs::firmwareNodePath(subdev_->devicePath()); + if (id_.empty()) { + LOG(CameraSensor, Error) << "Can't generate sensor ID"; + return -EINVAL; + } + + /* Initialize the static properties from the sensor database. */ + initStaticProperties(); + + /* Retrieve and register properties from the kernel interface. */ + const ControlInfoMap &controls = subdev_->controls(); + + const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION); + if (orientation != controls.end()) { + int32_t v4l2Orientation = orientation->second.def().get<int32_t>(); + int32_t propertyValue; + + switch (v4l2Orientation) { + default: + LOG(CameraSensor, Warning) + << "Unsupported camera location " + << v4l2Orientation << ", setting to External"; + [[fallthrough]]; + case V4L2_CAMERA_ORIENTATION_EXTERNAL: + propertyValue = properties::CameraLocationExternal; + break; + case V4L2_CAMERA_ORIENTATION_FRONT: + propertyValue = properties::CameraLocationFront; + break; + case V4L2_CAMERA_ORIENTATION_BACK: + propertyValue = properties::CameraLocationBack; + break; + } + properties_.set(properties::Location, propertyValue); + } else { + LOG(CameraSensor, Warning) << "Failed to retrieve the camera location"; + } + + const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION); + if (rotationControl != controls.end()) { + int32_t propertyValue = rotationControl->second.def().get<int32_t>(); + + /* + * Cache the Transform associated with the camera mounting + * rotation for later use in computeTransform(). + */ + bool success; + mountingOrientation_ = orientationFromRotation(propertyValue, &success); + if (!success) { + LOG(CameraSensor, Warning) + << "Invalid rotation of " << propertyValue + << " degrees - ignoring"; + mountingOrientation_ = Orientation::Rotate0; + } + + properties_.set(properties::Rotation, propertyValue); + } else { + LOG(CameraSensor, Warning) + << "Rotation control not available, default to 0 degrees"; + properties_.set(properties::Rotation, 0); + mountingOrientation_ = Orientation::Rotate0; + } + + properties_.set(properties::PixelArraySize, pixelArraySize_); + properties_.set(properties::PixelArrayActiveAreas, { activeArea_ }); + + /* Color filter array pattern. */ + uint32_t cfa; + + switch (cfaPattern_) { + case BayerFormat::BGGR: + cfa = properties::draft::BGGR; + break; + case BayerFormat::GBRG: + cfa = properties::draft::GBRG; + break; + case BayerFormat::GRBG: + cfa = properties::draft::GRBG; + break; + case BayerFormat::RGGB: + cfa = properties::draft::RGGB; + break; + case BayerFormat::MONO: + default: + cfa = properties::draft::MONO; + break; + } + + properties_.set(properties::draft::ColorFilterArrangement, cfa); + + return 0; +} + +void CameraSensorRaw::initStaticProperties() +{ + staticProps_ = CameraSensorProperties::get(model_); + if (!staticProps_) + return; + + /* Register the properties retrieved from the sensor database. */ + properties_.set(properties::UnitCellSize, staticProps_->unitCellSize); + + initTestPatternModes(); +} + +const CameraSensorProperties::SensorDelays &CameraSensorRaw::sensorDelays() +{ + static constexpr CameraSensorProperties::SensorDelays defaultSensorDelays = { + .exposureDelay = 2, + .gainDelay = 1, + .vblankDelay = 2, + .hblankDelay = 2, + }; + + if (!staticProps_ || + (!staticProps_->sensorDelays.exposureDelay && + !staticProps_->sensorDelays.gainDelay && + !staticProps_->sensorDelays.vblankDelay && + !staticProps_->sensorDelays.hblankDelay)) { + LOG(CameraSensor, Warning) + << "No sensor delays found in static properties. " + "Assuming unverified defaults."; + + return defaultSensorDelays; + } + + return staticProps_->sensorDelays; +} + +void CameraSensorRaw::initTestPatternModes() +{ + const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN); + if (v4l2TestPattern == controls().end()) { + LOG(CameraSensor, Debug) << "V4L2_CID_TEST_PATTERN is not supported"; + return; + } + + const auto &testPatternModes = staticProps_->testPatternModes; + if (testPatternModes.empty()) { + /* + * The camera sensor supports test patterns but we don't know + * how to map them so this should be fixed. + */ + LOG(CameraSensor, Debug) << "No static test pattern map for \'" + << model() << "\'"; + return; + } + + /* + * Create a map that associates the V4L2 control index to the test + * pattern mode by reversing the testPatternModes map provided by the + * camera sensor properties. This makes it easier to verify if the + * control index is supported in the below for loop that creates the + * list of supported test patterns. + */ + std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode; + for (const auto &it : testPatternModes) + indexToTestPatternMode[it.second] = it.first; + + for (const ControlValue &value : v4l2TestPattern->second.values()) { + const int32_t index = value.get<int32_t>(); + + const auto it = indexToTestPatternMode.find(index); + if (it == indexToTestPatternMode.end()) { + LOG(CameraSensor, Debug) + << "Test pattern mode " << index << " ignored"; + continue; + } + + testPatternModes_.push_back(it->second); + } +} + +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const +{ + std::vector<Size> sizes; + + const auto &format = formats_.find(mbusCode); + if (format == formats_.end()) + return sizes; + + const std::vector<SizeRange> &ranges = format->second; + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes), + [](const SizeRange &range) { return range.max; }); + + std::sort(sizes.begin(), sizes.end()); + + return sizes; +} + +Size CameraSensorRaw::resolution() const +{ + return std::min(sizes_.back(), activeArea_.size()); +} + +V4L2SubdeviceFormat +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes, + const Size &size, Size maxSize) const +{ + unsigned int desiredArea = size.width * size.height; + unsigned int bestArea = UINT_MAX; + float desiredRatio = static_cast<float>(size.width) / size.height; + float bestRatio = FLT_MAX; + const Size *bestSize = nullptr; + uint32_t bestCode = 0; + + for (unsigned int code : mbusCodes) { + const auto formats = formats_.find(code); + if (formats == formats_.end()) + continue; + + for (const SizeRange &range : formats->second) { + const Size &sz = range.max; + + if (!maxSize.isNull() && + (sz.width > maxSize.width || sz.height > maxSize.height)) + continue; + + if (sz.width < size.width || sz.height < size.height) + continue; + + float ratio = static_cast<float>(sz.width) / sz.height; + float ratioDiff = std::abs(ratio - desiredRatio); + unsigned int area = sz.width * sz.height; + unsigned int areaDiff = area - desiredArea; + + if (ratioDiff > bestRatio) + continue; + + if (ratioDiff < bestRatio || areaDiff < bestArea) { + bestRatio = ratioDiff; + bestArea = areaDiff; + bestSize = &sz; + bestCode = code; + } + } + } + + if (!bestSize) { + LOG(CameraSensor, Debug) << "No supported format or size found"; + return {}; + } + + V4L2SubdeviceFormat format{ + .code = bestCode, + .size = *bestSize, + .colorSpace = ColorSpace::Raw, + }; + + return format; +} + +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform) +{ + /* Configure flips if the sensor supports that. */ + if (supportFlips_) { + ControlList flipCtrls(subdev_->controls()); + + flipCtrls.set(V4L2_CID_HFLIP, + static_cast<int32_t>(!!(transform & Transform::HFlip))); + flipCtrls.set(V4L2_CID_VFLIP, + static_cast<int32_t>(!!(transform & Transform::VFlip))); + + int ret = subdev_->setControls(&flipCtrls); + if (ret) + return ret; + } + + /* Apply format on the subdev. */ + int ret = subdev_->setFormat(streams_.image.source, format); + if (ret) + return ret; + + subdev_->updateControlInfo(); + return 0; +} + +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const +{ + return subdev_->setFormat(streams_.image.source, format, + V4L2Subdevice::Whence::TryFormat); +} + +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config, + Transform transform, + V4L2SubdeviceFormat *sensorFormat) +{ + if (!config.isValid()) { + LOG(CameraSensor, Error) << "Invalid sensor configuration"; + return -EINVAL; + } + + std::vector<unsigned int> filteredCodes; + std::copy_if(mbusCodes_.begin(), mbusCodes_.end(), + std::back_inserter(filteredCodes), + [&config](unsigned int mbusCode) { + BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode); + if (bayer.bitDepth == config.bitDepth) + return true; + return false; + }); + if (filteredCodes.empty()) { + LOG(CameraSensor, Error) + << "Cannot find any format with bit depth " + << config.bitDepth; + return -EINVAL; + } + + /* + * Compute the sensor's data frame size by applying the cropping + * rectangle, subsampling and output crop to the sensor's pixel array + * size. + * + * \todo The actual size computation is for now ignored and only the + * output size is considered. This implies that resolutions obtained + * with two different cropping/subsampling will look identical and + * only the first found one will be considered. + */ + V4L2SubdeviceFormat subdevFormat = {}; + for (unsigned int code : filteredCodes) { + for (const Size &size : sizes(code)) { + if (size.width != config.outputSize.width || + size.height != config.outputSize.height) + continue; + + subdevFormat.code = code; + subdevFormat.size = size; + break; + } + } + if (!subdevFormat.code) { + LOG(CameraSensor, Error) << "Invalid output size in sensor configuration"; + return -EINVAL; + } + + int ret = setFormat(&subdevFormat, transform); + if (ret) + return ret; + + /* + * Return to the caller the format actually applied to the sensor. + * This is relevant if transform has changed the bayer pattern order. + */ + if (sensorFormat) + *sensorFormat = subdevFormat; + + /* \todo Handle AnalogCrop. Most sensors do not support set_selection */ + /* \todo Handle scaling in the digital domain. */ + + return 0; +} + +V4L2Subdevice::Stream CameraSensorRaw::imageStream() const +{ + return streams_.image.source; +} + +std::optional<V4L2Subdevice::Stream> CameraSensorRaw::embeddedDataStream() const +{ + if (!streams_.edata) + return {}; + + return { streams_.edata->source }; +} + +V4L2SubdeviceFormat CameraSensorRaw::embeddedDataFormat() const +{ + if (!streams_.edata) + return {}; + + V4L2SubdeviceFormat format; + int ret = subdev_->getFormat(streams_.edata->source, &format); + if (ret) + return {}; + + return format; +} + +int CameraSensorRaw::setEmbeddedDataEnabled(bool enable) +{ + if (!streams_.edata) + return enable ? -ENOSTR : 0; + + V4L2Subdevice::Routing routing{ 2 }; + + routing[0].sink = streams_.image.sink; + routing[0].source = streams_.image.source; + routing[0].flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; + + routing[1].sink = streams_.edata->sink; + routing[1].source = streams_.edata->source; + routing[1].flags = enable ? V4L2_SUBDEV_ROUTE_FL_ACTIVE : 0; + + int ret = subdev_->setRouting(&routing); + if (ret) + return ret; + + /* + * Check if the embedded data stream has been enabled or disabled + * correctly. Assume at least one route will match the embedded data + * source stream, as there would be something seriously wrong + * otherwise. + */ + bool enabled = false; + + for (const V4L2Subdevice::Route &route : routing) { + if (route.source != streams_.edata->source) + continue; + + enabled = route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE; + break; + } + + if (enabled != enable) + return enabled ? -EISCONN : -ENOSTR; + + return 0; +} + +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const +{ + info->model = model(); + + /* + * The active area size is a static property, while the crop + * rectangle needs to be re-read as it depends on the sensor + * configuration. + */ + info->activeAreaSize = { activeArea_.width, activeArea_.height }; + + int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, + &info->analogCrop); + if (ret) + return ret; + + /* + * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y + * are defined relatively to the active pixel area, while V4L2's + * TGT_CROP target is defined in respect to the full pixel array. + * + * Compensate it by subtracting the active area offset. + */ + info->analogCrop.x -= activeArea_.x; + info->analogCrop.y -= activeArea_.y; + + /* The bit depth and image size depend on the currently applied format. */ + V4L2SubdeviceFormat format{}; + ret = subdev_->getFormat(streams_.image.source, &format); + if (ret) + return ret; + info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; + info->outputSize = format.size; + + std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement); + info->cfaPattern = cfa ? *cfa : properties::draft::RGB; + + /* + * Retrieve the pixel rate, line length and minimum/maximum frame + * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, + * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. + */ + ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE, + V4L2_CID_HBLANK, + V4L2_CID_VBLANK }); + if (ctrls.empty()) { + LOG(CameraSensor, Error) + << "Failed to retrieve camera info controls"; + return -EINVAL; + } + + info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>(); + + const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK); + info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>(); + info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>(); + + const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK); + info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>(); + info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>(); + + return 0; +} + +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const +{ + /* + * If we cannot do any flips we cannot change the native camera mounting + * orientation. + */ + if (!supportFlips_) { + *orientation = mountingOrientation_; + return Transform::Identity; + } + + /* + * Now compute the required transform to obtain 'orientation' starting + * from the mounting rotation. + * + * As a note: + * orientation / mountingOrientation_ = transform + * mountingOrientation_ * transform = orientation + */ + Transform transform = *orientation / mountingOrientation_; + + /* + * If transform contains any Transpose we cannot do it, so adjust + * 'orientation' to report the image native orientation and return Identity. + */ + if (!!(transform & Transform::Transpose)) { + *orientation = mountingOrientation_; + return Transform::Identity; + } + + return transform; +} + +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const +{ + if (!flipsAlterBayerOrder_) + return cfaPattern_; + + /* + * Apply the transform to the native (i.e. untransformed) Bayer order, + * using the rest of the Bayer format supplied by the caller. + */ + BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None }; + return format.transform(t).order; +} + +const ControlInfoMap &CameraSensorRaw::controls() const +{ + return subdev_->controls(); +} + +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids) +{ + return subdev_->getControls(ids); +} + +int CameraSensorRaw::setControls(ControlList *ctrls) +{ + return subdev_->setControls(ctrls); +} + +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternMode_ == mode) + return 0; + + if (testPatternModes_.empty()) { + LOG(CameraSensor, Error) + << "Camera sensor does not support test pattern modes."; + return -EINVAL; + } + + return applyTestPatternMode(mode); +} + +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternModes_.empty()) + return 0; + + auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(), + mode); + if (it == testPatternModes_.end()) { + LOG(CameraSensor, Error) << "Unsupported test pattern mode " + << mode; + return -EINVAL; + } + + LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode; + + int32_t index = staticProps_->testPatternModes.at(mode); + ControlList ctrls{ controls() }; + ctrls.set(V4L2_CID_TEST_PATTERN, index); + + int ret = setControls(&ctrls); + if (ret) + return ret; + + testPatternMode_ = mode; + + return 0; +} + +std::string CameraSensorRaw::logPrefix() const +{ + return "'" + entity_->name() + "'"; +} + +REGISTER_CAMERA_SENSOR(CameraSensorRaw, 0) + +} /* namespace libcamera */ diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build new file mode 100644 index 00000000..dce74ed6 --- /dev/null +++ b/src/libcamera/sensor/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_internal_sources += files([ + 'camera_sensor.cpp', + 'camera_sensor_legacy.cpp', + 'camera_sensor_properties.cpp', + 'camera_sensor_raw.cpp', +]) diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp new file mode 100644 index 00000000..d9b61d37 --- /dev/null +++ b/src/libcamera/shared_mem_object.cpp @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023 Raspberry Pi Ltd + * Copyright (C) 2024 Andrei Konovalov + * Copyright (C) 2024 Dennis Bonke + * Copyright (C) 2024 Ideas on Board Oy + * + * Helpers for shared memory allocations + */ + +#include "libcamera/internal/shared_mem_object.h" + +#include <stdint.h> +#include <sys/mman.h> +#include <sys/types.h> + +#include <libcamera/base/memfd.h> + +/** + * \file shared_mem_object.cpp + * \brief Helpers for shared memory allocations + */ + +namespace libcamera { + +/** + * \class SharedMem + * \brief Helper class to allocate and manage memory shareable between processes + * + * SharedMem manages memory suitable for sharing between processes. When an + * instance is constructed, it allocates a memory buffer of the requested size + * backed by an anonymous file, using the memfd API. + * + * The allocated memory is exposed by the mem() function. If memory allocation + * fails, the function returns an empty Span. This can be also checked using the + * bool() operator. + * + * The file descriptor for the backing file is exposed as a SharedFD by the fd() + * function. It can be shared with other processes across IPC boundaries, which + * can then map the memory with mmap(). + * + * A single memfd is created for every SharedMem. If there is a need to allocate + * a large number of objects in shared memory, these objects should be grouped + * together and use the shared memory allocated by a single SharedMem object if + * possible. This will help to minimize the number of created memfd's. + */ + +SharedMem::SharedMem() = default; + +/** + * \brief Construct a SharedMem with memory of the given \a size + * \param[in] name Name of the SharedMem + * \param[in] size Size of the shared memory to allocate and map + * + * The \a name is used for debugging purpose only. Multiple SharedMem instances + * can have the same name. + */ +SharedMem::SharedMem(const std::string &name, std::size_t size) +{ + UniqueFD memfd = MemFd::create(name.c_str(), size, + MemFd::Seal::Shrink | MemFd::Seal::Grow); + if (!memfd.isValid()) + return; + + fd_ = SharedFD(std::move(memfd)); + + void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd_.get(), 0); + if (mem == MAP_FAILED) { + fd_ = SharedFD(); + return; + } + + mem_ = { static_cast<uint8_t *>(mem), size }; +} + +/** + * \brief Move constructor for SharedMem + * \param[in] rhs The object to move + */ +SharedMem::SharedMem(SharedMem &&rhs) +{ + this->fd_ = std::move(rhs.fd_); + this->mem_ = rhs.mem_; + rhs.mem_ = {}; +} + +/** + * \brief Destroy the SharedMem instance + * + * Destroying an instance invalidates the memory mapping exposed with mem(). + * Other mappings of the backing file, created in this or other processes with + * mmap(), remain valid. + * + * Similarly, other references to the backing file descriptor created by copying + * the SharedFD returned by fd() remain valid. The underlying memory will be + * freed only when all file descriptors that reference the anonymous file get + * closed. + */ +SharedMem::~SharedMem() +{ + if (!mem_.empty()) + munmap(mem_.data(), mem_.size_bytes()); +} + +/** + * \brief Move assignment operator for SharedMem + * \param[in] rhs The object to move + */ +SharedMem &SharedMem::operator=(SharedMem &&rhs) +{ + this->fd_ = std::move(rhs.fd_); + this->mem_ = rhs.mem_; + rhs.mem_ = {}; + return *this; +} + +/** + * \fn const SharedFD &SharedMem::fd() const + * \brief Retrieve the file descriptor for the underlying shared memory + * \return The file descriptor, or an invalid SharedFD if allocation failed + */ + +/** + * \fn Span<uint8_t> SharedMem::mem() const + * \brief Retrieve the underlying shared memory + * \return The memory buffer, or an empty Span if allocation failed + */ + +/** + * \fn SharedMem::operator bool() + * \brief Check if the shared memory allocation succeeded + * \return True if allocation of the shared memory succeeded, false otherwise + */ + +/** + * \class SharedMemObject + * \brief Helper class to allocate an object in shareable memory + * \tparam The object type + * + * The SharedMemObject class is a specialization of the SharedMem class that + * wraps an object of type \a T and constructs it in shareable memory. It uses + * the same underlying memory allocation and sharing mechanism as the SharedMem + * class. + * + * The wrapped object is constructed at the same time as the SharedMemObject + * instance, by forwarding the arguments passed to the SharedMemObject + * constructor. The underlying memory allocation is sized to the object \a T + * size. The bool() operator should be used to check the allocation was + * successful. The object can be accessed using the dereference operators + * operator*() and operator->(). + * + * While no restriction on the type \a T is enforced, not all types are suitable + * for sharing between multiple processes. Most notably, any object type that + * contains pointer or reference members will likely cause issues. Even if those + * members refer to other members of the same object, the shared memory will be + * mapped at different addresses in different processes, and the pointers will + * not be valid. + * + * A new anonymous file is created for every SharedMemObject instance. If there + * is a need to share a large number of small objects, these objects should be + * grouped into a single larger object to limit the number of file descriptors. + * + * To share the object with other processes, see the SharedMem documentation. + */ + +/** + * \var SharedMemObject::kSize + * \brief The size of the object stored in shared memory + */ + +/** + * \fn SharedMemObject::SharedMemObject(const std::string &name, Args &&...args) + * \brief Construct a SharedMemObject + * \param[in] name Name of the SharedMemObject + * \param[in] args Arguments to pass to the constructor of the object T + * + * The \a name is used for debugging purpose only. Multiple SharedMem instances + * can have the same name. + */ + +/** + * \fn SharedMemObject::SharedMemObject(SharedMemObject<T> &&rhs) + * \brief Move constructor for SharedMemObject + * \param[in] rhs The object to move + */ + +/** + * \fn SharedMemObject::~SharedMemObject() + * \brief Destroy the SharedMemObject instance + * + * Destroying a SharedMemObject calls the wrapped T object's destructor. While + * the underlying memory may not be freed immediately if other mappings have + * been created manually (see SharedMem::~SharedMem() for more information), the + * stored object may be modified. Depending on the ~T() destructor, accessing + * the object after destruction of the SharedMemObject causes undefined + * behaviour. It is the responsibility of the user of this class to synchronize + * with other users who have access to the shared object. + */ + +/** + * \fn SharedMemObject::operator=(SharedMemObject<T> &&rhs) + * \brief Move assignment operator for SharedMemObject + * \param[in] rhs The SharedMemObject object to take the data from + * + * Moving a SharedMemObject does not affect the stored object. + */ + +/** + * \fn SharedMemObject::operator->() + * \brief Dereference the stored object + * \return Pointer to the stored object + */ + +/** + * \fn const T *SharedMemObject::operator->() const + * \copydoc SharedMemObject::operator-> + */ + +/** + * \fn SharedMemObject::operator*() + * \brief Dereference the stored object + * \return Reference to the stored object + */ + +/** + * \fn const T &SharedMemObject::operator*() const + * \copydoc SharedMemObject::operator* + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO new file mode 100644 index 00000000..a50db668 --- /dev/null +++ b/src/libcamera/software_isp/TODO @@ -0,0 +1,208 @@ +2. Reconsider stats sharing + +>>> +void SwStatsCpu::finishFrame(void) +>>> +{ +>>> + *sharedStats_ = stats_; +>> +>> Is it more efficient to copy the stats instead of operating directly on +>> the shared memory ? +> +> I inherited doing things this way from Andrey. I kept this because +> we don't really have any synchronization with the IPA reading this. +> +> So the idea is to only touch this when the next set of statistics +> is ready since we don't know when the IPA is done with accessing +> the previous set of statistics ... +> +> This is both something which seems mostly a theoretic problem, +> yet also definitely something which I think we need to fix. +> +> Maybe use a ringbuffer of stats buffers and pass the index into +> the ringbuffer to the emit signal ? + +That would match how we deal with hardware ISPs, and I think that's a +good idea. It will help decoupling the processing side from the IPA. + +--- + +3. Remove statsReady signal + +> class SwStatsCpu +> { +> /** +> * \brief Signals that the statistics are ready +> */ +> Signal<> statsReady; + +But better, I wonder if the signal could be dropped completely. The +SwStatsCpu class does not operate asynchronously. Shouldn't whoever +calls the finishFrame() function then handle emitting the signal ? + +Now, the trouble is that this would be the DebayerCpu class, whose name +doesn't indicate as a prime candidate to handle stats. However, it +already exposes a getStatsFD() function, so we're already calling for +trouble :-) Either that should be moved to somewhere else, or the class +should be renamed. Considering that the class applies colour gains in +addition to performing the interpolation, it may be more of a naming +issue. + +Removing the signal and refactoring those classes doesn't have to be +addressed now, I think it would be part of a larger refactoring +(possibly also considering platforms that have no ISP but can produce +stats in hardware, such as the i.MX7), but please keep it on your radar. + +--- + +5. Store ISP parameters in per-frame buffers + +> /** +> * \fn void Debayer::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) +> * \brief Process the bayer data into the requested format. +> * \param[in] input The input buffer. +> * \param[in] output The output buffer. +> * \param[in] params The parameters to be used in debayering. +> * +> * \note DebayerParams is passed by value deliberately so that a copy is passed +> * when this is run in another thread by invokeMethod(). +> */ + +Possibly something to address later, by storing ISP parameters in +per-frame buffers like we do for hardware ISPs. + +--- + +6. Input buffer copying configuration + +> DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats) +> : stats_(std::move(stats)), gammaCorrection_(1.0) +> { +> enableInputMemcpy_ = true; + +Set this appropriately and/or make it configurable. + +--- + +7. Performance measurement configuration + +> void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) +> /* Measure before emitting signals */ +> if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure && +> ++measuredFrames_ > DebayerCpu::kFramesToSkip) { +> timespec frameEndTime = {}; +> clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime); +> frameProcessTime_ += timeDiff(frameEndTime, frameStartTime); +> if (measuredFrames_ == DebayerCpu::kLastFrameToMeasure) { +> const unsigned int measuredFrames = DebayerCpu::kLastFrameToMeasure - +> DebayerCpu::kFramesToSkip; +> LOG(Debayer, Info) +> << "Processed " << measuredFrames +> << " frames in " << frameProcessTime_ / 1000 << "us, " +> << frameProcessTime_ / (1000 * measuredFrames) +> << " us/frame"; +> } +> } + +I wonder if there would be a way to control at runtime when/how to +perform those measurements. Maybe that's a bit overkill. + +--- + +8. DebayerCpu cleanups + +> >> class DebayerCpu : public Debayer, public Object +> >> const SharedFD &getStatsFD() { return stats_->getStatsFD(); } +> > +> > This, +> +> Note the statistics pass-through stuff is sort of a necessary evil +> since we want one main loop going over the data line by line and +> doing both debayering as well as stats while the line is still +> hot in the l2 cache. And things like the process2() and process4() +> loops are highly CPU debayering specific so I don't think we should +> move those out of the CpuDebayer code. + +Yes, that I understood from the review. "necessary evil" is indeed the +right term :-) I expect it will take quite some design skills to balance +the need for performances and the need for a maintainable architecture. + +> > plus the fact that this class handles colour gains and gamma, +> > makes me thing we have either a naming issue, or an architecture issue. +> +> I agree that this does a bit more then debayering, although +> the debayering really is the main thing it does. +> +> I guess the calculation of the rgb lookup tables which do the +> color gains and gamma could be moved outside of this class, +> that might even be beneficial for GPU based debayering assuming +> that that is going to use rgb lookup tables too (it could +> implement actual color gains + gamma correction in some different +> way). +> +> I think this falls under the lets wait until we have a GPU +> based SoftISP MVP/POC and then do some refactoring to see which +> bits should go where. + +--- + +8. Decouple pipeline and IPA naming + +> The current src/ipa/meson.build assumes the IPA name to match the +> pipeline name. For this reason "-Dipas=simple" is used for the +> Soft IPA module. + +This should be addressed. + +--- + +9. Doxyfile cleanup + +>> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in +>> index a86ea6c1..2be8d47b 100644 +>> --- a/Documentation/Doxyfile.in +>> +++ b/Documentation/Doxyfile.in +>> @@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ +>> @TOP_SRCDIR@/src/libcamera/pipeline/ \ +>> @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ +>> @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ +>> + @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \ +> Why is this needed ? +> +>> @TOP_BUILDDIR@/src/libcamera/proxy/ +>> EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ +>> diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build +>> index f3b4881c..3352d08f 100644 +>> --- a/include/libcamera/ipa/meson.build +>> +++ b/include/libcamera/ipa/meson.build +>> @@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = { +>> 'ipu3': 'ipu3.mojom', +>> 'rkisp1': 'rkisp1.mojom', +>> 'rpi/vc4': 'raspberrypi.mojom', +>> + 'simple': 'soft.mojom', +>> 'vimc': 'vimc.mojom', +>> } +>> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom +>> new file mode 100644 +>> index 00000000..c249bd75 +>> --- /dev/null +>> +++ b/include/libcamera/ipa/soft.mojom +>> @@ -0,0 +1,28 @@ +>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +>> + +>> +/* +>> + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry. +> Ah that's why. + +Yes, because, well... all the other IPAs were doing that... + +> It doesn't have to be done before merging, but could you +> address this sooner than later ? + +--- + +13. Improve black level and colour gains application + +I think the black level should eventually be moved before debayering, and +ideally the colour gains as well. I understand the need for optimizations to +lower the CPU consumption, but at the same time I don't feel comfortable +building up on top of an implementation that may work a bit more by chance than +by correctness, as that's not very maintainable. diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp new file mode 100644 index 00000000..e9e18c48 --- /dev/null +++ b/src/libcamera/software_isp/debayer.cpp @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023-2025 Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * debayer base class + */ + +#include "debayer.h" + +namespace libcamera { + +/** + * \struct DebayerParams + * \brief Struct to hold the debayer parameters. + */ + +/** + * \var DebayerParams::kRGBLookupSize + * \brief Size of a color lookup table + */ + +/** + * \struct DebayerParams::CcmColumn + * \brief Type of a single column of a color correction matrix (CCM) + * + * When multiplying an input pixel, columns in the CCM correspond to the red, + * green or blue component of input pixel values, while rows correspond to the + * red, green or blue components of the output pixel values. The members of the + * CcmColumn structure are named after the colour components of the output pixel + * values they correspond to. + */ + +/** + * \var DebayerParams::CcmColumn::r + * \brief Red (first) component of a CCM column + */ + +/** + * \var DebayerParams::CcmColumn::g + * \brief Green (second) component of a CCM column + */ + +/** + * \var DebayerParams::CcmColumn::b + * \brief Blue (third) component of a CCM column + */ + +/** + * \typedef DebayerParams::LookupTable + * \brief Type of the lookup tables for single lookup values + */ + +/** + * \typedef DebayerParams::CcmLookupTable + * \brief Type of the CCM lookup tables for red, green, blue values + */ + +/** + * \var DebayerParams::red + * \brief Lookup table for red color, mapping input values to output values + */ + +/** + * \var DebayerParams::green + * \brief Lookup table for green color, mapping input values to output values + */ + +/** + * \var DebayerParams::blue + * \brief Lookup table for blue color, mapping input values to output values + */ + +/** + * \var DebayerParams::redCcm + * \brief Lookup table for the CCM red column, mapping input values to output values + */ + +/** + * \var DebayerParams::greenCcm + * \brief Lookup table for the CCM green column, mapping input values to output values + */ + +/** + * \var DebayerParams::blueCcm + * \brief Lookup table for the CCM blue column, mapping input values to output values + */ + +/** + * \var DebayerParams::gammaLut + * \brief Gamma lookup table used with color correction matrix + */ + +/** + * \class Debayer + * \brief Base debayering class + * + * Base class that provides functions for setting up the debayering process. + */ + +LOG_DEFINE_CATEGORY(Debayer) + +Debayer::~Debayer() +{ +} + +/** + * \fn int Debayer::configure() + * \brief Configure the debayer object according to the passed in parameters + * \param[in] inputCfg The input configuration + * \param[in] outputCfgs The output configurations + * \param[in] ccmEnabled Whether a color correction matrix is applied + * + * \return 0 on success, a negative errno on failure + */ + +/** + * \fn Size Debayer::patternSize(PixelFormat inputFormat) + * \brief Get the width and height at which the bayer pattern repeats + * \param[in] inputFormat The input format + * + * Valid sizes are: 2x2, 4x2 or 4x4. + * + * \return Pattern size or an empty size for unsupported inputFormats + */ + +/** + * \fn std::vector<PixelFormat> Debayer::formats(PixelFormat inputFormat) + * \brief Get the supported output formats + * \param[in] inputFormat The input format + * + * \return All supported output formats or an empty vector if there are none + */ + +/** + * \fn std::tuple<unsigned int, unsigned int> Debayer::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) + * \brief Get the stride and the frame size + * \param[in] outputFormat The output format + * \param[in] size The output size + * + * \return A tuple of the stride and the frame size, or a tuple with 0,0 if + * there is no valid output config + */ + +/** + * \fn void Debayer::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) + * \brief Process the bayer data into the requested format + * \param[in] frame The frame number + * \param[in] input The input buffer + * \param[in] output The output buffer + * \param[in] params The parameters to be used in debayering + * + * \note DebayerParams is passed by value deliberately so that a copy is passed + * when this is run in another thread by invokeMethod(). + */ + +/** + * \fn virtual SizeRange Debayer::sizes(PixelFormat inputFormat, const Size &inputSize) + * \brief Get the supported output sizes for the given input format and size + * \param[in] inputFormat The input format + * \param[in] inputSize The input size + * + * \return The valid size ranges or an empty range if there are none + */ + +/** + * \var Signal<FrameBuffer *> Debayer::inputBufferReady + * \brief Signals when the input buffer is ready + */ + +/** + * \var Signal<FrameBuffer *> Debayer::outputBufferReady + * \brief Signals when the output buffer is ready + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h new file mode 100644 index 00000000..ba033d44 --- /dev/null +++ b/src/libcamera/software_isp/debayer.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023, Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * debayering base class + */ + +#pragma once + +#include <stdint.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/signal.h> + +#include <libcamera/geometry.h> +#include <libcamera/stream.h> + +#include "libcamera/internal/software_isp/debayer_params.h" + +namespace libcamera { + +class FrameBuffer; + +LOG_DECLARE_CATEGORY(Debayer) + +class Debayer +{ +public: + virtual ~Debayer() = 0; + + virtual int configure(const StreamConfiguration &inputCfg, + const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, + bool ccmEnabled) = 0; + + virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0; + + virtual std::tuple<unsigned int, unsigned int> + strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0; + + virtual void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0; + + virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0; + + Signal<FrameBuffer *> inputBufferReady; + Signal<FrameBuffer *> outputBufferReady; + +private: + virtual Size patternSize(PixelFormat inputFormat) = 0; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp new file mode 100644 index 00000000..66f6038c --- /dev/null +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -0,0 +1,875 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023-2025 Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * CPU based debayering class + */ + +#include "debayer_cpu.h" + +#include <algorithm> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <time.h> +#include <utility> + +#include <linux/dma-buf.h> + +#include <libcamera/formats.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/mapped_framebuffer.h" + +namespace libcamera { + +/** + * \class DebayerCpu + * \brief Class for debayering on the CPU + * + * Implementation for CPU based debayering + */ + +/** + * \brief Constructs a DebayerCpu object + * \param[in] stats Pointer to the stats object to use + */ +DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats) + : stats_(std::move(stats)) +{ + /* + * Reading from uncached buffers may be very slow. + * In such a case, it's better to copy input buffer data to normal memory. + * But in case of cached buffers, copying the data is unnecessary overhead. + * enable_input_memcpy_ makes this behavior configurable. At the moment, we + * always set it to true as the safer choice but this should be changed in + * future. + */ + enableInputMemcpy_ = true; + + /* Initialize color lookup tables */ + for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { + red_[i] = green_[i] = blue_[i] = i; + redCcm_[i] = { static_cast<int16_t>(i), 0, 0 }; + greenCcm_[i] = { 0, static_cast<int16_t>(i), 0 }; + blueCcm_[i] = { 0, 0, static_cast<int16_t>(i) }; + } +} + +DebayerCpu::~DebayerCpu() = default; + +#define DECLARE_SRC_POINTERS(pixel_t) \ + const pixel_t *prev = (const pixel_t *)src[0] + xShift_; \ + const pixel_t *curr = (const pixel_t *)src[1] + xShift_; \ + const pixel_t *next = (const pixel_t *)src[2] + xShift_; + +#define GAMMA(value) \ + *dst++ = gammaLut_[std::clamp(value, 0, static_cast<int>(gammaLut_.size()) - 1)] + +#define STORE_PIXEL(b_, g_, r_) \ + if constexpr (ccmEnabled) { \ + const DebayerParams::CcmColumn &blue = blueCcm_[b_]; \ + const DebayerParams::CcmColumn &green = greenCcm_[g_]; \ + const DebayerParams::CcmColumn &red = redCcm_[r_]; \ + GAMMA(blue.b + green.b + red.b); \ + GAMMA(blue.g + green.g + red.g); \ + GAMMA(blue.r + green.r + red.r); \ + } else { \ + *dst++ = blue_[b_]; \ + *dst++ = green_[g_]; \ + *dst++ = red_[r_]; \ + } \ + if constexpr (addAlphaByte) \ + *dst++ = 255; \ + x++; + +/* + * RGR + * GBG + * RGR + */ +#define BGGR_BGR888(p, n, div) \ + STORE_PIXEL( \ + curr[x] / (div), \ + (prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div)), \ + (prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))) + +/* + * GBG + * RGR + * GBG + */ +#define GRBG_BGR888(p, n, div) \ + STORE_PIXEL( \ + (prev[x] + next[x]) / (2 * (div)), \ + curr[x] / (div), \ + (curr[x - p] + curr[x + n]) / (2 * (div))) + +/* + * GRG + * BGB + * GRG + */ +#define GBRG_BGR888(p, n, div) \ + STORE_PIXEL( \ + (curr[x - p] + curr[x + n]) / (2 * (div)), \ + curr[x] / (div), \ + (prev[x] + next[x]) / (2 * (div))) + +/* + * BGB + * GRG + * BGB + */ +#define RGGB_BGR888(p, n, div) \ + STORE_PIXEL( \ + (prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div)), \ + (prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div)), \ + curr[x] / (div)) + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint8_t) + + for (int x = 0; x < (int)window_.width;) { + BGGR_BGR888(1, 1, 1) + GBRG_BGR888(1, 1, 1) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint8_t) + + for (int x = 0; x < (int)window_.width;) { + GRBG_BGR888(1, 1, 1) + RGGB_BGR888(1, 1, 1) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint16_t) + + for (int x = 0; x < (int)window_.width;) { + /* divide values by 4 for 10 -> 8 bpp value */ + BGGR_BGR888(1, 1, 4) + GBRG_BGR888(1, 1, 4) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint16_t) + + for (int x = 0; x < (int)window_.width;) { + /* divide values by 4 for 10 -> 8 bpp value */ + GRBG_BGR888(1, 1, 4) + RGGB_BGR888(1, 1, 4) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint16_t) + + for (int x = 0; x < (int)window_.width;) { + /* divide values by 16 for 12 -> 8 bpp value */ + BGGR_BGR888(1, 1, 16) + GBRG_BGR888(1, 1, 16) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + DECLARE_SRC_POINTERS(uint16_t) + + for (int x = 0; x < (int)window_.width;) { + /* divide values by 16 for 12 -> 8 bpp value */ + GRBG_BGR888(1, 1, 16) + RGGB_BGR888(1, 1, 16) + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const int widthInBytes = window_.width * 5 / 4; + const uint8_t *prev = src[0]; + const uint8_t *curr = src[1]; + const uint8_t *next = src[2]; + + /* + * For the first pixel getting a pixel from the previous column uses + * x - 2 to skip the 5th byte with least-significant bits for 4 pixels. + * Same for last pixel (uses x + 2) and looking at the next column. + */ + for (int x = 0; x < widthInBytes;) { + /* First pixel */ + BGGR_BGR888(2, 1, 1) + /* Second pixel BGGR -> GBRG */ + GBRG_BGR888(1, 1, 1) + /* Same thing for third and fourth pixels */ + BGGR_BGR888(1, 1, 1) + GBRG_BGR888(1, 2, 1) + /* Skip 5th src byte with 4 x 2 least-significant-bits */ + x++; + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const int widthInBytes = window_.width * 5 / 4; + const uint8_t *prev = src[0]; + const uint8_t *curr = src[1]; + const uint8_t *next = src[2]; + + for (int x = 0; x < widthInBytes;) { + /* First pixel */ + GRBG_BGR888(2, 1, 1) + /* Second pixel GRBG -> RGGB */ + RGGB_BGR888(1, 1, 1) + /* Same thing for third and fourth pixels */ + GRBG_BGR888(1, 1, 1) + RGGB_BGR888(1, 2, 1) + /* Skip 5th src byte with 4 x 2 least-significant-bits */ + x++; + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const int widthInBytes = window_.width * 5 / 4; + const uint8_t *prev = src[0]; + const uint8_t *curr = src[1]; + const uint8_t *next = src[2]; + + for (int x = 0; x < widthInBytes;) { + /* Even pixel */ + GBRG_BGR888(2, 1, 1) + /* Odd pixel GBGR -> BGGR */ + BGGR_BGR888(1, 1, 1) + /* Same thing for next 2 pixels */ + GBRG_BGR888(1, 1, 1) + BGGR_BGR888(1, 2, 1) + /* Skip 5th src byte with 4 x 2 least-significant-bits */ + x++; + } +} + +template<bool addAlphaByte, bool ccmEnabled> +void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) +{ + const int widthInBytes = window_.width * 5 / 4; + const uint8_t *prev = src[0]; + const uint8_t *curr = src[1]; + const uint8_t *next = src[2]; + + for (int x = 0; x < widthInBytes;) { + /* Even pixel */ + RGGB_BGR888(2, 1, 1) + /* Odd pixel RGGB -> GRBG */ + GRBG_BGR888(1, 1, 1) + /* Same thing for next 2 pixels */ + RGGB_BGR888(1, 1, 1) + GRBG_BGR888(1, 2, 1) + /* Skip 5th src byte with 4 x 2 least-significant-bits */ + x++; + } +} + +static bool isStandardBayerOrder(BayerFormat::Order order) +{ + return order == BayerFormat::BGGR || order == BayerFormat::GBRG || + order == BayerFormat::GRBG || order == BayerFormat::RGGB; +} + +/* + * Setup the Debayer object according to the passed in parameters. + * Return 0 on success, a negative errno value on failure + * (unsupported parameters). + */ +int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config) +{ + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && + bayerFormat.packing == BayerFormat::Packing::None && + isStandardBayerOrder(bayerFormat.order)) { + config.bpp = (bayerFormat.bitDepth + 7) & ~7; + config.patternSize.width = 2; + config.patternSize.height = 2; + config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, + formats::XRGB8888, + formats::ARGB8888, + formats::BGR888, + formats::XBGR8888, + formats::ABGR8888 }); + return 0; + } + + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2 && + isStandardBayerOrder(bayerFormat.order)) { + config.bpp = 10; + config.patternSize.width = 4; /* 5 bytes per *4* pixels */ + config.patternSize.height = 2; + config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, + formats::XRGB8888, + formats::ARGB8888, + formats::BGR888, + formats::XBGR8888, + formats::ABGR8888 }); + return 0; + } + + LOG(Debayer, Info) + << "Unsupported input format " << inputFormat.toString(); + return -EINVAL; +} + +int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config) +{ + if (outputFormat == formats::RGB888 || outputFormat == formats::BGR888) { + config.bpp = 24; + return 0; + } + + if (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 || + outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) { + config.bpp = 32; + return 0; + } + + LOG(Debayer, Info) + << "Unsupported output format " << outputFormat.toString(); + return -EINVAL; +} + +/* + * Check for standard Bayer orders and set xShift_ and swap debayer0/1, so that + * a single pair of BGGR debayer functions can be used for all 4 standard orders. + */ +int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order) +{ + switch (order) { + case BayerFormat::BGGR: + break; + case BayerFormat::GBRG: + xShift_ = 1; /* BGGR -> GBRG */ + break; + case BayerFormat::GRBG: + std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */ + break; + case BayerFormat::RGGB: + xShift_ = 1; /* BGGR -> GBRG */ + std::swap(debayer0_, debayer1_); /* GBRG -> RGGB */ + break; + default: + return -EINVAL; + } + + return 0; +} + +#define SET_DEBAYER_METHODS(method0, method1) \ + debayer0_ = addAlphaByte \ + ? (ccmEnabled ? &DebayerCpu::method0<true, true> : &DebayerCpu::method0<true, false>) \ + : (ccmEnabled ? &DebayerCpu::method0<false, true> : &DebayerCpu::method0<false, false>); \ + debayer1_ = addAlphaByte \ + ? (ccmEnabled ? &DebayerCpu::method1<true, true> : &DebayerCpu::method1<true, false>) \ + : (ccmEnabled ? &DebayerCpu::method1<false, true> : &DebayerCpu::method1<false, false>); + +int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, + PixelFormat outputFormat, + bool ccmEnabled) +{ + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputFormat); + bool addAlphaByte = false; + + xShift_ = 0; + swapRedBlueGains_ = false; + + auto invalidFmt = []() -> int { + LOG(Debayer, Error) << "Unsupported input output format combination"; + return -EINVAL; + }; + + switch (outputFormat) { + case formats::XRGB8888: + case formats::ARGB8888: + addAlphaByte = true; + [[fallthrough]]; + case formats::RGB888: + break; + case formats::XBGR8888: + case formats::ABGR8888: + addAlphaByte = true; + [[fallthrough]]; + case formats::BGR888: + /* Swap R and B in bayer order to generate BGR888 instead of RGB888 */ + swapRedBlueGains_ = true; + + switch (bayerFormat.order) { + case BayerFormat::BGGR: + bayerFormat.order = BayerFormat::RGGB; + break; + case BayerFormat::GBRG: + bayerFormat.order = BayerFormat::GRBG; + break; + case BayerFormat::GRBG: + bayerFormat.order = BayerFormat::GBRG; + break; + case BayerFormat::RGGB: + bayerFormat.order = BayerFormat::BGGR; + break; + default: + return invalidFmt(); + } + break; + default: + return invalidFmt(); + } + + if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && + bayerFormat.packing == BayerFormat::Packing::None && + isStandardBayerOrder(bayerFormat.order)) { + switch (bayerFormat.bitDepth) { + case 8: + SET_DEBAYER_METHODS(debayer8_BGBG_BGR888, debayer8_GRGR_BGR888) + break; + case 10: + SET_DEBAYER_METHODS(debayer10_BGBG_BGR888, debayer10_GRGR_BGR888) + break; + case 12: + SET_DEBAYER_METHODS(debayer12_BGBG_BGR888, debayer12_GRGR_BGR888) + break; + } + setupStandardBayerOrder(bayerFormat.order); + return 0; + } + + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2) { + switch (bayerFormat.order) { + case BayerFormat::BGGR: + SET_DEBAYER_METHODS(debayer10P_BGBG_BGR888, debayer10P_GRGR_BGR888) + return 0; + case BayerFormat::GBRG: + SET_DEBAYER_METHODS(debayer10P_GBGB_BGR888, debayer10P_RGRG_BGR888) + return 0; + case BayerFormat::GRBG: + SET_DEBAYER_METHODS(debayer10P_GRGR_BGR888, debayer10P_BGBG_BGR888) + return 0; + case BayerFormat::RGGB: + SET_DEBAYER_METHODS(debayer10P_RGRG_BGR888, debayer10P_GBGB_BGR888) + return 0; + default: + break; + } + } + + return invalidFmt(); +} + +int DebayerCpu::configure(const StreamConfiguration &inputCfg, + const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, + bool ccmEnabled) +{ + if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) + return -EINVAL; + + if (stats_->configure(inputCfg) != 0) + return -EINVAL; + + const Size &statsPatternSize = stats_->patternSize(); + if (inputConfig_.patternSize.width != statsPatternSize.width || + inputConfig_.patternSize.height != statsPatternSize.height) { + LOG(Debayer, Error) + << "mismatching stats and debayer pattern sizes for " + << inputCfg.pixelFormat.toString(); + return -EINVAL; + } + + inputConfig_.stride = inputCfg.stride; + + if (outputCfgs.size() != 1) { + LOG(Debayer, Error) + << "Unsupported number of output streams: " + << outputCfgs.size(); + return -EINVAL; + } + + const StreamConfiguration &outputCfg = outputCfgs[0]; + SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size); + std::tie(outputConfig_.stride, outputConfig_.frameSize) = + strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size); + + if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) { + LOG(Debayer, Error) + << "Invalid output size/stride: " + << "\n " << outputCfg.size << " (" << outSizeRange << ")" + << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")"; + return -EINVAL; + } + + int ret = setDebayerFunctions(inputCfg.pixelFormat, + outputCfg.pixelFormat, + ccmEnabled); + if (ret != 0) + return -EINVAL; + + window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & + ~(inputConfig_.patternSize.width - 1); + window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & + ~(inputConfig_.patternSize.height - 1); + window_.width = outputCfg.size.width; + window_.height = outputCfg.size.height; + + /* Don't pass x,y since process() already adjusts src before passing it */ + stats_->setWindow(Rectangle(window_.size())); + + /* pad with patternSize.Width on both left and right side */ + lineBufferPadding_ = inputConfig_.patternSize.width * inputConfig_.bpp / 8; + lineBufferLength_ = window_.width * inputConfig_.bpp / 8 + + 2 * lineBufferPadding_; + + if (enableInputMemcpy_) { + for (unsigned int i = 0; i <= inputConfig_.patternSize.height; i++) + lineBuffers_[i].resize(lineBufferLength_); + } + + measuredFrames_ = 0; + frameProcessTime_ = 0; + + return 0; +} + +/* + * Get width and height at which the bayer-pattern repeats. + * Return pattern-size or an empty Size for an unsupported inputFormat. + */ +Size DebayerCpu::patternSize(PixelFormat inputFormat) +{ + DebayerCpu::DebayerInputConfig config; + + if (getInputConfig(inputFormat, config) != 0) + return {}; + + return config.patternSize; +} + +std::vector<PixelFormat> DebayerCpu::formats(PixelFormat inputFormat) +{ + DebayerCpu::DebayerInputConfig config; + + if (getInputConfig(inputFormat, config) != 0) + return std::vector<PixelFormat>(); + + return config.outputFormats; +} + +std::tuple<unsigned int, unsigned int> +DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) +{ + DebayerCpu::DebayerOutputConfig config; + + if (getOutputConfig(outputFormat, config) != 0) + return std::make_tuple(0, 0); + + /* round up to multiple of 8 for 64 bits alignment */ + unsigned int stride = (size.width * config.bpp / 8 + 7) & ~7; + + return std::make_tuple(stride, stride * size.height); +} + +void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[]) +{ + const unsigned int patternHeight = inputConfig_.patternSize.height; + + if (!enableInputMemcpy_) + return; + + for (unsigned int i = 0; i < patternHeight; i++) { + memcpy(lineBuffers_[i].data(), + linePointers[i + 1] - lineBufferPadding_, + lineBufferLength_); + linePointers[i + 1] = lineBuffers_[i].data() + lineBufferPadding_; + } + + /* Point lineBufferIndex_ to first unused lineBuffer */ + lineBufferIndex_ = patternHeight; +} + +void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src) +{ + const unsigned int patternHeight = inputConfig_.patternSize.height; + + for (unsigned int i = 0; i < patternHeight; i++) + linePointers[i] = linePointers[i + 1]; + + linePointers[patternHeight] = src + + (patternHeight / 2) * (int)inputConfig_.stride; +} + +void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[]) +{ + const unsigned int patternHeight = inputConfig_.patternSize.height; + + if (!enableInputMemcpy_) + return; + + memcpy(lineBuffers_[lineBufferIndex_].data(), + linePointers[patternHeight] - lineBufferPadding_, + lineBufferLength_); + linePointers[patternHeight] = lineBuffers_[lineBufferIndex_].data() + lineBufferPadding_; + + lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1); +} + +void DebayerCpu::process2(const uint8_t *src, uint8_t *dst) +{ + unsigned int yEnd = window_.y + window_.height; + /* Holds [0] previous- [1] current- [2] next-line */ + const uint8_t *linePointers[3]; + + /* Adjust src to top left corner of the window */ + src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; + + /* [x] becomes [x - 1] after initial shiftLinePointers() call */ + if (window_.y) { + linePointers[1] = src - inputConfig_.stride; /* previous-line */ + linePointers[2] = src; + } else { + /* window_.y == 0, use the next line as prev line */ + linePointers[1] = src + inputConfig_.stride; + linePointers[2] = src; + /* Last 2 lines also need special handling */ + yEnd -= 2; + } + + setupInputMemcpy(linePointers); + + for (unsigned int y = window_.y; y < yEnd; y += 2) { + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + stats_->processLine0(y, linePointers); + (this->*debayer0_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + (this->*debayer1_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + } + + if (window_.y == 0) { + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + stats_->processLine0(yEnd, linePointers); + (this->*debayer0_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + + shiftLinePointers(linePointers, src); + /* next line may point outside of src, use prev. */ + linePointers[2] = linePointers[0]; + (this->*debayer1_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + } +} + +void DebayerCpu::process4(const uint8_t *src, uint8_t *dst) +{ + const unsigned int yEnd = window_.y + window_.height; + /* + * This holds pointers to [0] 2-lines-up [1] 1-line-up [2] current-line + * [3] 1-line-down [4] 2-lines-down. + */ + const uint8_t *linePointers[5]; + + /* Adjust src to top left corner of the window */ + src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; + + /* [x] becomes [x - 1] after initial shiftLinePointers() call */ + linePointers[1] = src - 2 * inputConfig_.stride; + linePointers[2] = src - inputConfig_.stride; + linePointers[3] = src; + linePointers[4] = src + inputConfig_.stride; + + setupInputMemcpy(linePointers); + + for (unsigned int y = window_.y; y < yEnd; y += 4) { + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + stats_->processLine0(y, linePointers); + (this->*debayer0_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + (this->*debayer1_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + stats_->processLine2(y, linePointers); + (this->*debayer2_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + + shiftLinePointers(linePointers, src); + memcpyNextLine(linePointers); + (this->*debayer3_)(dst, linePointers); + src += inputConfig_.stride; + dst += outputConfig_.stride; + } +} + +namespace { + +inline int64_t timeDiff(timespec &after, timespec &before) +{ + return (after.tv_sec - before.tv_sec) * 1000000000LL + + (int64_t)after.tv_nsec - (int64_t)before.tv_nsec; +} + +} /* namespace */ + +void DebayerCpu::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params) +{ + timespec frameStartTime; + + if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure) { + frameStartTime = {}; + clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime); + } + + std::vector<DmaSyncer> dmaSyncers; + for (const FrameBuffer::Plane &plane : input->planes()) + dmaSyncers.emplace_back(plane.fd, DmaSyncer::SyncType::Read); + + for (const FrameBuffer::Plane &plane : output->planes()) + dmaSyncers.emplace_back(plane.fd, DmaSyncer::SyncType::Write); + + green_ = params.green; + greenCcm_ = params.greenCcm; + if (swapRedBlueGains_) { + red_ = params.blue; + blue_ = params.red; + redCcm_ = params.blueCcm; + blueCcm_ = params.redCcm; + for (unsigned int i = 0; i < 256; i++) { + std::swap(redCcm_[i].r, redCcm_[i].b); + std::swap(blueCcm_[i].r, blueCcm_[i].b); + } + } else { + red_ = params.red; + blue_ = params.blue; + redCcm_ = params.redCcm; + blueCcm_ = params.blueCcm; + } + gammaLut_ = params.gammaLut; + + /* Copy metadata from the input buffer */ + FrameMetadata &metadata = output->_d()->metadata(); + metadata.status = input->metadata().status; + metadata.sequence = input->metadata().sequence; + metadata.timestamp = input->metadata().timestamp; + + MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read); + MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write); + if (!in.isValid() || !out.isValid()) { + LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; + metadata.status = FrameMetadata::FrameError; + return; + } + + stats_->startFrame(); + + if (inputConfig_.patternSize.height == 2) + process2(in.planes()[0].data(), out.planes()[0].data()); + else + process4(in.planes()[0].data(), out.planes()[0].data()); + + metadata.planes()[0].bytesused = out.planes()[0].size(); + + dmaSyncers.clear(); + + /* Measure before emitting signals */ + if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure && + ++measuredFrames_ > DebayerCpu::kFramesToSkip) { + timespec frameEndTime = {}; + clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime); + frameProcessTime_ += timeDiff(frameEndTime, frameStartTime); + if (measuredFrames_ == DebayerCpu::kLastFrameToMeasure) { + const unsigned int measuredFrames = DebayerCpu::kLastFrameToMeasure - + DebayerCpu::kFramesToSkip; + LOG(Debayer, Info) + << "Processed " << measuredFrames + << " frames in " << frameProcessTime_ / 1000 << "us, " + << frameProcessTime_ / (1000 * measuredFrames) + << " us/frame"; + } + } + + /* + * Buffer ids are currently not used, so pass zeros as its parameter. + * + * \todo Pass real bufferId once stats buffer passing is changed. + */ + stats_->finishFrame(frame, 0); + outputBufferReady.emit(output); + inputBufferReady.emit(input); +} + +SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize) +{ + Size patternSize = this->patternSize(inputFormat); + unsigned int borderHeight = patternSize.height; + + if (patternSize.isNull()) + return {}; + + /* No need for top/bottom border with a pattern height of 2 */ + if (patternSize.height == 2) + borderHeight = 0; + + /* + * For debayer interpolation a border is kept around the entire image + * and the minimum output size is pattern-height x pattern-width. + */ + if (inputSize.width < (3 * patternSize.width) || + inputSize.height < (2 * borderHeight + patternSize.height)) { + LOG(Debayer, Warning) + << "Input format size too small: " << inputSize.toString(); + return {}; + } + + return SizeRange(Size(patternSize.width, patternSize.height), + Size((inputSize.width - 2 * patternSize.width) & ~(patternSize.width - 1), + (inputSize.height - 2 * borderHeight) & ~(patternSize.height - 1)), + patternSize.width, patternSize.height); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h new file mode 100644 index 00000000..926195e9 --- /dev/null +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023-2025 Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * CPU based debayering header + */ + +#pragma once + +#include <memory> +#include <stdint.h> +#include <vector> + +#include <libcamera/base/object.h> + +#include "libcamera/internal/bayer_format.h" + +#include "debayer.h" +#include "swstats_cpu.h" + +namespace libcamera { + +class DebayerCpu : public Debayer, public Object +{ +public: + DebayerCpu(std::unique_ptr<SwStatsCpu> stats); + ~DebayerCpu(); + + int configure(const StreamConfiguration &inputCfg, + const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, + bool ccmEnabled); + Size patternSize(PixelFormat inputFormat); + std::vector<PixelFormat> formats(PixelFormat input); + std::tuple<unsigned int, unsigned int> + strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); + void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, DebayerParams params); + SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); + + /** + * \brief Get the file descriptor for the statistics + * + * \return the file descriptor pointing to the statistics + */ + const SharedFD &getStatsFD() { return stats_->getStatsFD(); } + + /** + * \brief Get the output frame size + * + * \return The output frame size + */ + unsigned int frameSize() { return outputConfig_.frameSize; } + +private: + /** + * \brief Called to debayer 1 line of Bayer input data to output format + * \param[out] dst Pointer to the start of the output line to write + * \param[in] src The input data + * + * Input data is an array of (patternSize_.height + 1) src + * pointers each pointing to a line in the Bayer source. The middle + * element of the array will point to the actual line being processed. + * Earlier element(s) will point to the previous line(s) and later + * element(s) to the next line(s). + * + * These functions take an array of src pointers, rather than + * a single src pointer + a stride for the source, so that when the src + * is slow uncached memory it can be copied to faster memory before + * debayering. Debayering a standard 2x2 Bayer pattern requires access + * to the previous and next src lines for interpolating the missing + * colors. To allow copying the src lines only once 3 temporary buffers + * each holding a single line are used, re-using the oldest buffer for + * the next line and the pointers are swizzled so that: + * src[0] = previous-line, src[1] = currrent-line, src[2] = next-line. + * This way the 3 pointers passed to the debayer functions form + * a sliding window over the src avoiding the need to copy each + * line more than once. + * + * Similarly for bayer patterns which repeat every 4 lines, 5 src + * pointers are passed holding: src[0] = 2-lines-up, src[1] = 1-line-up + * src[2] = current-line, src[3] = 1-line-down, src[4] = 2-lines-down. + */ + using debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]); + + /* 8-bit raw bayer format */ + template<bool addAlphaByte, bool ccmEnabled> + void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 10-bit raw bayer format */ + template<bool addAlphaByte, bool ccmEnabled> + void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + /* unpacked 12-bit raw bayer format */ + template<bool addAlphaByte, bool ccmEnabled> + void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ + template<bool addAlphaByte, bool ccmEnabled> + void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); + template<bool addAlphaByte, bool ccmEnabled> + void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); + + struct DebayerInputConfig { + Size patternSize; + unsigned int bpp; /* Memory used per pixel, not precision */ + unsigned int stride; + std::vector<PixelFormat> outputFormats; + }; + + struct DebayerOutputConfig { + unsigned int bpp; /* Memory used per pixel, not precision */ + unsigned int stride; + unsigned int frameSize; + }; + + int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); + int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config); + int setupStandardBayerOrder(BayerFormat::Order order); + int setDebayerFunctions(PixelFormat inputFormat, + PixelFormat outputFormat, + bool ccmEnabled); + void setupInputMemcpy(const uint8_t *linePointers[]); + void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); + void memcpyNextLine(const uint8_t *linePointers[]); + void process2(const uint8_t *src, uint8_t *dst); + void process4(const uint8_t *src, uint8_t *dst); + + /* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */ + static constexpr unsigned int kMaxLineBuffers = 5; + + DebayerParams::LookupTable red_; + DebayerParams::LookupTable green_; + DebayerParams::LookupTable blue_; + DebayerParams::CcmLookupTable redCcm_; + DebayerParams::CcmLookupTable greenCcm_; + DebayerParams::CcmLookupTable blueCcm_; + DebayerParams::LookupTable gammaLut_; + debayerFn debayer0_; + debayerFn debayer1_; + debayerFn debayer2_; + debayerFn debayer3_; + Rectangle window_; + DebayerInputConfig inputConfig_; + DebayerOutputConfig outputConfig_; + std::unique_ptr<SwStatsCpu> stats_; + std::vector<uint8_t> lineBuffers_[kMaxLineBuffers]; + unsigned int lineBufferLength_; + unsigned int lineBufferPadding_; + unsigned int lineBufferIndex_; + unsigned int xShift_; /* Offset of 0/1 applied to window_.x */ + bool enableInputMemcpy_; + bool swapRedBlueGains_; + unsigned int measuredFrames_; + int64_t frameProcessTime_; + /* Skip 30 frames for things to stabilize then measure 30 frames */ + static constexpr unsigned int kFramesToSkip = 30; + static constexpr unsigned int kLastFrameToMeasure = 60; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build new file mode 100644 index 00000000..aac7eda7 --- /dev/null +++ b/src/libcamera/software_isp/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: CC0-1.0 + +softisp_enabled = pipelines.contains('simple') +summary({'SoftISP support' : softisp_enabled}, section : 'Configuration') + +if not softisp_enabled + subdir_done() +endif + +libcamera_internal_sources += files([ + 'debayer.cpp', + 'debayer_cpu.cpp', + 'software_isp.cpp', + 'swstats_cpu.cpp', +]) diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp new file mode 100644 index 00000000..28e2a360 --- /dev/null +++ b/src/libcamera/software_isp/software_isp.cpp @@ -0,0 +1,422 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * + * Simple software ISP implementation + */ + +#include "libcamera/internal/software_isp/software_isp.h" + +#include <cmath> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/thread.h> + +#include <libcamera/controls.h> +#include <libcamera/formats.h> +#include <libcamera/stream.h> + +#include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/ipa_manager.h" +#include "libcamera/internal/software_isp/debayer_params.h" + +#include "debayer_cpu.h" + +/** + * \file software_isp.cpp + * \brief Simple software ISP implementation + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(SoftwareIsp) + +/** + * \class SoftwareIsp + * \brief Class for the Software ISP + */ + +/** + * \var SoftwareIsp::inputBufferReady + * \brief A signal emitted when the input frame buffer completes + */ + +/** + * \var SoftwareIsp::outputBufferReady + * \brief A signal emitted when the output frame buffer completes + */ + +/** + * \var SoftwareIsp::ispStatsReady + * \brief A signal emitted when the statistics for IPA are ready + */ + +/** + * \var SoftwareIsp::metadataReady + * \brief A signal emitted when the metadata for IPA is ready + */ + +/** + * \var SoftwareIsp::setSensorControls + * \brief A signal emitted when the values to write to the sensor controls are + * ready + */ + +/** + * \brief Constructs SoftwareIsp object + * \param[in] pipe The pipeline handler in use + * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline + * \param[out] ipaControls The IPA controls to update + * handler + */ +SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, + ControlInfoMap *ipaControls) + : dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap | + DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | + DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) +{ + /* + * debayerParams_ must be initialized because the initial value is used for + * the first two frames, i.e. until stats processing starts providing its + * own parameters. + * + * \todo This should be handled in the same place as the related + * operations, in the IPA module. + */ + std::array<uint8_t, 256> gammaTable; + for (unsigned int i = 0; i < 256; i++) + gammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5); + for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) { + debayerParams_.red[i] = gammaTable[i]; + debayerParams_.green[i] = gammaTable[i]; + debayerParams_.blue[i] = gammaTable[i]; + } + + if (!dmaHeap_.isValid()) { + LOG(SoftwareIsp, Error) << "Failed to create DmaBufAllocator object"; + return; + } + + sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params"); + if (!sharedParams_) { + LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters"; + return; + } + + auto stats = std::make_unique<SwStatsCpu>(); + if (!stats->isValid()) { + LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object"; + return; + } + stats->statsReady.connect(this, &SoftwareIsp::statsReady); + + debayer_ = std::make_unique<DebayerCpu>(std::move(stats)); + debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady); + debayer_->outputBufferReady.connect(this, &SoftwareIsp::outputReady); + + ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0); + if (!ipa_) { + LOG(SoftwareIsp, Error) + << "Creating IPA for software ISP failed"; + debayer_.reset(); + return; + } + + /* + * The API tuning file is made from the sensor name. If the tuning file + * isn't found, fall back to the 'uncalibrated' file. + */ + std::string ipaTuningFile = + ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml"); + + IPACameraSensorInfo sensorInfo{}; + int ret = sensor->sensorInfo(&sensorInfo); + if (ret) { + LOG(SoftwareIsp, Error) << "Camera sensor information not available"; + return; + } + + ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() }, + debayer_->getStatsFD(), + sharedParams_.fd(), + sensorInfo, + sensor->controls(), + ipaControls, + &ccmEnabled_); + if (ret) { + LOG(SoftwareIsp, Error) << "IPA init failed"; + debayer_.reset(); + return; + } + + ipa_->setIspParams.connect(this, &SoftwareIsp::saveIspParams); + ipa_->metadataReady.connect(this, + [this](uint32_t frame, const ControlList &metadata) { + metadataReady.emit(frame, metadata); + }); + ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls); + + debayer_->moveToThread(&ispWorkerThread_); +} + +SoftwareIsp::~SoftwareIsp() +{ + /* make sure to destroy the DebayerCpu before the ispWorkerThread_ is gone */ + debayer_.reset(); +} + +/** + * \fn int SoftwareIsp::loadConfiguration([[maybe_unused]] const std::string &filename) + * \brief Load a configuration from a file + * \param[in] filename The file to load the configuration data from + * + * Currently is a stub doing nothing and always returning "success". + * + * \return 0 on success + */ + +/** + * \brief Process the statistics gathered + * \param[in] frame The frame number + * \param[in] bufferId ID of the statistics buffer + * \param[in] sensorControls The sensor controls + * + * Requests the IPA to calculate new parameters for ISP and new control + * values for the sensor. + */ +void SoftwareIsp::processStats(const uint32_t frame, const uint32_t bufferId, + const ControlList &sensorControls) +{ + ASSERT(ipa_); + ipa_->processStats(frame, bufferId, sensorControls); +} + +/** + * \brief Check the validity of Software Isp object + * \return True if Software Isp is valid, false otherwise + */ +bool SoftwareIsp::isValid() const +{ + return !!debayer_; +} + +/** + * \brief Get the output formats supported for the given input format + * \param[in] inputFormat The input format + * \return All the supported output formats or an empty vector if there are none + */ +std::vector<PixelFormat> SoftwareIsp::formats(PixelFormat inputFormat) +{ + ASSERT(debayer_); + + return debayer_->formats(inputFormat); +} + +/** + * \brief Get the supported output sizes for the given input format and size + * \param[in] inputFormat The input format + * \param[in] inputSize The input frame size + * \return The valid size range or an empty range if there are none + */ +SizeRange SoftwareIsp::sizes(PixelFormat inputFormat, const Size &inputSize) +{ + ASSERT(debayer_); + + return debayer_->sizes(inputFormat, inputSize); +} + +/** + * Get the output stride and the frame size in bytes for the given output format and size + * \param[in] outputFormat The output format + * \param[in] size The output size (width and height in pixels) + * \return A tuple of the stride and the frame size in bytes, or a tuple of 0,0 + * if there is no valid output config + */ +std::tuple<unsigned int, unsigned int> +SoftwareIsp::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) +{ + ASSERT(debayer_); + + return debayer_->strideAndFrameSize(outputFormat, size); +} + +/** + * \brief Configure the SoftwareIsp object according to the passed in parameters + * \param[in] inputCfg The input configuration + * \param[in] outputCfgs The output configurations + * \param[in] configInfo The IPA configuration data, received from the pipeline + * handler + * \return 0 on success, a negative errno on failure + */ +int SoftwareIsp::configure(const StreamConfiguration &inputCfg, + const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs, + const ipa::soft::IPAConfigInfo &configInfo) +{ + ASSERT(ipa_ && debayer_); + + int ret = ipa_->configure(configInfo); + if (ret < 0) + return ret; + + return debayer_->configure(inputCfg, outputCfgs, ccmEnabled_); +} + +/** + * \brief Export the buffers from the Software ISP + * \param[in] stream Output stream exporting the buffers + * \param[in] count Number of buffers to allocate + * \param[out] buffers Vector to store the allocated buffers + * \return The number of allocated buffers on success or a negative error code + * otherwise + */ +int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) +{ + ASSERT(debayer_ != nullptr); + + /* single output for now */ + if (stream == nullptr) + return -EINVAL; + + return dmaHeap_.exportBuffers(count, { debayer_->frameSize() }, buffers); +} + +/** + * \brief Queue a request and process the control list from the application + * \param[in] frame The number of the frame which will be processed next + * \param[in] controls The controls for the \a frame + */ +void SoftwareIsp::queueRequest(const uint32_t frame, const ControlList &controls) +{ + ipa_->queueRequest(frame, controls); +} + +/** + * \brief Queue buffers to Software ISP + * \param[in] frame The frame number + * \param[in] input The input framebuffer + * \param[in] outputs The container holding the output stream pointers and + * their respective frame buffer outputs + * \return 0 on success, a negative errno on failure + */ +int SoftwareIsp::queueBuffers(uint32_t frame, FrameBuffer *input, + const std::map<const Stream *, FrameBuffer *> &outputs) +{ + /* + * Validate the outputs as a sanity check: at least one output is + * required, all outputs must reference a valid stream. + */ + if (outputs.empty()) + return -EINVAL; + + /* We only support a single stream for now. */ + if (outputs.size() != 1) + return -EINVAL; + + for (auto [stream, buffer] : outputs) { + if (!buffer) + return -EINVAL; + } + + queuedInputBuffers_.push_back(input); + + for (auto iter = outputs.begin(); iter != outputs.end(); iter++) { + FrameBuffer *const buffer = iter->second; + queuedOutputBuffers_.push_back(buffer); + process(frame, input, buffer); + } + + return 0; +} + +/** + * \brief Starts the Software ISP streaming operation + * \return 0 on success, any other value indicates an error + */ +int SoftwareIsp::start() +{ + int ret = ipa_->start(); + if (ret) + return ret; + + ispWorkerThread_.start(); + return 0; +} + +/** + * \brief Stops the Software ISP streaming operation + * + * All pending buffers are returned back as canceled before this function + * returns. + */ +void SoftwareIsp::stop() +{ + ispWorkerThread_.exit(); + ispWorkerThread_.wait(); + + Thread::current()->dispatchMessages(Message::Type::InvokeMessage, this); + + ipa_->stop(); + + for (auto buffer : queuedOutputBuffers_) { + FrameMetadata &metadata = buffer->_d()->metadata(); + metadata.status = FrameMetadata::FrameCancelled; + outputBufferReady.emit(buffer); + } + queuedOutputBuffers_.clear(); + + for (auto buffer : queuedInputBuffers_) { + FrameMetadata &metadata = buffer->_d()->metadata(); + metadata.status = FrameMetadata::FrameCancelled; + inputBufferReady.emit(buffer); + } + queuedInputBuffers_.clear(); +} + +/** + * \brief Passes the input framebuffer to the ISP worker to process + * \param[in] frame The frame number + * \param[in] input The input framebuffer + * \param[out] output The framebuffer to write the processed frame to + */ +void SoftwareIsp::process(uint32_t frame, FrameBuffer *input, FrameBuffer *output) +{ + ipa_->computeParams(frame); + debayer_->invokeMethod(&DebayerCpu::process, + ConnectionTypeQueued, frame, input, output, debayerParams_); +} + +void SoftwareIsp::saveIspParams() +{ + debayerParams_ = *sharedParams_; +} + +void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls) +{ + setSensorControls.emit(sensorControls); +} + +void SoftwareIsp::statsReady(uint32_t frame, uint32_t bufferId) +{ + ispStatsReady.emit(frame, bufferId); +} + +void SoftwareIsp::inputReady(FrameBuffer *input) +{ + ASSERT(queuedInputBuffers_.front() == input); + queuedInputBuffers_.pop_front(); + inputBufferReady.emit(input); +} + +void SoftwareIsp::outputReady(FrameBuffer *output) +{ + ASSERT(queuedOutputBuffers_.front() == output); + queuedOutputBuffers_.pop_front(); + outputBufferReady.emit(output); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp new file mode 100644 index 00000000..c520c806 --- /dev/null +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023, Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * CPU based software statistics implementation + */ + +#include "swstats_cpu.h" + +#include <libcamera/base/log.h> + +#include <libcamera/stream.h> + +#include "libcamera/internal/bayer_format.h" + +namespace libcamera { + +/** + * \class SwStatsCpu + * \brief Class for gathering statistics on the CPU + * + * CPU based software ISP statistics implementation. + * + * This class offers a configure function + functions to gather statistics on a + * line by line basis. This allows CPU based software debayering to interleave + * debayering and statistics gathering on a line by line basis while the input + * data is still hot in the cache. + * + * It is also possible to specify a window over which to gather statistics + * instead of processing the whole frame. + */ + +/** + * \fn bool SwStatsCpu::isValid() const + * \brief Gets whether the statistics object is valid + * + * \return True if it's valid, false otherwise + */ + +/** + * \fn const SharedFD &SwStatsCpu::getStatsFD() + * \brief Get the file descriptor for the statistics + * + * \return The file descriptor + */ + +/** + * \fn const Size &SwStatsCpu::patternSize() + * \brief Get the pattern size + * + * For some input-formats, e.g. Bayer data, processing is done multiple lines + * and/or columns at a time. Get width and height at which the (bayer) pattern + * repeats. Window values are rounded down to a multiple of this and the height + * also indicates if processLine2() should be called or not. + * This may only be called after a successful configure() call. + * + * \return The pattern size + */ + +/** + * \fn void SwStatsCpu::processLine0(unsigned int y, const uint8_t *src[]) + * \brief Process line 0 + * \param[in] y The y coordinate. + * \param[in] src The input data. + * + * This function processes line 0 for input formats with + * patternSize height == 1. + * It'll process line 0 and 1 for input formats with patternSize height >= 2. + * This function may only be called after a successful setWindow() call. + */ + +/** + * \fn void SwStatsCpu::processLine2(unsigned int y, const uint8_t *src[]) + * \brief Process line 2 and 3 + * \param[in] y The y coordinate. + * \param[in] src The input data. + * + * This function processes line 2 and 3 for input formats with + * patternSize height == 4. + * This function may only be called after a successful setWindow() call. + */ + +/** + * \var Signal<> SwStatsCpu::statsReady + * \brief Signals that the statistics are ready + */ + +/** + * \typedef SwStatsCpu::statsProcessFn + * \brief Called when there is data to get statistics from + * \param[in] src The input data + * + * These functions take an array of (patternSize_.height + 1) src + * pointers each pointing to a line in the source image. The middle + * element of the array will point to the actual line being processed. + * Earlier element(s) will point to the previous line(s) and later + * element(s) to the next line(s). + * + * See the documentation of DebayerCpu::debayerFn for more details. + */ + +/** + * \var unsigned int SwStatsCpu::ySkipMask_ + * \brief Skip lines where this bitmask is set in y + */ + +/** + * \var Rectangle SwStatsCpu::window_ + * \brief Statistics window, set by setWindow(), used every line + */ + +/** + * \var Size SwStatsCpu::patternSize_ + * \brief The size of the bayer pattern + * + * Valid sizes are: 2x2, 4x2 or 4x4. + */ + +/** + * \var unsigned int SwStatsCpu::xShift_ + * \brief The offset of x, applied to window_.x for bayer variants + * + * This can either be 0 or 1. + */ + +LOG_DEFINE_CATEGORY(SwStatsCpu) + +SwStatsCpu::SwStatsCpu() + : sharedStats_("softIsp_stats") +{ + if (!sharedStats_) + LOG(SwStatsCpu, Error) + << "Failed to create shared memory for statistics"; +} + +static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */ +static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */ +static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ + +#define SWSTATS_START_LINE_STATS(pixel_t) \ + pixel_t r, g, g2, b; \ + uint64_t yVal; \ + \ + uint64_t sumR = 0; \ + uint64_t sumG = 0; \ + uint64_t sumB = 0; + +#define SWSTATS_ACCUMULATE_LINE_STATS(div) \ + sumR += r; \ + sumG += g; \ + sumB += b; \ + \ + yVal = r * kRedYMul; \ + yVal += g * kGreenYMul; \ + yVal += b * kBlueYMul; \ + stats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; + +#define SWSTATS_FINISH_LINE_STATS() \ + stats_.sumR_ += sumR; \ + stats_.sumG_ += sumG; \ + stats_.sumB_ += sumB; + +void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[]) +{ + const uint8_t *src0 = src[1] + window_.x; + const uint8_t *src1 = src[2] + window_.x; + + SWSTATS_START_LINE_STATS(uint8_t) + + if (swapLines_) + std::swap(src0, src1); + + /* x += 4 sample every other 2x2 block */ + for (int x = 0; x < (int)window_.width; x += 4) { + b = src0[x]; + g = src0[x + 1]; + g2 = src1[x]; + r = src1[x + 1]; + + g = (g + g2) / 2; + + SWSTATS_ACCUMULATE_LINE_STATS(1) + } + + SWSTATS_FINISH_LINE_STATS() +} + +void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[]) +{ + const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; + const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; + + SWSTATS_START_LINE_STATS(uint16_t) + + if (swapLines_) + std::swap(src0, src1); + + /* x += 4 sample every other 2x2 block */ + for (int x = 0; x < (int)window_.width; x += 4) { + b = src0[x]; + g = src0[x + 1]; + g2 = src1[x]; + r = src1[x + 1]; + + g = (g + g2) / 2; + + /* divide Y by 4 for 10 -> 8 bpp value */ + SWSTATS_ACCUMULATE_LINE_STATS(4) + } + + SWSTATS_FINISH_LINE_STATS() +} + +void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[]) +{ + const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; + const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; + + SWSTATS_START_LINE_STATS(uint16_t) + + if (swapLines_) + std::swap(src0, src1); + + /* x += 4 sample every other 2x2 block */ + for (int x = 0; x < (int)window_.width; x += 4) { + b = src0[x]; + g = src0[x + 1]; + g2 = src1[x]; + r = src1[x + 1]; + + g = (g + g2) / 2; + + /* divide Y by 16 for 12 -> 8 bpp value */ + SWSTATS_ACCUMULATE_LINE_STATS(16) + } + + SWSTATS_FINISH_LINE_STATS() +} + +void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[]) +{ + const uint8_t *src0 = src[1] + window_.x * 5 / 4; + const uint8_t *src1 = src[2] + window_.x * 5 / 4; + const int widthInBytes = window_.width * 5 / 4; + + if (swapLines_) + std::swap(src0, src1); + + SWSTATS_START_LINE_STATS(uint8_t) + + /* x += 5 sample every other 2x2 block */ + for (int x = 0; x < widthInBytes; x += 5) { + /* BGGR */ + b = src0[x]; + g = src0[x + 1]; + g2 = src1[x]; + r = src1[x + 1]; + g = (g + g2) / 2; + /* Data is already 8 bits, divide by 1 */ + SWSTATS_ACCUMULATE_LINE_STATS(1) + } + + SWSTATS_FINISH_LINE_STATS() +} + +void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[]) +{ + const uint8_t *src0 = src[1] + window_.x * 5 / 4; + const uint8_t *src1 = src[2] + window_.x * 5 / 4; + const int widthInBytes = window_.width * 5 / 4; + + if (swapLines_) + std::swap(src0, src1); + + SWSTATS_START_LINE_STATS(uint8_t) + + /* x += 5 sample every other 2x2 block */ + for (int x = 0; x < widthInBytes; x += 5) { + /* GBRG */ + g = src0[x]; + b = src0[x + 1]; + r = src1[x]; + g2 = src1[x + 1]; + g = (g + g2) / 2; + /* Data is already 8 bits, divide by 1 */ + SWSTATS_ACCUMULATE_LINE_STATS(1) + } + + SWSTATS_FINISH_LINE_STATS() +} + +/** + * \brief Reset state to start statistics gathering for a new frame + * + * This may only be called after a successful setWindow() call. + */ +void SwStatsCpu::startFrame(void) +{ + if (window_.width == 0) + LOG(SwStatsCpu, Error) << "Calling startFrame() without setWindow()"; + + stats_.sumR_ = 0; + stats_.sumB_ = 0; + stats_.sumG_ = 0; + stats_.yHistogram.fill(0); +} + +/** + * \brief Finish statistics calculation for the current frame + * \param[in] frame The frame number + * \param[in] bufferId ID of the statistics buffer + * + * This may only be called after a successful setWindow() call. + */ +void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) +{ + *sharedStats_ = stats_; + statsReady.emit(frame, bufferId); +} + +/** + * \brief Setup SwStatsCpu object for standard Bayer orders + * \param[in] order The Bayer order + * + * Check if order is a standard Bayer order and setup xShift_ and swapLines_ + * so that a single BGGR stats function can be used for all 4 standard orders. + */ +int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order) +{ + switch (order) { + case BayerFormat::BGGR: + xShift_ = 0; + swapLines_ = false; + break; + case BayerFormat::GBRG: + xShift_ = 1; /* BGGR -> GBRG */ + swapLines_ = false; + break; + case BayerFormat::GRBG: + xShift_ = 0; + swapLines_ = true; /* BGGR -> GRBG */ + break; + case BayerFormat::RGGB: + xShift_ = 1; /* BGGR -> GBRG */ + swapLines_ = true; /* GBRG -> RGGB */ + break; + default: + return -EINVAL; + } + + patternSize_.height = 2; + patternSize_.width = 2; + ySkipMask_ = 0x02; /* Skip every 3th and 4th line */ + return 0; +} + +/** + * \brief Configure the statistics object for the passed in input format + * \param[in] inputCfg The input format + * + * \return 0 on success, a negative errno value on failure + */ +int SwStatsCpu::configure(const StreamConfiguration &inputCfg) +{ + BayerFormat bayerFormat = + BayerFormat::fromPixelFormat(inputCfg.pixelFormat); + + if (bayerFormat.packing == BayerFormat::Packing::None && + setupStandardBayerOrder(bayerFormat.order) == 0) { + switch (bayerFormat.bitDepth) { + case 8: + stats0_ = &SwStatsCpu::statsBGGR8Line0; + return 0; + case 10: + stats0_ = &SwStatsCpu::statsBGGR10Line0; + return 0; + case 12: + stats0_ = &SwStatsCpu::statsBGGR12Line0; + return 0; + } + } + + if (bayerFormat.bitDepth == 10 && + bayerFormat.packing == BayerFormat::Packing::CSI2) { + patternSize_.height = 2; + patternSize_.width = 4; /* 5 bytes per *4* pixels */ + /* Skip every 3th and 4th line, sample every other 2x2 block */ + ySkipMask_ = 0x02; + xShift_ = 0; + + switch (bayerFormat.order) { + case BayerFormat::BGGR: + case BayerFormat::GRBG: + stats0_ = &SwStatsCpu::statsBGGR10PLine0; + swapLines_ = bayerFormat.order == BayerFormat::GRBG; + return 0; + case BayerFormat::GBRG: + case BayerFormat::RGGB: + stats0_ = &SwStatsCpu::statsGBRG10PLine0; + swapLines_ = bayerFormat.order == BayerFormat::RGGB; + return 0; + default: + break; + } + } + + LOG(SwStatsCpu, Info) + << "Unsupported input format " << inputCfg.pixelFormat.toString(); + return -EINVAL; +} + +/** + * \brief Specify window coordinates over which to gather statistics + * \param[in] window The window object. + */ +void SwStatsCpu::setWindow(const Rectangle &window) +{ + window_ = window; + + window_.x &= ~(patternSize_.width - 1); + window_.x += xShift_; + window_.y &= ~(patternSize_.height - 1); + + /* width_ - xShift_ to make sure the window fits */ + window_.width -= xShift_; + window_.width &= ~(patternSize_.width - 1); + window_.height &= ~(patternSize_.height - 1); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h new file mode 100644 index 00000000..26a2f462 --- /dev/null +++ b/src/libcamera/software_isp/swstats_cpu.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Linaro Ltd + * Copyright (C) 2023, Red Hat Inc. + * + * Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * CPU based software statistics implementation + */ + +#pragma once + +#include <stdint.h> + +#include <libcamera/base/signal.h> + +#include <libcamera/geometry.h> + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/shared_mem_object.h" +#include "libcamera/internal/software_isp/swisp_stats.h" + +namespace libcamera { + +class PixelFormat; +struct StreamConfiguration; + +class SwStatsCpu +{ +public: + SwStatsCpu(); + ~SwStatsCpu() = default; + + bool isValid() const { return sharedStats_.fd().isValid(); } + + const SharedFD &getStatsFD() { return sharedStats_.fd(); } + + const Size &patternSize() { return patternSize_; } + + int configure(const StreamConfiguration &inputCfg); + void setWindow(const Rectangle &window); + void startFrame(); + void finishFrame(uint32_t frame, uint32_t bufferId); + + void processLine0(unsigned int y, const uint8_t *src[]) + { + if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) || + y >= (window_.y + window_.height)) + return; + + (this->*stats0_)(src); + } + + void processLine2(unsigned int y, const uint8_t *src[]) + { + if ((y & ySkipMask_) || y < static_cast<unsigned int>(window_.y) || + y >= (window_.y + window_.height)) + return; + + (this->*stats2_)(src); + } + + Signal<uint32_t, uint32_t> statsReady; + +private: + using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]); + + int setupStandardBayerOrder(BayerFormat::Order order); + /* Bayer 8 bpp unpacked */ + void statsBGGR8Line0(const uint8_t *src[]); + /* Bayer 10 bpp unpacked */ + void statsBGGR10Line0(const uint8_t *src[]); + /* Bayer 12 bpp unpacked */ + void statsBGGR12Line0(const uint8_t *src[]); + /* Bayer 10 bpp packed */ + void statsBGGR10PLine0(const uint8_t *src[]); + void statsGBRG10PLine0(const uint8_t *src[]); + + /* Variables set by configure(), used every line */ + statsProcessFn stats0_; + statsProcessFn stats2_; + bool swapLines_; + + unsigned int ySkipMask_; + + Rectangle window_; + + Size patternSize_; + + unsigned int xShift_; + + SharedMemObject<SwIspStats> sharedStats_; + SwIspStats stats_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/source_paths.cpp b/src/libcamera/source_paths.cpp index 19689585..1af5386a 100644 --- a/src/libcamera/source_paths.cpp +++ b/src/libcamera/source_paths.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * source_paths.cpp - Identify libcamera source and build paths + * Identify libcamera source and build paths */ #include "libcamera/internal/source_paths.h" diff --git a/src/libcamera/stream.cpp b/src/libcamera/stream.cpp index f3e00ead..f091487c 100644 --- a/src/libcamera/stream.cpp +++ b/src/libcamera/stream.cpp @@ -2,22 +2,22 @@ /* * Copyright (C) 2019, Google Inc. * - * stream.cpp - Video stream for a Camera + * Video stream for a Camera */ #include <libcamera/stream.h> #include <algorithm> #include <array> -#include <iomanip> #include <limits.h> -#include <sstream> - -#include <libcamera/request.h> +#include <ostream> +#include <string> +#include <vector> #include <libcamera/base/log.h> #include <libcamera/base/utils.h> +#include <libcamera/request.h> /** * \file stream.h @@ -392,7 +392,24 @@ StreamConfiguration::StreamConfiguration(const StreamFormats &formats) */ std::string StreamConfiguration::toString() const { - return size.toString() + "-" + pixelFormat.toString(); + std::stringstream ss; + ss << *this; + + return ss.str(); +} + +/** + * \brief Insert a text representation of a StreamConfiguration into an output + * stream + * \param[in] out The output stream + * \param[in] cfg The StreamConfiguration + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const StreamConfiguration &cfg) +{ + out << cfg.size << "-" << cfg.pixelFormat << "/" + << ColorSpace::toString(cfg.colorSpace); + return out; } /** @@ -433,7 +450,7 @@ std::ostream &operator<<(std::ostream &out, StreamRole role) "Viewfinder", }; - out << names[static_cast<std::underlying_type_t<StreamRole>>(role)]; + out << names[utils::to_underlying(role)]; return out; } diff --git a/src/libcamera/sysfs.cpp b/src/libcamera/sysfs.cpp index 44c3331b..3d9885b0 100644 --- a/src/libcamera/sysfs.cpp +++ b/src/libcamera/sysfs.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * sysfs.cpp - Miscellaneous utility functions to access sysfs + * Miscellaneous utility functions to access sysfs */ #include "libcamera/internal/sysfs.h" diff --git a/src/libcamera/tracepoints.cpp b/src/libcamera/tracepoints.cpp index 0173b75a..90662d12 100644 --- a/src/libcamera/tracepoints.cpp +++ b/src/libcamera/tracepoints.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * tracepoints.cpp - Tracepoints with lttng + * Tracepoints with lttng */ #define TRACEPOINT_CREATE_PROBES #define TRACEPOINT_DEFINE diff --git a/src/libcamera/transform.cpp b/src/libcamera/transform.cpp index fb2d55ac..9fe8b562 100644 --- a/src/libcamera/transform.cpp +++ b/src/libcamera/transform.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Raspberry Pi Ltd * - * transform.cpp - 2D plane transforms. + * 2D plane transforms. */ #include <libcamera/transform.h> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 24d208ef..0db92c19 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -2,15 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_device.cpp - Common base for V4L2 video devices and subdevices + * Common base for V4L2 video devices and subdevices */ #include "libcamera/internal/v4l2_device.h" #include <fcntl.h> -#include <iomanip> -#include <limits.h> #include <map> +#include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> @@ -206,10 +205,29 @@ ControlList V4L2Device::getControls(const std::vector<uint32_t> &ids) if (info.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) { ControlType type; + ControlValue &value = ctrl.second; + Span<uint8_t> data; switch (info.type) { case V4L2_CTRL_TYPE_U8: type = ControlTypeByte; + value.reserve(type, true, info.elems); + data = value.data(); + v4l2Ctrl.p_u8 = data.data(); + break; + + case V4L2_CTRL_TYPE_U16: + type = ControlTypeUnsigned16; + value.reserve(type, true, info.elems); + data = value.data(); + v4l2Ctrl.p_u16 = reinterpret_cast<uint16_t *>(data.data()); + break; + + case V4L2_CTRL_TYPE_U32: + type = ControlTypeUnsigned32; + value.reserve(type, true, info.elems); + data = value.data(); + v4l2Ctrl.p_u32 = reinterpret_cast<uint32_t *>(data.data()); break; default: @@ -219,11 +237,6 @@ ControlList V4L2Device::getControls(const std::vector<uint32_t> &ids) return {}; } - ControlValue &value = ctrl.second; - value.reserve(type, true, info.elems); - Span<uint8_t> data = value.data(); - - v4l2Ctrl.p_u8 = data.data(); v4l2Ctrl.size = data.size(); } } @@ -301,6 +314,30 @@ int V4L2Device::setControls(ControlList *ctrls) /* Set the v4l2_ext_control value for the write operation. */ ControlValue &value = ctrl->second; switch (iter->first->type()) { + case ControlTypeUnsigned16: { + if (value.isArray()) { + Span<uint8_t> data = value.data(); + v4l2Ctrl.p_u16 = reinterpret_cast<uint16_t *>(data.data()); + v4l2Ctrl.size = data.size(); + } else { + v4l2Ctrl.value = value.get<uint16_t>(); + } + + break; + } + + case ControlTypeUnsigned32: { + if (value.isArray()) { + Span<uint8_t> data = value.data(); + v4l2Ctrl.p_u32 = reinterpret_cast<uint32_t *>(data.data()); + v4l2Ctrl.size = data.size(); + } else { + v4l2Ctrl.value = value.get<uint32_t>(); + } + + break; + } + case ControlTypeInteger32: { if (value.isArray()) { Span<uint8_t> data = value.data(); @@ -413,6 +450,28 @@ std::string V4L2Device::devicePath() const } /** + * \brief Check if frame start event is supported + * + * Due to limitations in the kernel API, this function may disable the frame + * start event as a side effect. It should only be called during initialization, + * before enabling the frame start event with setFrameStartEnabled(). + * + * \return True if frame start event is supported, false otherwise + */ +bool V4L2Device::supportsFrameStartEvent() +{ + struct v4l2_event_subscription event{}; + event.type = V4L2_EVENT_FRAME_SYNC; + + int ret = ioctl(VIDIOC_SUBSCRIBE_EVENT, &event); + if (ret) + return false; + + ioctl(VIDIOC_UNSUBSCRIBE_EVENT, &event); + return true; +} + +/** * \brief Enable or disable frame start event notification * \param[in] enable True to enable frame start events, false to disable them * @@ -490,6 +549,12 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType) case V4L2_CTRL_TYPE_BOOLEAN: return ControlTypeBool; + case V4L2_CTRL_TYPE_U16: + return ControlTypeUnsigned16; + + case V4L2_CTRL_TYPE_U32: + return ControlTypeUnsigned32; + case V4L2_CTRL_TYPE_INTEGER: return ControlTypeInteger32; @@ -522,7 +587,15 @@ std::unique_ptr<ControlId> V4L2Device::v4l2ControlId(const v4l2_query_ext_ctrl & const std::string name(static_cast<const char *>(ctrl.name), len); const ControlType type = v4l2CtrlType(ctrl.type); - return std::make_unique<ControlId>(ctrl.id, name, type); + ControlId::DirectionFlags flags; + if (ctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) + flags = ControlId::Direction::Out; + else if (ctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY) + flags = ControlId::Direction::In; + else + flags = ControlId::Direction::In | ControlId::Direction::Out; + + return std::make_unique<ControlId>(ctrl.id, name, "v4l2", type, flags); } /** @@ -538,6 +611,16 @@ std::optional<ControlInfo> V4L2Device::v4l2ControlInfo(const v4l2_query_ext_ctrl static_cast<uint8_t>(ctrl.maximum), static_cast<uint8_t>(ctrl.default_value)); + case V4L2_CTRL_TYPE_U16: + return ControlInfo(static_cast<uint16_t>(ctrl.minimum), + static_cast<uint16_t>(ctrl.maximum), + static_cast<uint16_t>(ctrl.default_value)); + + case V4L2_CTRL_TYPE_U32: + return ControlInfo(static_cast<uint32_t>(ctrl.minimum), + static_cast<uint32_t>(ctrl.maximum), + static_cast<uint32_t>(ctrl.default_value)); + case V4L2_CTRL_TYPE_BOOLEAN: return ControlInfo(static_cast<bool>(ctrl.minimum), static_cast<bool>(ctrl.maximum), @@ -624,6 +707,8 @@ void V4L2Device::listControls() case V4L2_CTRL_TYPE_BITMASK: case V4L2_CTRL_TYPE_INTEGER_MENU: case V4L2_CTRL_TYPE_U8: + case V4L2_CTRL_TYPE_U16: + case V4L2_CTRL_TYPE_U32: break; /* \todo Support other control types. */ default: diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp index 5551c62e..e8b3eb9c 100644 --- a/src/libcamera/v4l2_pixelformat.cpp +++ b/src/libcamera/v4l2_pixelformat.cpp @@ -3,7 +3,7 @@ * Copyright (C) 2019, Google Inc. * Copyright (C) 2020, Raspberry Pi Ltd * - * v4l2_pixelformat.cpp - V4L2 Pixel Format + * V4L2 Pixel Format */ #include "libcamera/internal/v4l2_pixelformat.h" @@ -71,6 +71,10 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{ { formats::BGRA8888, "32-bit ARGB 8-8-8-8" } }, { V4L2PixelFormat(V4L2_PIX_FMT_BGRA32), { formats::RGBA8888, "32-bit ABGR 8-8-8-8" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_RGB48), + { formats::BGR161616, "48-bit RGB 16-16-16" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_BGR48), + { formats::RGB161616, "48-bit BGR 16-16-16" } }, /* YUV packed formats. */ { V4L2PixelFormat(V4L2_PIX_FMT_YUYV), @@ -135,6 +139,10 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{ { formats::R10_CSI2P, "10-bit Greyscale Packed" } }, { V4L2PixelFormat(V4L2_PIX_FMT_Y12), { formats::R12, "12-bit Greyscale" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_Y12P), + { formats::R12_CSI2P, "12-bit Greyscale Packed" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_Y16), + { formats::R16, "16-bit Greyscale" } }, /* Bayer formats. */ { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8), @@ -201,6 +209,16 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{ { formats::SGRBG16, "16-bit Bayer GRGR/BGBG" } }, { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB16), { formats::SRGGB16, "16-bit Bayer RGRG/GBGB" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_BGGR), + { formats::BGGR_PISP_COMP1, "16-bit Bayer BGBG/GRGR PiSP Compress Mode 1" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GBRG), + { formats::GBRG_PISP_COMP1, "16-bit Bayer GBGB/RGRG PiSP Compress Mode 1" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_GRBG), + { formats::GRBG_PISP_COMP1, "16-bit Bayer GRGR/BGBG PiSP Compress Mode 1" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_RGGB), + { formats::RGGB_PISP_COMP1, "16-bit Bayer RGRG/GBGB PiSP Compress Mode 1" } }, + { V4L2PixelFormat(V4L2_PIX_FMT_PISP_COMP1_MONO), + { formats::MONO_PISP_COMP1, "16-bit Mono PiSP Compress Mode 1" } }, /* Compressed formats. */ { V4L2PixelFormat(V4L2_PIX_FMT_MJPEG), @@ -355,6 +373,40 @@ V4L2PixelFormat::fromPixelFormat(const PixelFormat &pixelFormat) } /** + * \brief Test if a V4L2PixelFormat is one of the line based generic metadata + * formats + * + * A limited number of metadata formats, the ones that represents generic + * line-based metadata buffers, need to have their width, height and + * bytesperline set by userspace. + * + * This function tests if the current V4L2PixelFormat is one of those. + * + * Note: It would have been nicer to store this information in a + * V4L2PixelFormat::Info instance, but as metadata format are not exposed to + * applications, there are no PixelFormat and DRM fourcc codes associated to + * them. + * + * \return True if the V4L2PixelFormat() is a generic line based format, false + * otherwise + */ +bool V4L2PixelFormat::isGenericLineBasedMetadata() const +{ + switch (fourcc_) { + case V4L2_META_FMT_GENERIC_8: + case V4L2_META_FMT_GENERIC_CSI2_10: + case V4L2_META_FMT_GENERIC_CSI2_12: + case V4L2_META_FMT_GENERIC_CSI2_14: + case V4L2_META_FMT_GENERIC_CSI2_16: + case V4L2_META_FMT_GENERIC_CSI2_20: + case V4L2_META_FMT_GENERIC_CSI2_24: + return true; + default: + return false; + } +} + +/** * \brief Insert a text representation of a V4L2PixelFormat into an output * stream * \param[in] out The output stream diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index 15e8206a..33279654 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -2,27 +2,32 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice.cpp - V4L2 Subdevice + * V4L2 Subdevice */ #include "libcamera/internal/v4l2_subdevice.h" #include <fcntl.h> -#include <iomanip> -#include <regex> #include <sstream> #include <string.h> #include <sys/ioctl.h> #include <unistd.h> +#pragma GCC diagnostic push +#if defined __SANITIZE_ADDRESS__ && defined __OPTIMIZE__ +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#include <regex> +#pragma GCC diagnostic pop + #include <linux/media-bus-format.h> #include <linux/v4l2-subdev.h> -#include <libcamera/geometry.h> - #include <libcamera/base/log.h> #include <libcamera/base/utils.h> +#include <libcamera/geometry.h> + #include "libcamera/internal/formats.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/media_object.h" @@ -36,110 +41,808 @@ namespace libcamera { LOG_DECLARE_CATEGORY(V4L2) -namespace { - -/* - * \struct V4L2SubdeviceFormatInfo +/** + * \class MediaBusFormatInfo * \brief Information about media bus formats - * \param bitsPerPixel Bits per pixel - * \param name Name of MBUS format - * \param colourEncoding Type of colour encoding + * + * The MediaBusFormatInfo class groups together information describing a media + * bus format. It facilitates handling of media bus formats by providing data + * commonly used in pipeline handlers. + * + * \var MediaBusFormatInfo::name + * \brief The format name as a human-readable string, used as the text + * representation of the format + * + * \var MediaBusFormatInfo::code + * \brief The media bus format code described by this instance (MEDIA_BUS_FMT_*) + * + * \var MediaBusFormatInfo::type + * \brief The media bus format type + * + * \var MediaBusFormatInfo::bitsPerPixel + * \brief The average number of bits per pixel + * + * The number of bits per pixel averages the total number of bits for all + * colour components over the whole image, excluding any padding bits or + * padding pixels. + * + * For formats that transmit multiple or fractional pixels per sample, the + * value will differ from the bus width. + * + * Formats that don't have a fixed number of bits per pixel, such as compressed + * formats, or device-specific embedded data formats, report 0 in this field. + * + * \var MediaBusFormatInfo::colourEncoding + * \brief The colour encoding type + * + * This field is valid for Type::Image formats only. */ -struct V4L2SubdeviceFormatInfo { - unsigned int bitsPerPixel; - const char *name; - PixelFormatInfo::ColourEncoding colourEncoding; -}; -/* - * \var formatInfoMap - * \brief A map that associates V4L2SubdeviceFormatInfo struct to V4L2 media - * bus codes - */ -const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = { - { MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, { 16, "RGB444_2X8_PADHI_BE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, { 16, "RGB444_2X8_PADHI_LE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, { 16, "RGB555_2X8_PADHI_BE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, { 16, "RGB555_2X8_PADHI_LE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB565_1X16, { 16, "RGB565_1X16", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_BGR565_2X8_BE, { 16, "BGR565_2X8_BE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_BGR565_2X8_LE, { 16, "BGR565_2X8_LE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB565_2X8_BE, { 16, "RGB565_2X8_BE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB565_2X8_LE, { 16, "RGB565_2X8_LE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB666_1X18, { 18, "RGB666_1X18", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_BGR888_1X24, { 24, "BGR888_1X24", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB888_1X24, { 24, "RGB888_1X24", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB888_2X12_BE, { 24, "RGB888_2X12_BE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_RGB888_2X12_LE, { 24, "RGB888_2X12_LE", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_ARGB8888_1X32, { 32, "ARGB8888_1X32", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_Y8_1X8, { 8, "Y8_1X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UV8_1X8, { 8, "UV8_1X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY8_1_5X8, { 12, "UYVY8_1_5X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY8_1_5X8, { 12, "VYUY8_1_5X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV8_1_5X8, { 12, "YUYV8_1_5X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU8_1_5X8, { 12, "YVYU8_1_5X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY8_2X8, { 16, "UYVY8_2X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY8_2X8, { 16, "VYUY8_2X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV8_2X8, { 16, "YUYV8_2X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU8_2X8, { 16, "YVYU8_2X8", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_Y10_1X10, { 10, "Y10_1X10", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY10_2X10, { 20, "UYVY10_2X10", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY10_2X10, { 20, "VYUY10_2X10", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV10_2X10, { 20, "YUYV10_2X10", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU10_2X10, { 20, "YVYU10_2X10", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_Y12_1X12, { 12, "Y12_1X12", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY8_1X16, { 16, "UYVY8_1X16", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY8_1X16, { 16, "VYUY8_1X16", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV8_1X16, { 16, "YUYV8_1X16", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU8_1X16, { 16, "YVYU8_1X16", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YDYUYDYV8_1X16, { 16, "YDYUYDYV8_1X16", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY10_1X20, { 20, "UYVY10_1X20", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY10_1X20, { 20, "VYUY10_1X20", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV10_1X20, { 20, "YUYV10_1X20", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU10_1X20, { 20, "YVYU10_1X20", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUV8_1X24, { 24, "YUV8_1X24", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUV10_1X30, { 30, "YUV10_1X30", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_AYUV8_1X32, { 32, "AYUV8_1X32", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY12_2X12, { 24, "UYVY12_2X12", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY12_2X12, { 24, "VYUY12_2X12", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV12_2X12, { 24, "YUYV12_2X12", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU12_2X12, { 24, "YVYU12_2X12", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_UYVY12_1X24, { 24, "UYVY12_1X24", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_VYUY12_1X24, { 24, "VYUY12_1X24", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YUYV12_1X24, { 24, "YUYV12_1X24", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_YVYU12_1X24, { 24, "YVYU12_1X24", PixelFormatInfo::ColourEncodingYUV } }, - { MEDIA_BUS_FMT_SBGGR8_1X8, { 8, "SBGGR8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG8_1X8, { 8, "SGBRG8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG8_1X8, { 8, "SGRBG8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB8_1X8, { 8, "SRGGB8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, { 8, "SBGGR10_ALAW8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, { 8, "SGBRG10_ALAW8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, { 8, "SGRBG10_ALAW8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, { 8, "SRGGB10_ALAW8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, { 8, "SBGGR10_DPCM8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, { 8, "SGBRG10_DPCM8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, { 8, "SGRBG10_DPCM8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, { 8, "SRGGB10_DPCM8_1X8", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, { 16, "SBGGR10_2X8_PADHI_BE", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, { 16, "SBGGR10_2X8_PADHI_LE", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, { 16, "SBGGR10_2X8_PADLO_BE", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, { 16, "SBGGR10_2X8_PADLO_LE", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR10_1X10, { 10, "SBGGR10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG10_1X10, { 10, "SGBRG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG10_1X10, { 10, "SGRBG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB10_1X10, { 10, "SRGGB10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR12_1X12, { 12, "SBGGR12_1X12", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG12_1X12, { 12, "SGBRG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG12_1X12, { 12, "SGRBG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB12_1X12, { 12, "SRGGB12_1X12", PixelFormatInfo::ColourEncodingRAW } }, +/** + * \enum MediaBusFormatInfo::Type + * \brief The format type + * + * \var MediaBusFormatInfo::Type::Image + * \brief The format describes image data + * + * \var MediaBusFormatInfo::Type::Metadata + * \brief The format describes generic metadata + * + * \var MediaBusFormatInfo::Type::EmbeddedData + * \brief The format describes sensor embedded data + */ + +namespace { + +const std::map<uint32_t, MediaBusFormatInfo> mediaBusFormatInfo{ + /* This table is sorted to match the order in linux/media-bus-format.h */ + { MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, { + .name = "RGB444_2X8_PADHI_BE", + .code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, { + .name = "RGB444_2X8_PADHI_LE", + .code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, { + .name = "RGB555_2X8_PADHI_BE", + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, { + .name = "RGB555_2X8_PADHI_LE", + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB565_1X16, { + .name = "RGB565_1X16", + .code = MEDIA_BUS_FMT_RGB565_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_BGR565_2X8_BE, { + .name = "BGR565_2X8_BE", + .code = MEDIA_BUS_FMT_BGR565_2X8_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_BGR565_2X8_LE, { + .name = "BGR565_2X8_LE", + .code = MEDIA_BUS_FMT_BGR565_2X8_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB565_2X8_BE, { + .name = "RGB565_2X8_BE", + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB565_2X8_LE, { + .name = "RGB565_2X8_LE", + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB666_1X18, { + .name = "RGB666_1X18", + .code = MEDIA_BUS_FMT_RGB666_1X18, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 18, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_BGR888_1X24, { + .name = "BGR888_1X24", + .code = MEDIA_BUS_FMT_BGR888_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB888_1X24, { + .name = "RGB888_1X24", + .code = MEDIA_BUS_FMT_RGB888_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB888_2X12_BE, { + .name = "RGB888_2X12_BE", + .code = MEDIA_BUS_FMT_RGB888_2X12_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB888_2X12_LE, { + .name = "RGB888_2X12_LE", + .code = MEDIA_BUS_FMT_RGB888_2X12_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB121212_1X36, { + .name = "RGB121212_1X36", + .code = MEDIA_BUS_FMT_RGB121212_1X36, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 36, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_RGB202020_1X60, { + .name = "RGB202020_1X60", + .code = MEDIA_BUS_FMT_RGB202020_1X60, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 60, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_ARGB8888_1X32, { + .name = "ARGB8888_1X32", + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 32, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_Y8_1X8, { + .name = "Y8_1X8", + .code = MEDIA_BUS_FMT_Y8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UV8_1X8, { + .name = "UV8_1X8", + .code = MEDIA_BUS_FMT_UV8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, { + .name = "UYVY8_1_5X8", + .code = MEDIA_BUS_FMT_UYVY8_1_5X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, { + .name = "VYUY8_1_5X8", + .code = MEDIA_BUS_FMT_VYUY8_1_5X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV8_1_5X8, { + .name = "YUYV8_1_5X8", + .code = MEDIA_BUS_FMT_YUYV8_1_5X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, { + .name = "YVYU8_1_5X8", + .code = MEDIA_BUS_FMT_YVYU8_1_5X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY8_2X8, { + .name = "UYVY8_2X8", + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY8_2X8, { + .name = "VYUY8_2X8", + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV8_2X8, { + .name = "YUYV8_2X8", + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU8_2X8, { + .name = "YVYU8_2X8", + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_Y10_1X10, { + .name = "Y10_1X10", + .code = MEDIA_BUS_FMT_Y10_1X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY10_2X10, { + .name = "UYVY10_2X10", + .code = MEDIA_BUS_FMT_UYVY10_2X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY10_2X10, { + .name = "VYUY10_2X10", + .code = MEDIA_BUS_FMT_VYUY10_2X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV10_2X10, { + .name = "YUYV10_2X10", + .code = MEDIA_BUS_FMT_YUYV10_2X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU10_2X10, { + .name = "YVYU10_2X10", + .code = MEDIA_BUS_FMT_YVYU10_2X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_Y12_1X12, { + .name = "Y12_1X12", + .code = MEDIA_BUS_FMT_Y12_1X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_Y16_1X16, { + .name = "Y16_1X16", + .code = MEDIA_BUS_FMT_Y16_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY8_1X16, { + .name = "UYVY8_1X16", + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY8_1X16, { + .name = "VYUY8_1X16", + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV8_1X16, { + .name = "YUYV8_1X16", + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU8_1X16, { + .name = "YVYU8_1X16", + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YDYUYDYV8_1X16, { + .name = "YDYUYDYV8_1X16", + .code = MEDIA_BUS_FMT_YDYUYDYV8_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY10_1X20, { + .name = "UYVY10_1X20", + .code = MEDIA_BUS_FMT_UYVY10_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY10_1X20, { + .name = "VYUY10_1X20", + .code = MEDIA_BUS_FMT_VYUY10_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV10_1X20, { + .name = "YUYV10_1X20", + .code = MEDIA_BUS_FMT_YUYV10_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU10_1X20, { + .name = "YVYU10_1X20", + .code = MEDIA_BUS_FMT_YVYU10_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUV8_1X24, { + .name = "YUV8_1X24", + .code = MEDIA_BUS_FMT_YUV8_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUV10_1X30, { + .name = "YUV10_1X30", + .code = MEDIA_BUS_FMT_YUV10_1X30, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 30, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_AYUV8_1X32, { + .name = "AYUV8_1X32", + .code = MEDIA_BUS_FMT_AYUV8_1X32, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 32, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY12_2X12, { + .name = "UYVY12_2X12", + .code = MEDIA_BUS_FMT_UYVY12_2X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY12_2X12, { + .name = "VYUY12_2X12", + .code = MEDIA_BUS_FMT_VYUY12_2X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV12_2X12, { + .name = "YUYV12_2X12", + .code = MEDIA_BUS_FMT_YUYV12_2X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU12_2X12, { + .name = "YVYU12_2X12", + .code = MEDIA_BUS_FMT_YVYU12_2X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_UYVY12_1X24, { + .name = "UYVY12_1X24", + .code = MEDIA_BUS_FMT_UYVY12_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_VYUY12_1X24, { + .name = "VYUY12_1X24", + .code = MEDIA_BUS_FMT_VYUY12_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YUYV12_1X24, { + .name = "YUYV12_1X24", + .code = MEDIA_BUS_FMT_YUYV12_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_YVYU12_1X24, { + .name = "YVYU12_1X24", + .code = MEDIA_BUS_FMT_YVYU12_1X24, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_SBGGR8_1X8, { + .name = "SBGGR8_1X8", + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG8_1X8, { + .name = "SGBRG8_1X8", + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG8_1X8, { + .name = "SGRBG8_1X8", + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB8_1X8, { + .name = "SRGGB8_1X8", + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, { + .name = "SBGGR10_ALAW8_1X8", + .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, { + .name = "SGBRG10_ALAW8_1X8", + .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, { + .name = "SGRBG10_ALAW8_1X8", + .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, { + .name = "SRGGB10_ALAW8_1X8", + .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, { + .name = "SBGGR10_DPCM8_1X8", + .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, { + .name = "SGBRG10_DPCM8_1X8", + .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, { + .name = "SGRBG10_DPCM8_1X8", + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, { + .name = "SRGGB10_DPCM8_1X8", + .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, { + .name = "SBGGR10_2X8_PADHI_BE", + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, { + .name = "SBGGR10_2X8_PADHI_LE", + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, { + .name = "SBGGR10_2X8_PADLO_BE", + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, { + .name = "SBGGR10_2X8_PADLO_LE", + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, { + .name = "SBGGR10_1X10", + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, { + .name = "SGBRG10_1X10", + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, { + .name = "SGRBG10_1X10", + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, { + .name = "SRGGB10_1X10", + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, { + .name = "SBGGR12_1X12", + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, { + .name = "SGBRG12_1X12", + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, { + .name = "SGRBG12_1X12", + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB12_1X12, { + .name = "SRGGB12_1X12", + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR14_1X14, { + .name = "SBGGR14_1X14", + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 14, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGBRG14_1X14, { + .name = "SGBRG14_1X14", + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 14, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SGRBG14_1X14, { + .name = "SGRBG14_1X14", + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 14, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SRGGB14_1X14, { + .name = "SRGGB14_1X14", + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 14, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_SBGGR16_1X16, { + .name = "SBGGR16_1X16", + .code = MEDIA_BUS_FMT_SBGGR16_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SGBRG16_1X16, { + .name = "SGBRG16_1X16", + .code = MEDIA_BUS_FMT_SGBRG16_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SGRBG16_1X16, { + .name = "SGRBG16_1X16", + .code = MEDIA_BUS_FMT_SGRBG16_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SRGGB16_1X16, { + .name = "SRGGB16_1X16", + .code = MEDIA_BUS_FMT_SRGGB16_1X16, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SBGGR20_1X20, { + .name = "SBGGR20_1X20", + .code = MEDIA_BUS_FMT_SBGGR20_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SGBRG20_1X20, { + .name = "SGBRG20_1X20", + .code = MEDIA_BUS_FMT_SGBRG20_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SGRBG20_1X20, { + .name = "SGRBG20_1X20", + .code = MEDIA_BUS_FMT_SGRBG20_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, + { MEDIA_BUS_FMT_SRGGB20_1X20, { + .name = "SRGGB20_1X20", + .code = MEDIA_BUS_FMT_SRGGB20_1X20, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW + } }, /* \todo Clarify colour encoding for HSV formats */ - { MEDIA_BUS_FMT_AHSV8888_1X32, { 32, "AHSV8888_1X32", PixelFormatInfo::ColourEncodingRGB } }, - { MEDIA_BUS_FMT_JPEG_1X8, { 8, "JPEG_1X8", PixelFormatInfo::ColourEncodingYUV } }, + { MEDIA_BUS_FMT_AHSV8888_1X32, { + .name = "AHSV8888_1X32", + .code = MEDIA_BUS_FMT_AHSV8888_1X32, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 32, + .colourEncoding = PixelFormatInfo::ColourEncodingRGB, + } }, + { MEDIA_BUS_FMT_JPEG_1X8, { + .name = "JPEG_1X8", + .code = MEDIA_BUS_FMT_JPEG_1X8, + .type = MediaBusFormatInfo::Type::Image, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingYUV, + } }, + { MEDIA_BUS_FMT_METADATA_FIXED, { + .name = "METADATA_FIXED", + .code = MEDIA_BUS_FMT_METADATA_FIXED, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 0, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_8, { + .name = "META_8", + .code = MEDIA_BUS_FMT_META_8, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 8, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_10, { + .name = "META_10", + .code = MEDIA_BUS_FMT_META_10, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 10, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_12, { + .name = "META_12", + .code = MEDIA_BUS_FMT_META_12, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 12, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_14, { + .name = "META_14", + .code = MEDIA_BUS_FMT_META_14, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 14, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_16, { + .name = "META_16", + .code = MEDIA_BUS_FMT_META_16, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 16, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_20, { + .name = "META_20", + .code = MEDIA_BUS_FMT_META_20, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 20, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_META_24, { + .name = "META_24", + .code = MEDIA_BUS_FMT_META_24, + .type = MediaBusFormatInfo::Type::Metadata, + .bitsPerPixel = 24, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_CCS_EMBEDDED, { + .name = "CCS_EMBEDDED", + .code = MEDIA_BUS_FMT_CCS_EMBEDDED, + .type = MediaBusFormatInfo::Type::EmbeddedData, + .bitsPerPixel = 0, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, + { MEDIA_BUS_FMT_OV2740_EMBEDDED, { + .name = "OV2740_EMBEDDED", + .code = MEDIA_BUS_FMT_CCS_EMBEDDED, + .type = MediaBusFormatInfo::Type::EmbeddedData, + .bitsPerPixel = 0, + .colourEncoding = PixelFormatInfo::ColourEncodingRAW, + } }, }; } /* namespace */ /** + * \fn bool MediaBusFormatInfo::isValid() const + * \brief Check if the media bus format info is valid + * \return True if the media bus format info is valid, false otherwise + */ + +/** + * \brief Retrieve information about a media bus format + * \param[in] code The media bus format code + * \return The MediaBusFormatInfo describing the \a code if known, or an invalid + * MediaBusFormatInfo otherwise + */ +const MediaBusFormatInfo &MediaBusFormatInfo::info(uint32_t code) +{ + static const MediaBusFormatInfo invalid{}; + + const auto it = mediaBusFormatInfo.find(code); + if (it == mediaBusFormatInfo.end()) { + LOG(V4L2, Warning) + << "Unsupported media bus format " + << utils::hex(code, 4); + return invalid; + } + + return it->second; +} + +/** * \struct V4L2SubdeviceCapability * \brief struct v4l2_subdev_capability object wrapper and helpers * @@ -192,7 +895,7 @@ const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = { */ /** - * \var V4L2SubdeviceFormat::mbus_code + * \var V4L2SubdeviceFormat::code * \brief The image format bus code */ @@ -229,23 +932,6 @@ const std::string V4L2SubdeviceFormat::toString() const } /** - * \brief Retrieve the number of bits per pixel for the V4L2 subdevice format - * \return The number of bits per pixel for the format, or 0 if the format is - * not supported - */ -uint8_t V4L2SubdeviceFormat::bitsPerPixel() const -{ - const auto it = formatInfoMap.find(mbus_code); - if (it == formatInfoMap.end()) { - LOG(V4L2, Error) << "No information available for format '" - << *this << "'"; - return 0; - } - - return it->second.bitsPerPixel; -} - -/** * \brief Insert a text representation of a V4L2SubdeviceFormat into an output * stream * \param[in] out The output stream @@ -256,10 +942,10 @@ std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f) { out << f.size << "-"; - const auto it = formatInfoMap.find(f.mbus_code); + const auto it = mediaBusFormatInfo.find(f.code); - if (it == formatInfoMap.end()) - out << utils::hex(f.mbus_code, 4); + if (it == mediaBusFormatInfo.end()) + out << utils::hex(f.code, 4); else out << it->second.name; @@ -295,30 +981,131 @@ std::ostream &operator<<(std::ostream &out, const V4L2SubdeviceFormat &f) */ /** - * \class V4L2Subdevice::Routing + * \class V4L2Subdevice::Stream + * \brief V4L2 subdevice stream + * + * This class identifies a subdev stream, by bundling the pad number with the + * stream number. It is used in all stream-aware functions of the V4L2Subdevice + * class to identify the stream the functions operate on. + * + * \var V4L2Subdevice::Stream::pad + * \brief The 0-indexed pad number + * + * \var V4L2Subdevice::Stream::stream + * \brief The stream number + */ + +/** + * \fn V4L2Subdevice::Stream::Stream() + * \brief Construct a Stream with pad and stream set to 0 + */ + +/** + * \fn V4L2Subdevice::Stream::Stream(unsigned int pad, unsigned int stream) + * \brief Construct a Stream with a given \a pad and \a stream number + * \param[in] pad The indexed pad number + * \param[in] stream The stream number + */ + +/** + * \brief Compare streams for equality + * \return True if the two streams are equal, false otherwise + */ +bool operator==(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs) +{ + return lhs.pad == rhs.pad && lhs.stream == rhs.stream; +} + +/** + * \fn bool operator!=(const V4L2Subdevice::Stream &lhs, const V4L2Subdevice::Stream &rhs) + * \brief Compare streams for inequality + * \return True if the two streams are not equal, false otherwise + */ + +/** + * \brief Insert a text representation of a V4L2Subdevice::Stream into an + * output stream + * \param[in] out The output stream + * \param[in] stream The V4L2Subdevice::Stream + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Stream &stream) +{ + out << stream.pad << "/" << stream.stream; + + return out; +} + +/** + * \class V4L2Subdevice::Route + * \brief V4L2 subdevice routing table entry + * + * This class models a route in the subdevice routing table. It is similar to + * the v4l2_subdev_route structure, but uses the V4L2Subdevice::Stream class + * for easier usage with the V4L2Subdevice stream-aware functions. + * + * \var V4L2Subdevice::Route::sink + * \brief The sink stream of the route + * + * \var V4L2Subdevice::Route::source + * \brief The source stream of the route + * + * \var V4L2Subdevice::Route::flags + * \brief The route flags (V4L2_SUBDEV_ROUTE_FL_*) + */ + +/** + * \fn V4L2Subdevice::Route::Route() + * \brief Construct a Route with default streams + */ + +/** + * \fn V4L2Subdevice::Route::Route(const Stream &sink, const Stream &source, + * uint32_t flags) + * \brief Construct a Route from \a sink to \a source + * \param[in] sink The sink stream + * \param[in] source The source stream + * \param[in] flags The route flags + */ + +/** + * \brief Insert a text representation of a V4L2Subdevice::Route into an + * output stream + * \param[in] out The output stream + * \param[in] route The V4L2Subdevice::Route + * \return The output stream \a out + */ +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route) +{ + out << route.sink << " -> " << route.source + << " (" << utils::hex(route.flags) << ")"; + + return out; +} + +/** + * \typedef V4L2Subdevice::Routing * \brief V4L2 subdevice routing table * * This class stores a subdevice routing table as a vector of routes. */ /** - * \brief Assemble and return a string describing the routing table - * \return A string describing the routing table + * \brief Insert a text representation of a V4L2Subdevice::Routing into an + * output stream + * \param[in] out The output stream + * \param[in] routing The V4L2Subdevice::Routing + * \return The output stream \a out */ -std::string V4L2Subdevice::Routing::toString() const +std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing) { - std::stringstream routing; - - for (const auto &[i, route] : utils::enumerate(*this)) { - routing << "[" << i << "] " - << route.sink_pad << "/" << route.sink_stream << " -> " - << route.source_pad << "/" << route.source_stream - << " (" << utils::hex(route.flags) << ")"; - if (i != size() - 1) - routing << ", "; + for (const auto &[i, route] : utils::enumerate(routing)) { + out << "[" << i << "] " << route; + if (i != routing.size() - 1) + out << ", "; } - return routing.str(); + return out; } /** @@ -359,6 +1146,21 @@ int V4L2Subdevice::open() return ret; } + /* If the subdev supports streams, enable the streams API. */ + if (caps_.hasStreams()) { + struct v4l2_subdev_client_capability clientCaps{}; + clientCaps.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS; + + ret = ioctl(VIDIOC_SUBDEV_S_CLIENT_CAP, &clientCaps); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Unable to set client capabilities: " + << strerror(-ret); + return ret; + } + } + return 0; } @@ -370,7 +1172,7 @@ int V4L2Subdevice::open() /** * \brief Get selection rectangle \a rect for \a target - * \param[in] pad The 0-indexed pad number the rectangle is retrieved from + * \param[in] stream The stream the rectangle is retrieved from * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags * \param[out] rect The retrieved selection rectangle * @@ -378,13 +1180,14 @@ int V4L2Subdevice::open() * * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, +int V4L2Subdevice::getSelection(const Stream &stream, unsigned int target, Rectangle *rect) { struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; - sel.pad = pad; + sel.pad = stream.pad; + sel.stream = stream.stream; sel.target = target; sel.flags = 0; @@ -392,7 +1195,7 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, if (ret < 0) { LOG(V4L2, Error) << "Unable to get rectangle " << target << " on pad " - << pad << ": " << strerror(-ret); + << stream << ": " << strerror(-ret); return ret; } @@ -405,8 +1208,19 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, } /** + * \fn V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, + * Rectangle *rect) + * \brief Get selection rectangle \a rect for \a target + * \param[in] pad The 0-indexed pad number the rectangle is retrieved from + * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags + * \param[out] rect The retrieved selection rectangle + * + * \return 0 on success or a negative error code otherwise + */ + +/** * \brief Set selection rectangle \a rect for \a target - * \param[in] pad The 0-indexed pad number the rectangle is to be applied to + * \param[in] stream The stream the rectangle is to be applied to * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags * \param[inout] rect The selection rectangle to be applied * @@ -414,13 +1228,14 @@ int V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, * * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target, +int V4L2Subdevice::setSelection(const Stream &stream, unsigned int target, Rectangle *rect) { struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; - sel.pad = pad; + sel.pad = stream.pad; + sel.stream = stream.stream; sel.target = target; sel.flags = 0; @@ -433,7 +1248,7 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target, if (ret < 0) { LOG(V4L2, Error) << "Unable to set rectangle " << target << " on pad " - << pad << ": " << strerror(-ret); + << stream << ": " << strerror(-ret); return ret; } @@ -444,26 +1259,40 @@ int V4L2Subdevice::setSelection(unsigned int pad, unsigned int target, return 0; } + /** - * \brief Enumerate all media bus codes and frame sizes on a \a pad - * \param[in] pad The 0-indexed pad number to enumerate formats on + * \fn V4L2Subdevice::setSelection(unsigned int pad, unsigned int target, + * Rectangle *rect) + * \brief Set selection rectangle \a rect for \a target + * \param[in] pad The 0-indexed pad number the rectangle is to be applied to + * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags + * \param[inout] rect The selection rectangle to be applied + * + * \todo Define a V4L2SelectionTarget enum for the selection target + * + * \return 0 on success or a negative error code otherwise + */ + +/** + * \brief Enumerate all media bus codes and frame sizes on a \a stream + * \param[in] stream The stream to enumerate formats for * * Enumerate all media bus codes and frame sizes supported by the subdevice on - * a \a pad. + * a \a stream. * * \return A list of the supported device formats */ -V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad) +V4L2Subdevice::Formats V4L2Subdevice::formats(const Stream &stream) { Formats formats; - if (pad >= entity_->pads().size()) { - LOG(V4L2, Error) << "Invalid pad: " << pad; + if (stream.pad >= entity_->pads().size()) { + LOG(V4L2, Error) << "Invalid pad: " << stream.pad; return {}; } - for (unsigned int code : enumPadCodes(pad)) { - std::vector<SizeRange> sizes = enumPadSizes(pad, code); + for (unsigned int code : enumPadCodes(stream)) { + std::vector<SizeRange> sizes = enumPadSizes(stream, code); if (sizes.empty()) return {}; @@ -471,7 +1300,7 @@ V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad) if (!inserted.second) { LOG(V4L2, Error) << "Could not add sizes for media bus code " - << code << " on pad " << pad; + << code << " on pad " << stream.pad; return {}; } } @@ -479,6 +1308,17 @@ V4L2Subdevice::Formats V4L2Subdevice::formats(unsigned int pad) return formats; } +/** + * \fn V4L2Subdevice::formats(unsigned int pad) + * \brief Enumerate all media bus codes and frame sizes on a \a pad + * \param[in] pad The 0-indexed pad number to enumerate formats on + * + * Enumerate all media bus codes and frame sizes supported by the subdevice on + * a \a pad + * + * \return A list of the supported device formats + */ + std::optional<ColorSpace> V4L2Subdevice::toColorSpace(const v4l2_mbus_framefmt &format) const { /* @@ -494,9 +1334,9 @@ std::optional<ColorSpace> V4L2Subdevice::toColorSpace(const v4l2_mbus_framefmt & return std::nullopt; PixelFormatInfo::ColourEncoding colourEncoding; - auto iter = formatInfoMap.find(format.code); - if (iter != formatInfoMap.end()) { - colourEncoding = iter->second.colourEncoding; + const MediaBusFormatInfo &info = MediaBusFormatInfo::info(format.code); + if (info.isValid()) { + colourEncoding = info.colourEncoding; } else { LOG(V4L2, Warning) << "Unknown subdev format " @@ -510,83 +1350,189 @@ std::optional<ColorSpace> V4L2Subdevice::toColorSpace(const v4l2_mbus_framefmt & } /** - * \brief Retrieve the image format set on one of the V4L2 subdevice pads - * \param[in] pad The 0-indexed pad number the format is to be retrieved from + * \brief Retrieve the image format set on one of the V4L2 subdevice streams + * \param[in] stream The stream the format is to be retrieved from * \param[out] format The image bus format * \param[in] whence The format to get, \ref V4L2Subdevice::ActiveFormat * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format, +int V4L2Subdevice::getFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence) { struct v4l2_subdev_format subdevFmt = {}; subdevFmt.which = whence; - subdevFmt.pad = pad; + subdevFmt.pad = stream.pad; + subdevFmt.stream = stream.stream; int ret = ioctl(VIDIOC_SUBDEV_G_FMT, &subdevFmt); if (ret) { LOG(V4L2, Error) - << "Unable to get format on pad " << pad - << ": " << strerror(-ret); + << "Unable to get format on pad " << stream << ": " + << strerror(-ret); return ret; } format->size.width = subdevFmt.format.width; format->size.height = subdevFmt.format.height; - format->mbus_code = subdevFmt.format.code; + format->code = subdevFmt.format.code; format->colorSpace = toColorSpace(subdevFmt.format); return 0; } /** + * \fn V4L2Subdevice::getFormat(unsigned int pad, V4L2SubdeviceFormat *format, + * Whence whence) + * \brief Retrieve the image format set on one of the V4L2 subdevice pads + * \param[in] pad The 0-indexed pad number the format is to be retrieved from + * \param[out] format The image bus format + * \param[in] whence The format to get, \ref V4L2Subdevice::ActiveFormat + * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" + * \return 0 on success or a negative error code otherwise + */ + +/** * \brief Set an image format on one of the V4L2 subdevice pads - * \param[in] pad The 0-indexed pad number the format is to be applied to - * \param[inout] format The image bus format to apply to the subdevice's pad + * \param[in] stream The stream the format is to be applied to + * \param[inout] format The image bus format to apply to the stream * \param[in] whence The format to set, \ref V4L2Subdevice::ActiveFormat * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" * - * Apply the requested image format to the desired media pad and return the + * Apply the requested image format to the desired stream and return the * actually applied format parameters, as getFormat() would do. * * \return 0 on success or a negative error code otherwise */ -int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format, +int V4L2Subdevice::setFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence) { struct v4l2_subdev_format subdevFmt = {}; subdevFmt.which = whence; - subdevFmt.pad = pad; + subdevFmt.pad = stream.pad; + subdevFmt.stream = stream.stream; subdevFmt.format.width = format->size.width; subdevFmt.format.height = format->size.height; - subdevFmt.format.code = format->mbus_code; + subdevFmt.format.code = format->code; subdevFmt.format.field = V4L2_FIELD_NONE; if (format->colorSpace) { fromColorSpace(format->colorSpace, subdevFmt.format); /* The CSC flag is only applicable to source pads. */ - if (entity_->pads()[pad]->flags() & MEDIA_PAD_FL_SOURCE) + if (entity_->pads()[stream.pad]->flags() & MEDIA_PAD_FL_SOURCE) subdevFmt.format.flags |= V4L2_MBUS_FRAMEFMT_SET_CSC; } int ret = ioctl(VIDIOC_SUBDEV_S_FMT, &subdevFmt); if (ret) { LOG(V4L2, Error) - << "Unable to set format on pad " << pad - << ": " << strerror(-ret); + << "Unable to set format on pad " << stream << ": " + << strerror(-ret); return ret; } format->size.width = subdevFmt.format.width; format->size.height = subdevFmt.format.height; - format->mbus_code = subdevFmt.format.code; + format->code = subdevFmt.format.code; format->colorSpace = toColorSpace(subdevFmt.format); return 0; } /** + * \fn V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format, + * Whence whence) + * \brief Set an image format on one of the V4L2 subdevice pads + * \param[in] pad The 0-indexed pad number the format is to be applied to + * \param[inout] format The image bus format to apply to the subdevice's pad + * \param[in] whence The format to set, \ref V4L2Subdevice::ActiveFormat + * "ActiveFormat" or \ref V4L2Subdevice::TryFormat "TryFormat" + * + * Apply the requested image format to the desired media pad and return the + * actually applied format parameters, as getFormat() would do. + * + * \return 0 on success or a negative error code otherwise + */ + +namespace { + +void routeFromKernel(V4L2Subdevice::Route &route, + const struct v4l2_subdev_route &kroute) +{ + route.sink.pad = kroute.sink_pad; + route.sink.stream = kroute.sink_stream; + route.source.pad = kroute.source_pad; + route.source.stream = kroute.source_stream; + route.flags = kroute.flags; +} + +void routeToKernel(const V4L2Subdevice::Route &route, + struct v4l2_subdev_route &kroute) +{ + kroute.sink_pad = route.sink.pad; + kroute.sink_stream = route.sink.stream; + kroute.source_pad = route.source.pad; + kroute.source_stream = route.source.stream; + kroute.flags = route.flags; +} + +/* + * Legacy routing support for pre-v6.10-rc1 kernels. Drop when v6.12-rc1 gets + * released. + */ +struct v4l2_subdev_routing_legacy { + __u32 which; + __u32 num_routes; + __u64 routes; + __u32 reserved[6]; +}; + +#define VIDIOC_SUBDEV_G_ROUTING_LEGACY _IOWR('V', 38, struct v4l2_subdev_routing_legacy) +#define VIDIOC_SUBDEV_S_ROUTING_LEGACY _IOWR('V', 39, struct v4l2_subdev_routing_legacy) + +} /* namespace */ + +int V4L2Subdevice::getRoutingLegacy(Routing *routing, Whence whence) +{ + struct v4l2_subdev_routing_legacy rt = {}; + + rt.which = whence; + + int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt); + if (ret == 0 || ret == -ENOTTY) + return ret; + + if (ret != -ENOSPC) { + LOG(V4L2, Error) + << "Failed to retrieve number of routes: " + << strerror(-ret); + return ret; + } + + std::vector<struct v4l2_subdev_route> routes{ rt.num_routes }; + rt.routes = reinterpret_cast<uintptr_t>(routes.data()); + + ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt); + if (ret) { + LOG(V4L2, Error) + << "Failed to retrieve routes: " << strerror(-ret); + return ret; + } + + if (rt.num_routes != routes.size()) { + LOG(V4L2, Error) << "Invalid number of routes"; + return -EINVAL; + } + + routing->resize(rt.num_routes); + + for (const auto &[i, route] : utils::enumerate(routes)) + routeFromKernel((*routing)[i], route); + + return 0; +} + +/** * \brief Retrieve the subdevice's internal routing table * \param[out] routing The routing table * \param[in] whence The routing table to get, \ref V4L2Subdevice::ActiveFormat @@ -596,6 +1542,8 @@ int V4L2Subdevice::setFormat(unsigned int pad, V4L2SubdeviceFormat *format, */ int V4L2Subdevice::getRouting(Routing *routing, Whence whence) { + routing->clear(); + if (!caps_.hasStreams()) return 0; @@ -604,18 +1552,24 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence) rt.which = whence; int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt); - if (ret == 0 || ret == -ENOTTY) - return ret; + if (ret == -ENOTTY) + return V4L2Subdevice::getRoutingLegacy(routing, whence); - if (ret != -ENOSPC) { + if (ret) { LOG(V4L2, Error) << "Failed to retrieve number of routes: " << strerror(-ret); return ret; } - routing->resize(rt.num_routes); - rt.routes = reinterpret_cast<uintptr_t>(routing->data()); + if (!rt.num_routes) + return 0; + + std::vector<struct v4l2_subdev_route> routes{ rt.num_routes }; + rt.routes = reinterpret_cast<uintptr_t>(routes.data()); + + rt.len_routes = rt.num_routes; + rt.num_routes = 0; ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt); if (ret) { @@ -624,11 +1578,43 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence) return ret; } - if (rt.num_routes != routing->size()) { + if (rt.num_routes != routes.size()) { LOG(V4L2, Error) << "Invalid number of routes"; return -EINVAL; } + routing->resize(rt.num_routes); + + for (const auto &[i, route] : utils::enumerate(routes)) + routeFromKernel((*routing)[i], route); + + return 0; +} + +int V4L2Subdevice::setRoutingLegacy(Routing *routing, Whence whence) +{ + std::vector<struct v4l2_subdev_route> routes{ routing->size() }; + + for (const auto &[i, route] : utils::enumerate(*routing)) + routeToKernel(route, routes[i]); + + struct v4l2_subdev_routing_legacy rt = {}; + rt.which = whence; + rt.num_routes = routes.size(); + rt.routes = reinterpret_cast<uintptr_t>(routes.data()); + + int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING_LEGACY, &rt); + if (ret) { + LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret); + return ret; + } + + routes.resize(rt.num_routes); + routing->resize(rt.num_routes); + + for (const auto &[i, route] : utils::enumerate(routes)) + routeFromKernel((*routing)[i], route); + return 0; } @@ -646,22 +1632,60 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence) */ int V4L2Subdevice::setRouting(Routing *routing, Whence whence) { - if (!caps_.hasStreams()) + if (!caps_.hasStreams()) { + routing->clear(); return 0; + } + + std::vector<struct v4l2_subdev_route> routes{ routing->size() }; + + for (const auto &[i, route] : utils::enumerate(*routing)) + routeToKernel(route, routes[i]); struct v4l2_subdev_routing rt = {}; rt.which = whence; - rt.num_routes = routing->size(); - rt.routes = reinterpret_cast<uintptr_t>(routing->data()); + rt.len_routes = routes.size(); + rt.num_routes = routes.size(); + rt.routes = reinterpret_cast<uintptr_t>(routes.data()); int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt); + if (ret == -ENOTTY) + return setRoutingLegacy(routing, whence); + if (ret) { LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret); return ret; } + /* + * The kernel may want to return more routes than we have space for. In + * that event, we must issue a VIDIOC_SUBDEV_G_ROUTING call to retrieve + * the additional routes. + */ + if (rt.num_routes > routes.size()) { + routes.resize(rt.num_routes); + + rt.len_routes = rt.num_routes; + rt.num_routes = 0; + + ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt); + if (ret) { + LOG(V4L2, Error) + << "Failed to retrieve routes: " << strerror(-ret); + return ret; + } + } + + if (rt.num_routes != routes.size()) { + LOG(V4L2, Error) << "Invalid number of routes"; + return -EINVAL; + } + routing->resize(rt.num_routes); + for (const auto &[i, route] : utils::enumerate(routes)) + routeFromKernel((*routing)[i], route); + return 0; } @@ -747,14 +1771,15 @@ std::string V4L2Subdevice::logPrefix() const return "'" + entity_->name() + "'"; } -std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad) +std::vector<unsigned int> V4L2Subdevice::enumPadCodes(const Stream &stream) { std::vector<unsigned int> codes; int ret; for (unsigned int index = 0; ; index++) { struct v4l2_subdev_mbus_code_enum mbusEnum = {}; - mbusEnum.pad = pad; + mbusEnum.pad = stream.pad; + mbusEnum.stream = stream.stream; mbusEnum.index = index; mbusEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -767,7 +1792,7 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad) if (ret < 0 && ret != -EINVAL) { LOG(V4L2, Error) - << "Unable to enumerate formats on pad " << pad + << "Unable to enumerate formats on pad " << stream << ": " << strerror(-ret); return {}; } @@ -775,7 +1800,7 @@ std::vector<unsigned int> V4L2Subdevice::enumPadCodes(unsigned int pad) return codes; } -std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad, +std::vector<SizeRange> V4L2Subdevice::enumPadSizes(const Stream &stream, unsigned int code) { std::vector<SizeRange> sizes; @@ -784,7 +1809,8 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad, for (unsigned int index = 0;; index++) { struct v4l2_subdev_frame_size_enum sizeEnum = {}; sizeEnum.index = index; - sizeEnum.pad = pad; + sizeEnum.pad = stream.pad; + sizeEnum.stream = stream.stream; sizeEnum.code = code; sizeEnum.which = V4L2_SUBDEV_FORMAT_ACTIVE; @@ -798,7 +1824,7 @@ std::vector<SizeRange> V4L2Subdevice::enumPadSizes(unsigned int pad, if (ret < 0 && ret != -EINVAL && ret != -ENOTTY) { LOG(V4L2, Error) - << "Unable to enumerate sizes on pad " << pad + << "Unable to enumerate sizes on pad " << stream << ": " << strerror(-ret); return {}; } diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index a72ef64d..d53aa2d3 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_videodevice.cpp - V4L2 Video Device + * V4L2 Video Device */ #include "libcamera/internal/v4l2_videodevice.h" @@ -10,7 +10,6 @@ #include <algorithm> #include <array> #include <fcntl.h> -#include <iomanip> #include <sstream> #include <string.h> #include <sys/ioctl.h> @@ -191,7 +190,7 @@ V4L2BufferCache::V4L2BufferCache(const std::vector<std::unique_ptr<FrameBuffer>> { for (const std::unique_ptr<FrameBuffer> &buffer : buffers) cache_.emplace_back(true, - lastUsedCounter_.fetch_add(1, std::memory_order_acq_rel), + lastUsedCounter_++, *buffer.get()); } @@ -259,7 +258,7 @@ int V4L2BufferCache::get(const FrameBuffer &buffer) return -ENOENT; cache_[use] = Entry(false, - lastUsedCounter_.fetch_add(1, std::memory_order_acq_rel), + lastUsedCounter_++, buffer); return use; @@ -803,12 +802,19 @@ std::string V4L2VideoDevice::logPrefix() const */ int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format) { - if (caps_.isMeta()) - return getFormatMeta(format); - else if (caps_.isMultiplanar()) - return getFormatMultiplane(format); - else + switch (bufferType_) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: return getFormatSingleplane(format); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return getFormatMultiplane(format); + case V4L2_BUF_TYPE_META_CAPTURE: + case V4L2_BUF_TYPE_META_OUTPUT: + return getFormatMeta(format); + default: + return -EINVAL; + } } /** @@ -823,12 +829,19 @@ int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format) */ int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format) { - if (caps_.isMeta()) - return trySetFormatMeta(format, false); - else if (caps_.isMultiplanar()) - return trySetFormatMultiplane(format, false); - else + switch (bufferType_) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: return trySetFormatSingleplane(format, false); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return trySetFormatMultiplane(format, false); + case V4L2_BUF_TYPE_META_CAPTURE: + case V4L2_BUF_TYPE_META_OUTPUT: + return trySetFormatMeta(format, false); + default: + return -EINVAL; + } } /** @@ -842,13 +855,25 @@ int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format) */ int V4L2VideoDevice::setFormat(V4L2DeviceFormat *format) { - int ret = 0; - if (caps_.isMeta()) - ret = trySetFormatMeta(format, true); - else if (caps_.isMultiplanar()) - ret = trySetFormatMultiplane(format, true); - else + int ret; + + switch (bufferType_) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT: ret = trySetFormatSingleplane(format, true); + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = trySetFormatMultiplane(format, true); + break; + case V4L2_BUF_TYPE_META_CAPTURE: + case V4L2_BUF_TYPE_META_OUTPUT: + ret = trySetFormatMeta(format, true); + break; + default: + ret = -EINVAL; + break; + } /* Cache the set format on success. */ if (ret) @@ -863,7 +888,7 @@ int V4L2VideoDevice::setFormat(V4L2DeviceFormat *format) int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format) { struct v4l2_format v4l2Format = {}; - struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; + struct v4l2_meta_format *meta = &v4l2Format.fmt.meta; int ret; v4l2Format.type = bufferType_; @@ -873,25 +898,42 @@ int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format) return ret; } - format->size.width = 0; - format->size.height = 0; - format->fourcc = V4L2PixelFormat(pix->dataformat); + format->fourcc = V4L2PixelFormat(meta->dataformat); + format->planes[0].size = meta->buffersize; format->planesCount = 1; - format->planes[0].bpl = pix->buffersize; - format->planes[0].size = pix->buffersize; + + bool genericLineBased = caps_.isMetaCapture() && + format->fourcc.isGenericLineBasedMetadata(); + + if (genericLineBased) { + format->size.width = meta->width; + format->size.height = meta->height; + format->planes[0].bpl = meta->bytesperline; + } else { + format->size.width = 0; + format->size.height = 0; + format->planes[0].bpl = meta->buffersize; + } return 0; } int V4L2VideoDevice::trySetFormatMeta(V4L2DeviceFormat *format, bool set) { + bool genericLineBased = caps_.isMetaCapture() && + format->fourcc.isGenericLineBasedMetadata(); struct v4l2_format v4l2Format = {}; - struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; + struct v4l2_meta_format *meta = &v4l2Format.fmt.meta; int ret; v4l2Format.type = bufferType_; - pix->dataformat = format->fourcc; - pix->buffersize = format->planes[0].size; + meta->dataformat = format->fourcc; + meta->buffersize = format->planes[0].size; + if (genericLineBased) { + meta->width = format->size.width; + meta->height = format->size.height; + meta->bytesperline = format->planes[0].bpl; + } ret = ioctl(set ? VIDIOC_S_FMT : VIDIOC_TRY_FMT, &v4l2Format); if (ret) { LOG(V4L2, Error) @@ -904,12 +946,18 @@ int V4L2VideoDevice::trySetFormatMeta(V4L2DeviceFormat *format, bool set) * Return to caller the format actually applied on the video device, * which might differ from the requested one. */ - format->size.width = 0; - format->size.height = 0; - format->fourcc = V4L2PixelFormat(pix->dataformat); + format->fourcc = V4L2PixelFormat(meta->dataformat); format->planesCount = 1; - format->planes[0].bpl = pix->buffersize; - format->planes[0].size = pix->buffersize; + format->planes[0].size = meta->buffersize; + if (genericLineBased) { + format->size.width = meta->width; + format->size.height = meta->height; + format->planes[0].bpl = meta->bytesperline; + } else { + format->size.width = 0; + format->size.height = 0; + format->planes[0].bpl = meta->buffersize; + } return 0; } @@ -1190,6 +1238,38 @@ std::vector<SizeRange> V4L2VideoDevice::enumSizes(V4L2PixelFormat pixelFormat) } /** + * \brief Get the selection rectangle for \a target + * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags + * \param[out] rect The selection rectangle to retrieve + * + * \todo Define a V4L2SelectionTarget enum for the selection target + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::getSelection(unsigned int target, Rectangle *rect) +{ + struct v4l2_selection sel = {}; + + sel.type = bufferType_; + sel.target = target; + sel.flags = 0; + + int ret = ioctl(VIDIOC_G_SELECTION, &sel); + if (ret < 0) { + LOG(V4L2, Error) << "Unable to get rectangle " << target + << ": " << strerror(-ret); + return ret; + } + + rect->x = sel.r.left; + rect->y = sel.r.top; + rect->width = sel.r.width; + rect->height = sel.r.height; + + return 0; +} + +/** * \brief Set a selection rectangle \a rect for \a target * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags * \param[inout] rect The selection rectangle to be applied @@ -1246,7 +1326,8 @@ int V4L2VideoDevice::requestBuffers(unsigned int count, if (rb.count < count) { LOG(V4L2, Error) - << "Not enough buffers provided by V4L2VideoDevice"; + << "Not enough buffers provided by V4L2VideoDevice. Wanted " + << count << ", got " << rb.count; requestBuffers(0, memoryType); return -ENOMEM; } @@ -1815,7 +1896,7 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer() * Detect kernel drivers which do not reset the sequence number to zero * on stream start. */ - if (!firstFrame_) { + if (!firstFrame_.has_value()) { if (buf.sequence) LOG(V4L2, Info) << "Zero sequence expected for first frame (got " @@ -2067,15 +2148,24 @@ V4L2PixelFormat V4L2VideoDevice::toV4L2PixelFormat(const PixelFormat &pixelForma * \class V4L2M2MDevice * \brief Memory-to-Memory video device * + * Memory to Memory devices in the kernel using the V4L2 M2M API can + * operate with multiple contexts for parallel operations on a single + * device. Each instance of a V4L2M2MDevice represents a single context. + * * The V4L2M2MDevice manages two V4L2VideoDevice instances on the same * deviceNode which operate together using two queues to implement the V4L2 * Memory to Memory API. * - * The two devices should be opened by calling open() on the V4L2M2MDevice, and - * can be closed by calling close on the V4L2M2MDevice. + * Users of this class should create a new instance of the V4L2M2MDevice for + * each desired execution context and then open it by calling open() on the + * V4L2M2MDevice and close it by calling close() on the V4L2M2MDevice. * * Calling V4L2VideoDevice::open() and V4L2VideoDevice::close() on the capture * or output V4L2VideoDevice is not permitted. + * + * Once the M2M device is open, users can operate on the output and capture + * queues represented by the V4L2VideoDevice returned by the output() and + * capture() functions. */ /** diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp new file mode 100644 index 00000000..4dad1b90 --- /dev/null +++ b/src/libcamera/vector.cpp @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Vector and related operations + */ + +#include "libcamera/internal/vector.h" + +#include <libcamera/base/log.h> + +/** + * \file vector.h + * \brief Vector class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Vector) + +/** + * \class Vector + * \brief Vector class + * \tparam T Type of numerical values to be stored in the vector + * \tparam Rows Number of dimension of the vector (= number of elements) + */ + +/** + * \fn Vector::Vector() + * \brief Construct an uninitialized vector + */ + +/** + * \fn Vector::Vector(T scalar) + * \brief Construct a vector filled with a \a scalar value + * \param[in] scalar The scalar value + */ + +/** + * \fn Vector::Vector(const std::array<T, Rows> &data) + * \brief Construct vector from supplied data + * \param data Data from which to construct a vector + * + * The size of \a data must be equal to the dimension size Rows of the vector. + */ + +/** + * \fn Vector::Vector(const Span<const T, Rows> data) + * \brief Construct vector from supplied data + * \param data Data from which to construct a vector + * + * The size of \a data must be equal to the dimension size Rows of the vector. + */ + +/** + * \fn T Vector::operator[](size_t i) const + * \brief Index to an element in the vector + * \param i Index of element to retrieve + * \return Element at index \a i from the vector + */ + +/** + * \fn T &Vector::operator[](size_t i) + * \copydoc Vector::operator[](size_t i) const + */ + +/** + * \fn Vector::operator-() const + * \brief Negate a Vector by negating both all of its coordinates + * \return The negated vector + */ + +/** + * \fn Vector::operator+(Vector const &other) const + * \brief Calculate the sum of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise sum of this vector and \a other + */ + +/** + * \fn Vector::operator+(T scalar) const + * \brief Calculate the sum of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise sum of this vector and \a other + */ + +/** + * \fn Vector::operator-(Vector const &other) const + * \brief Calculate the difference of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise subtraction of \a other from this vector + */ + +/** + * \fn Vector::operator-(T scalar) const + * \brief Calculate the difference of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise subtraction of \a scalar from this vector + */ + +/** + * \fn Vector::operator*(const Vector &other) const + * \brief Calculate the product of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise product of this vector and \a other + */ + +/** + * \fn Vector::operator*(T scalar) const + * \brief Calculate the product of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise product of this vector and \a scalar + */ + +/** + * \fn Vector::operator/(const Vector &other) const + * \brief Calculate the quotient of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise division of this vector by \a other + */ + +/** + * \fn Vector::operator/(T scalar) const + * \brief Calculate the quotient of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise division of this vector by \a scalar + */ + +/** + * \fn Vector::operator+=(Vector const &other) + * \brief Add \a other element-wise to this vector + * \param[in] other The other vector + * \return This vector + */ + +/** + * \fn Vector::operator+=(T scalar) + * \brief Add \a scalar element-wise to this vector + * \param[in] scalar The scalar + * \return This vector + */ + +/** + * \fn Vector::operator-=(Vector const &other) + * \brief Subtract \a other element-wise from this vector + * \param[in] other The other vector + * \return This vector + */ + +/** + * \fn Vector::operator-=(T scalar) + * \brief Subtract \a scalar element-wise from this vector + * \param[in] scalar The scalar + * \return This vector + */ + +/** + * \fn Vector::operator*=(const Vector &other) + * \brief Multiply this vector by \a other element-wise + * \param[in] other The other vector + * \return This vector + */ + +/** + * \fn Vector::operator*=(T scalar) + * \brief Multiply this vector by \a scalar element-wise + * \param[in] scalar The scalar + * \return This vector + */ + +/** + * \fn Vector::operator/=(const Vector &other) + * \brief Divide this vector by \a other element-wise + * \param[in] other The other vector + * \return This vector + */ + +/** + * \fn Vector::operator/=(T scalar) + * \brief Divide this vector by \a scalar element-wise + * \param[in] scalar The scalar + * \return This vector + */ + +/** + * \fn Vector::min(const Vector &other) const + * \brief Calculate the minimum of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise minimum of this vector and \a other + */ + +/** + * \fn Vector::min(T scalar) const + * \brief Calculate the minimum of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise minimum of this vector and \a scalar + */ + +/** + * \fn Vector::max(const Vector &other) const + * \brief Calculate the maximum of this vector and \a other element-wise + * \param[in] other The other vector + * \return The element-wise maximum of this vector and \a other + */ + +/** + * \fn Vector::max(T scalar) const + * \brief Calculate the maximum of this vector and \a scalar element-wise + * \param[in] scalar The scalar + * \return The element-wise maximum of this vector and \a scalar + */ + +/** + * \fn Vector::dot(const Vector<T, Rows> &other) const + * \brief Compute the dot product + * \param[in] other The other vector + * \return The dot product of the two vectors + */ + +/** + * \fn constexpr T &Vector::x() + * \brief Convenience function to access the first element of the vector + * \return The first element of the vector + */ + +/** + * \fn constexpr T &Vector::y() + * \brief Convenience function to access the second element of the vector + * \return The second element of the vector + */ + +/** + * \fn constexpr T &Vector::z() + * \brief Convenience function to access the third element of the vector + * \return The third element of the vector + */ + +/** + * \fn constexpr const T &Vector::x() const + * \copydoc Vector::x() + */ + +/** + * \fn constexpr const T &Vector::y() const + * \copydoc Vector::y() + */ + +/** + * \fn constexpr const T &Vector::z() const + * \copydoc Vector::z() + */ + +/** + * \fn constexpr T &Vector::r() + * \brief Convenience function to access the first element of the vector + * \return The first element of the vector + */ + +/** + * \fn constexpr T &Vector::g() + * \brief Convenience function to access the second element of the vector + * \return The second element of the vector + */ + +/** + * \fn constexpr T &Vector::b() + * \brief Convenience function to access the third element of the vector + * \return The third element of the vector + */ + +/** + * \fn constexpr const T &Vector::r() const + * \copydoc Vector::r() + */ + +/** + * \fn constexpr const T &Vector::g() const + * \copydoc Vector::g() + */ + +/** + * \fn constexpr const T &Vector::b() const + * \copydoc Vector::b() + */ + +/** + * \fn Vector::length2() + * \brief Get the squared length of the vector + * \return The squared length of the vector + */ + +/** + * \fn Vector::length() + * \brief Get the length of the vector + * \return The length of the vector + */ + +/** + * \fn Vector::sum() const + * \brief Calculate the sum of all the vector elements + * \tparam R The type of the sum + * + * The type R of the sum defaults to the type T of the elements, but can be set + * explicitly to use a different type in case the type T would risk + * overflowing. + * + * \return The sum of all the vector elements + */ + +/** + * \fn operator*(const Matrix<T, Rows, Cols> &m, const Vector<U, Cols> &v) + * \brief Multiply a matrix by a vector + * \tparam T Numerical type of the contents of the matrix + * \tparam U Numerical type of the contents of the vector + * \tparam Rows The number of rows in the matrix + * \tparam Cols The number of columns in the matrix (= rows in the vector) + * \param m The matrix + * \param v The vector + * \return Product of matrix \a m and vector \a v + */ + +/** + * \typedef RGB + * \brief A Vector of 3 elements representing an RGB pixel value + */ + +/** + * \fn bool operator==(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs) + * \brief Compare vectors for equality + * \return True if the two vectors are equal, false otherwise + */ + +/** + * \fn bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs) + * \brief Compare vectors for inequality + * \return True if the two vectors are not equal, false otherwise + */ + +#ifndef __DOXYGEN__ +bool vectorValidateYaml(const YamlObject &obj, unsigned int size) +{ + if (!obj.isList()) + return false; + + if (obj.size() != size) { + LOG(Vector, Error) + << "Wrong number of values in YAML vector: expected " + << size << ", got " << obj.size(); + return false; + } + + return true; +} +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/src/libcamera/version.cpp.in b/src/libcamera/version.cpp.in index 5aec08a1..bf5a2c30 100644 --- a/src/libcamera/version.cpp.in +++ b/src/libcamera/version.cpp.in @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * version.cpp - libcamera version + * libcamera version * * This file is auto-generated. Do not edit. */ diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp index bf21141e..a5e42461 100644 --- a/src/libcamera/yaml_parser.cpp +++ b/src/libcamera/yaml_parser.cpp @@ -2,15 +2,16 @@ /* * Copyright (C) 2022, Google Inc. * - * yaml_parser.cpp - libcamera YAML parsing helper + * libcamera YAML parsing helper */ #include "libcamera/internal/yaml_parser.h" -#include <cstdlib> +#include <charconv> #include <errno.h> #include <functional> #include <limits> +#include <stdlib.h> #include <libcamera/base/file.h> #include <libcamera/base/log.h> @@ -18,7 +19,7 @@ #include <yaml.h> /** - * \file libcamera/internal/yaml_parser.h + * \file yaml_parser.h * \brief A YAML parser helper */ @@ -38,12 +39,12 @@ static const YamlObject empty; * \brief A class representing the tree structure of the YAML content * * The YamlObject class represents the tree structure of YAML content. A - * YamlObject can be a dictionary or list of YamlObjects or a value if a tree - * leaf. + * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a + * tree leaf. */ YamlObject::YamlObject() - : type_(Type::Value) + : type_(Type::Empty) { } @@ -71,6 +72,20 @@ YamlObject::~YamlObject() = default; */ /** + * \fn YamlObject::isEmpty() + * \brief Return whether the YamlObject is an empty + * + * \return True if the YamlObject is empty, false otherwise + */ + +/** + * \fn YamlObject::operator bool() + * \brief Return whether the YamlObject is a non-empty + * + * \return False if the YamlObject is empty, true otherwise + */ + +/** * \fn YamlObject::size() * \brief Retrieve the number of elements in a dictionary or list YamlObject * @@ -104,7 +119,7 @@ std::size_t YamlObject::size() const */ /** - * \fn template<typename T> YamlObject::get<T>(const T &defaultValue) const + * \fn template<typename T, typename U> YamlObject::get<T>(U &&defaultValue) const * \brief Parse the YamlObject as a \a T value * \param[in] defaultValue The default value when failing to parse * @@ -118,172 +133,74 @@ std::size_t YamlObject::size() const #ifndef __DOXYGEN__ template<> -std::optional<bool> YamlObject::get() const +std::optional<bool> +YamlObject::Getter<bool>::get(const YamlObject &obj) const { - if (type_ != Type::Value) + if (obj.type_ != Type::Value) return std::nullopt; - if (value_ == "true") + if (obj.value_ == "true") return true; - else if (value_ == "false") + else if (obj.value_ == "false") return false; return std::nullopt; } -namespace { - -bool parseSignedInteger(const std::string &str, long min, long max, - long *result) -{ - if (str == "") - return false; - - char *end; - - errno = 0; - long value = std::strtol(str.c_str(), &end, 10); - - if ('\0' != *end || errno == ERANGE || value < min || value > max) - return false; - - *result = value; - return true; -} - -bool parseUnsignedInteger(const std::string &str, unsigned long max, - unsigned long *result) -{ - if (str == "") - return false; - - /* - * strtoul() accepts strings representing a negative number, in which - * case it negates the converted value. We don't want to silently accept - * negative values and return a large positive number, so check for a - * minus sign (after optional whitespace) and return an error. - */ - std::size_t found = str.find_first_not_of(" \t"); - if (found != std::string::npos && str[found] == '-') - return false; - - char *end; - - errno = 0; - unsigned long value = std::strtoul(str.c_str(), &end, 10); - - if ('\0' != *end || errno == ERANGE || value > max) - return false; - - *result = value; - return true; -} - -} /* namespace */ - -template<> -std::optional<int8_t> YamlObject::get() const -{ - if (type_ != Type::Value) - return std::nullopt; - - long value; - - if (!parseSignedInteger(value_, std::numeric_limits<int8_t>::min(), - std::numeric_limits<int8_t>::max(), &value)) - return std::nullopt; - - return value; -} - -template<> -std::optional<uint8_t> YamlObject::get() const -{ - if (type_ != Type::Value) - return std::nullopt; - - unsigned long value; - - if (!parseUnsignedInteger(value_, std::numeric_limits<uint8_t>::max(), - &value)) - return std::nullopt; - - return value; -} - -template<> -std::optional<int16_t> YamlObject::get() const -{ - if (type_ != Type::Value) - return std::nullopt; - - long value; - - if (!parseSignedInteger(value_, std::numeric_limits<int16_t>::min(), - std::numeric_limits<int16_t>::max(), &value)) - return std::nullopt; - - return value; -} - -template<> -std::optional<uint16_t> YamlObject::get() const +template<typename T> +struct YamlObject::Getter<T, std::enable_if_t< + std::is_same_v<int8_t, T> || + std::is_same_v<uint8_t, T> || + std::is_same_v<int16_t, T> || + std::is_same_v<uint16_t, T> || + std::is_same_v<int32_t, T> || + std::is_same_v<uint32_t, T>>> { - if (type_ != Type::Value) - return std::nullopt; - - unsigned long value; - - if (!parseUnsignedInteger(value_, std::numeric_limits<uint16_t>::max(), - &value)) - return std::nullopt; + std::optional<T> get(const YamlObject &obj) const + { + if (obj.type_ != Type::Value) + return std::nullopt; - return value; -} + const std::string &str = obj.value_; + T value; -template<> -std::optional<int32_t> YamlObject::get() const -{ - if (type_ != Type::Value) - return std::nullopt; + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), + value); + if (ptr != str.data() + str.size() || ec != std::errc()) + return std::nullopt; - long value; + return value; + } +}; - if (!parseSignedInteger(value_, std::numeric_limits<int32_t>::min(), - std::numeric_limits<int32_t>::max(), &value)) - return std::nullopt; - - return value; -} +template struct YamlObject::Getter<int8_t>; +template struct YamlObject::Getter<uint8_t>; +template struct YamlObject::Getter<int16_t>; +template struct YamlObject::Getter<uint16_t>; +template struct YamlObject::Getter<int32_t>; +template struct YamlObject::Getter<uint32_t>; template<> -std::optional<uint32_t> YamlObject::get() const +std::optional<float> +YamlObject::Getter<float>::get(const YamlObject &obj) const { - if (type_ != Type::Value) - return std::nullopt; - - unsigned long value; - - if (!parseUnsignedInteger(value_, std::numeric_limits<uint32_t>::max(), - &value)) - return std::nullopt; - - return value; + return obj.get<double>(); } template<> -std::optional<double> YamlObject::get() const +std::optional<double> +YamlObject::Getter<double>::get(const YamlObject &obj) const { - if (type_ != Type::Value) + if (obj.type_ != Type::Value) return std::nullopt; - if (value_ == "") + if (obj.value_.empty()) return std::nullopt; char *end; errno = 0; - double value = utils::strtod(value_.c_str(), &end); + double value = utils::strtod(obj.value_.c_str(), &end); if ('\0' != *end || errno == ERANGE) return std::nullopt; @@ -292,28 +209,30 @@ std::optional<double> YamlObject::get() const } template<> -std::optional<std::string> YamlObject::get() const +std::optional<std::string> +YamlObject::Getter<std::string>::get(const YamlObject &obj) const { - if (type_ != Type::Value) + if (obj.type_ != Type::Value) return std::nullopt; - return value_; + return obj.value_; } template<> -std::optional<Size> YamlObject::get() const +std::optional<Size> +YamlObject::Getter<Size>::get(const YamlObject &obj) const { - if (type_ != Type::List) + if (obj.type_ != Type::List) return std::nullopt; - if (list_.size() != 2) + if (obj.list_.size() != 2) return std::nullopt; - auto width = list_[0].value->get<uint32_t>(); + auto width = obj.list_[0].value->get<uint32_t>(); if (!width) return std::nullopt; - auto height = list_[1].value->get<uint32_t>(); + auto height = obj.list_[1].value->get<uint32_t>(); if (!height) return std::nullopt; @@ -339,6 +258,7 @@ std::optional<Size> YamlObject::get() const template<typename T, std::enable_if_t< std::is_same_v<bool, T> || + std::is_same_v<float, T> || std::is_same_v<double, T> || std::is_same_v<int8_t, T> || std::is_same_v<uint8_t, T> || @@ -367,6 +287,7 @@ std::optional<std::vector<T>> YamlObject::getList() const } template std::optional<std::vector<bool>> YamlObject::getList<bool>() const; +template std::optional<std::vector<float>> YamlObject::getList<float>() const; template std::optional<std::vector<double>> YamlObject::getList<double>() const; template std::optional<std::vector<int8_t>> YamlObject::getList<int8_t>() const; template std::optional<std::vector<uint8_t>> YamlObject::getList<uint8_t>() const; @@ -424,7 +345,8 @@ template std::optional<std::vector<Size>> YamlObject::getList<Size>() const; * * This function retrieves an element of the YamlObject. Only YamlObject * instances of List type associate elements with index, calling this function - * on other types of instances is invalid and results in undefined behaviour. + * on other types of instances or with an invalid index results in an empty + * object. * * \return The YamlObject as an element of the list */ @@ -447,31 +369,31 @@ const YamlObject &YamlObject::operator[](std::size_t index) const * * \return True if an element exists, false otherwise */ -bool YamlObject::contains(const std::string &key) const +bool YamlObject::contains(std::string_view key) const { - if (dictionary_.find(std::ref(key)) == dictionary_.end()) - return false; - - return true; + return dictionary_.find(key) != dictionary_.end(); } /** - * \fn YamlObject::operator[](const std::string &key) const + * \fn YamlObject::operator[](std::string_view key) const * \brief Retrieve a member by name from the dictionary * * This function retrieve a member of a YamlObject by name. Only YamlObject * instances of Dictionary type associate elements with names, calling this - * function on other types of instances is invalid and results in undefined - * behaviour. + * function on other types of instances or with a nonexistent key results in an + * empty object. * * \return The YamlObject corresponding to the \a key member */ -const YamlObject &YamlObject::operator[](const std::string &key) const +const YamlObject &YamlObject::operator[](std::string_view key) const { - if (type_ != Type::Dictionary || !contains(key)) + if (type_ != Type::Dictionary) return empty; auto iter = dictionary_.find(key); + if (iter == dictionary_.end()) + return empty; + return *iter->second; } @@ -587,8 +509,17 @@ YamlParserContext::EventPtr YamlParserContext::nextEvent() EventPtr event(new yaml_event_t); /* yaml_parser_parse returns 1 when it succeeds */ - if (!yaml_parser_parse(&parser_, event.get())) + if (!yaml_parser_parse(&parser_, event.get())) { + File *file = static_cast<File *>(parser_.read_handler_data); + + LOG(YamlParser, Error) << file->fileName() << ":" + << parser_.problem_mark.line << ":" + << parser_.problem_mark.column << " " + << parser_.problem << " " + << parser_.context; + return nullptr; + } return event; } diff --git a/src/meson.build b/src/meson.build index 165a77bb..8eb8f05b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,37 @@ else ipa_sign_module = false endif +# libyuv, used by the Android adaptation layer and the virtual pipeline handler. +# Fallback to a subproject if libyuv isn't found, as it's typically not provided +# by distributions. Where libyuv is provided by a distribution, it may not +# always supply a pkg-config implementation, requiring cxx.find_library() to +# search for it. +if not get_option('force_fallback_for').contains('libyuv') + libyuv_dep = dependency('libyuv', required : false) + if not libyuv_dep.found() + libyuv_dep = cxx.find_library('yuv', has_headers : 'libyuv.h', + required : false) + endif +else + libyuv_dep = dependency('', required : false) +endif + +if (pipelines.contains('virtual') or get_option('android').allowed()) and \ + not libyuv_dep.found() + cmake = import('cmake') + + libyuv_vars = cmake.subproject_options() + libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'}) + libyuv_vars.set_override_option('cpp_std', 'c++17') + libyuv_vars.append_compile_args('cpp', + '-Wno-sign-compare', + '-Wno-unused-variable', + '-Wno-unused-parameter') + libyuv_vars.append_link_args('-ljpeg') + libyuv = cmake.subproject('libyuv', options : libyuv_vars) + libyuv_dep = libyuv.dependency('yuv') +endif + # libcamera must be built first as a dependency to the other components. subdir('libcamera') diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py index c1723b44..22d8c4da 100644 --- a/src/py/cam/cam_qt.py +++ b/src/py/cam/cam_qt.py @@ -2,7 +2,7 @@ # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> from helpers import mfb_to_rgb -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtGui, QtWidgets import libcamera as libcam import libcamera.utils import sys @@ -63,10 +63,10 @@ class QtRenderer: self.buf_mmap_map = buf_mmap_map def run(self): - camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Type.Read) camnotif.activated.connect(lambda _: self.readcam()) - keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) + keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read) keynotif.activated.connect(lambda _: self.readkey()) print('Capturing...') diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py index 6cfbd347..35b4b06b 100644 --- a/src/py/cam/cam_qtgl.py +++ b/src/py/cam/cam_qtgl.py @@ -1,8 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> -from PyQt5 import QtCore, QtWidgets -from PyQt5.QtCore import Qt +from PyQt6 import QtCore, QtWidgets +from PyQt6.QtCore import Qt import math import os @@ -142,10 +142,10 @@ class QtRenderer: self.window = window def run(self): - camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Read) + camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Type.Read) camnotif.activated.connect(lambda _: self.readcam()) - keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read) + keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read) keynotif.activated.connect(lambda _: self.readkey()) print('Capturing...') @@ -175,8 +175,8 @@ class MainWindow(QtWidgets.QWidget): def __init__(self, state): super().__init__() - self.setAttribute(Qt.WA_PaintOnScreen) - self.setAttribute(Qt.WA_NativeWindow) + self.setAttribute(Qt.WidgetAttribute.WA_PaintOnScreen) + self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow) self.state = state diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py index 9948c41e..d43a7c1c 100755 --- a/src/py/libcamera/gen-py-controls.py +++ b/src/py/libcamera/gen-py-controls.py @@ -4,10 +4,12 @@ # Generate Python bindings controls from YAML import argparse -import string +import jinja2 import sys import yaml +from controls import Control + def find_common_prefix(strings): prefix = strings[0] @@ -21,87 +23,86 @@ def find_common_prefix(strings): return prefix -def generate_py(controls, mode): - out = '' - - for ctrl in controls: - name, ctrl = ctrl.popitem() - - if ctrl.get('draft'): - ns = 'libcamera::{}::draft::'.format(mode) - container = 'draft' - else: - ns = 'libcamera::{}::'.format(mode) - container = 'controls' - - out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n' - - enum = ctrl.get('enum') - if not enum: - continue - - cpp_enum = name + 'Enum' +def extend_control(ctrl, mode): + if ctrl.vendor != 'libcamera': + ctrl.klass = ctrl.vendor + ctrl.namespace = f'{ctrl.vendor}::' + else: + ctrl.klass = mode + ctrl.namespace = '' - out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum) + if not ctrl.is_enum: + return ctrl - if mode == 'controls': - # Adjustments for controls - if name == 'LensShadingMapMode': - prefix = 'LensShadingMapMode' - else: - prefix = find_common_prefix([e['name'] for e in enum]) + if mode == 'controls': + # Adjustments for controls + if ctrl.name == 'LensShadingMapMode': + prefix = 'LensShadingMapMode' else: - # Adjustments for properties - prefix = find_common_prefix([e['name'] for e in enum]) - - for entry in enum: - cpp_enum = entry['name'] - py_enum = entry['name'][len(prefix):] - - out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum) - - out += '\t;\n\n' - - return {'controls': out} + prefix = find_common_prefix([e.name for e in ctrl.enum_values]) + else: + # Adjustments for properties + prefix = find_common_prefix([e.name for e in ctrl.enum_values]) + for enum in ctrl.enum_values: + enum.py_name = enum.name[len(prefix):] -def fill_template(template, data): - template = open(template, 'rb').read() - template = template.decode('utf-8') - template = string.Template(template) - return template.substitute(data) + return ctrl def main(argv): + headers = { + 'controls': 'control_ids.h', + 'properties': 'property_ids.h', + } + # Parse command line arguments parser = argparse.ArgumentParser() - parser.add_argument('-o', dest='output', metavar='file', type=str, + parser.add_argument('--mode', '-m', type=str, required=True, + help='Mode is either "controls" or "properties"') + parser.add_argument('--output', '-o', metavar='file', type=str, help='Output file name. Defaults to standard output if not specified.') - parser.add_argument('input', type=str, - help='Input file name.') - parser.add_argument('template', type=str, + parser.add_argument('--template', '-t', type=str, required=True, help='Template file name.') - parser.add_argument('--mode', type=str, required=True, - help='Mode is either "controls" or "properties"') + parser.add_argument('input', type=str, nargs='+', + help='Input file name.') args = parser.parse_args(argv[1:]) - if args.mode not in ['controls', 'properties']: + if not headers.get(args.mode): print(f'Invalid mode option "{args.mode}"', file=sys.stderr) return -1 - data = open(args.input, 'rb').read() - controls = yaml.safe_load(data)['controls'] + controls = [] + vendors = [] + + for input in args.input: + data = yaml.safe_load(open(input, 'rb').read()) + + vendor = data['vendor'] + if vendor != 'libcamera': + vendors.append(vendor) + + for ctrl in data['controls']: + ctrl = Control(*ctrl.popitem(), vendor, args.mode) + controls.append(extend_control(ctrl, args.mode)) - data = generate_py(controls, args.mode) + data = { + 'mode': args.mode, + 'header': headers[args.mode], + 'vendors': vendors, + 'controls': controls, + } - data = fill_template(args.template, data) + env = jinja2.Environment() + template = env.from_string(open(args.template, 'r', encoding='utf-8').read()) + string = template.render(data) if args.output: - output = open(args.output, 'wb') - output.write(data.encode('utf-8')) + output = open(args.output, 'w', encoding='utf-8') + output.write(string) output.close() else: - sys.stdout.write(data) + sys.stdout.write(string) return 0 diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index f58c7198..33ab6579 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -1,21 +1,5 @@ # SPDX-License-Identifier: CC0-1.0 -py3_dep = dependency('python3', required : get_option('pycamera')) - -if not py3_dep.found() - pycamera_enabled = false - subdir_done() -endif - -pybind11_dep = dependency('pybind11', required : get_option('pycamera')) - -if not pybind11_dep.found() - pycamera_enabled = false - subdir_done() -endif - -pycamera_enabled = true - pycamera_sources = files([ 'py_camera_manager.cpp', 'py_color_space.cpp', @@ -26,31 +10,26 @@ pycamera_sources = files([ 'py_transform.cpp', ]) -# Generate controls - -gen_py_controls_input_files = files([ - '../../libcamera/control_ids.yaml', - 'py_controls_generated.cpp.in', -]) +# Generate controls and properties +gen_py_controls_template = files('py_controls_generated.cpp.in') gen_py_controls = files('gen-py-controls.py') pycamera_sources += custom_target('py_gen_controls', - input : gen_py_controls_input_files, + input : controls_files, output : ['py_controls_generated.cpp'], - 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', -]) + command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', + '-t', gen_py_controls_template, '@INPUT@'], + depend_files : [py_mod_controls], + env : py_build_env) pycamera_sources += custom_target('py_gen_properties', - input : gen_py_property_enums_input_files, + input : properties_files, output : ['py_properties_generated.cpp'], - command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@']) + command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', + '-t', gen_py_controls_template, '@INPUT@'], + depend_files : [py_mod_controls], + env : py_build_env) # Generate formats @@ -84,6 +63,7 @@ pycamera = shared_module('_libcamera', pycamera_sources, install : true, install_dir : destdir, + install_tag : 'python-runtime', name_prefix : '', dependencies : pycamera_deps, cpp_args : pycamera_args) @@ -99,7 +79,9 @@ run_command('ln', '-fsrT', meson.current_source_dir() / 'utils', meson.current_build_dir() / 'utils', check : true) -install_data(['__init__.py'], install_dir : destdir) +install_data(['__init__.py'], + install_dir : destdir, + install_tag : 'python-runtime') # \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files # Note: Depends on pybind11-stubgen. To generate pylibcamera stubs: diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 3574db23..af69b915 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -20,7 +20,7 @@ public: ~PyCameraManager(); pybind11::list cameras(); - std::shared_ptr<Camera> get(const std::string &name) { return cameraManager_->get(name); } + std::shared_ptr<Camera> get(std::string_view name) { return cameraManager_->get(name); } static const std::string &version() { return CameraManager::version(); } diff --git a/src/py/libcamera/py_color_space.cpp b/src/py/libcamera/py_color_space.cpp index 5201121a..fd5a5dab 100644 --- a/src/py/libcamera/py_color_space.cpp +++ b/src/py/libcamera/py_color_space.cpp @@ -12,6 +12,8 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include "py_main.h" + namespace py = pybind11; using namespace libcamera; diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in index 18fa57d9..22a132d1 100644 --- a/src/py/libcamera/py_controls_generated.cpp.in +++ b/src/py/libcamera/py_controls_generated.cpp.in @@ -2,29 +2,46 @@ /* * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> * - * Python bindings - Auto-generated controls + * Python bindings - Auto-generated {{mode}} * * This file is auto-generated. Do not edit. */ -#include <libcamera/control_ids.h> +#include <libcamera/{{header}}> #include <pybind11/pybind11.h> +#include "py_main.h" + namespace py = pybind11; -class PyControls +class Py{{mode|capitalize}} { }; -class PyDraftControls +{% for vendor in vendors -%} +class Py{{vendor|capitalize}}{{mode|capitalize}} { }; -void init_py_controls_generated(py::module& m) +{% endfor -%} + +void init_py_{{mode}}_generated(py::module& m) { - auto controls = py::class_<PyControls>(m, "controls"); - auto draft = py::class_<PyDraftControls>(controls, "draft"); + auto {{mode}} = py::class_<Py{{mode|capitalize}}>(m, "{{mode}}"); +{%- for vendor in vendors %} + auto {{vendor}} = py::class_<Py{{vendor|capitalize}}{{mode|capitalize}}>({{mode}}, "{{vendor}}"); +{%- endfor %} + +{% for ctrl in controls %} + {{ctrl.klass}}.def_readonly_static("{{ctrl.name}}", static_cast<const libcamera::ControlId *>(&libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}})); +{%- if ctrl.is_enum %} -${controls} + py::enum_<libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}Enum>({{ctrl.klass}}, "{{ctrl.name}}Enum") +{%- for enum in ctrl.enum_values %} + .value("{{enum.py_name}}", libcamera::{{mode}}::{{ctrl.namespace}}{{enum.name}}) +{%- endfor %} + ; +{%- endif %} +{% endfor -%} } diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp index e25689c6..9e75ec1a 100644 --- a/src/py/libcamera/py_enums.cpp +++ b/src/py/libcamera/py_enums.cpp @@ -9,6 +9,8 @@ #include <pybind11/pybind11.h> +#include "py_main.h" + namespace py = pybind11; using namespace libcamera; @@ -30,7 +32,8 @@ void init_py_enums(py::module &m) .value("Float", ControlType::ControlTypeFloat) .value("String", ControlType::ControlTypeString) .value("Rectangle", ControlType::ControlTypeRectangle) - .value("Size", ControlType::ControlTypeSize); + .value("Size", ControlType::ControlTypeSize) + .value("Point", ControlType::ControlTypePoint); py::enum_<Orientation>(m, "Orientation") .value("Rotate0", Orientation::Rotate0) diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/libcamera/py_formats_generated.cpp.in index a3f7f94d..c5fb9063 100644 --- a/src/py/libcamera/py_formats_generated.cpp.in +++ b/src/py/libcamera/py_formats_generated.cpp.in @@ -11,6 +11,8 @@ #include <pybind11/pybind11.h> +#include "py_main.h" + namespace py = pybind11; class PyFormats diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp index 5c2aeac4..c7e30360 100644 --- a/src/py/libcamera/py_geometry.cpp +++ b/src/py/libcamera/py_geometry.cpp @@ -14,6 +14,8 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include "py_main.h" + namespace py = pybind11; using namespace libcamera; diff --git a/src/py/libcamera/py_helpers.cpp b/src/py/libcamera/py_helpers.cpp index 79891ab6..1ad1d4c1 100644 --- a/src/py/libcamera/py_helpers.cpp +++ b/src/py/libcamera/py_helpers.cpp @@ -34,6 +34,8 @@ static py::object valueOrTuple(const ControlValue &cv) py::object controlValueToPy(const ControlValue &cv) { switch (cv.type()) { + case ControlTypeNone: + return py::none(); case ControlTypeBool: return valueOrTuple<bool>(cv); case ControlTypeByte: @@ -46,14 +48,14 @@ py::object controlValueToPy(const ControlValue &cv) return valueOrTuple<float>(cv); case ControlTypeString: return py::cast(cv.get<std::string>()); - case ControlTypeRectangle: - return valueOrTuple<Rectangle>(cv); case ControlTypeSize: { const Size *v = reinterpret_cast<const Size *>(cv.data().data()); return py::cast(v); } - case ControlTypeNone: - return py::none(); + case ControlTypeRectangle: + return valueOrTuple<Rectangle>(cv); + case ControlTypePoint: + return valueOrTuple<Point>(cv); default: throw std::runtime_error("Unsupported ControlValue type"); } @@ -73,6 +75,8 @@ static ControlValue controlValueMaybeArray(const py::object &ob) ControlValue pyToControlValue(const py::object &ob, ControlType type) { switch (type) { + case ControlTypeNone: + return ControlValue(); case ControlTypeBool: return ControlValue(ob.cast<bool>()); case ControlTypeByte: @@ -89,8 +93,8 @@ ControlValue pyToControlValue(const py::object &ob, ControlType type) return controlValueMaybeArray<Rectangle>(ob); case ControlTypeSize: return ControlValue(ob.cast<Size>()); - case ControlTypeNone: - return ControlValue(); + case ControlTypePoint: + return controlValueMaybeArray<Point>(ob); default: throw std::runtime_error("Control type not implemented"); } diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index bce08218..441a70ab 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -7,6 +7,7 @@ #include "py_main.h" +#include <limits> #include <memory> #include <stdexcept> #include <string> @@ -85,14 +86,6 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, PyCameraSmartPtr<T>) */ static std::weak_ptr<PyCameraManager> gCameraManager; -void init_py_color_space(py::module &m); -void init_py_controls_generated(py::module &m); -void init_py_enums(py::module &m); -void init_py_formats_generated(py::module &m); -void init_py_geometry(py::module &m); -void init_py_properties_generated(py::module &m); -void init_py_transform(py::module &m); - PYBIND11_MODULE(_libcamera, m) { init_py_enums(m); @@ -407,12 +400,26 @@ PYBIND11_MODULE(_libcamera, m) pyControlId .def_property_readonly("id", &ControlId::id) .def_property_readonly("name", &ControlId::name) + .def_property_readonly("vendor", &ControlId::vendor) .def_property_readonly("type", &ControlId::type) + .def_property_readonly("isArray", &ControlId::isArray) + .def_property_readonly("size", &ControlId::size) .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()); - }); + std::string sizeStr = ""; + if (self.isArray()) { + sizeStr = "["; + size_t size = self.size(); + if (size == std::numeric_limits<size_t>::max()) + sizeStr += "n"; + else + sizeStr += std::to_string(size); + sizeStr += "]"; + } + return py::str("libcamera.ControlId({}, {}.{}{}, {})") + .format(self.id(), self.vendor(), self.name(), sizeStr, self.type()); + }) + .def("enumerators", &ControlId::enumerators); pyControlInfo .def_property_readonly("min", [](const ControlInfo &self) { diff --git a/src/py/libcamera/py_main.h b/src/py/libcamera/py_main.h index 5bb5f2d1..4d594326 100644 --- a/src/py/libcamera/py_main.h +++ b/src/py/libcamera/py_main.h @@ -7,8 +7,18 @@ #include <libcamera/base/log.h> +#include <pybind11/pybind11.h> + namespace libcamera { LOG_DECLARE_CATEGORY(Python) } + +void init_py_color_space(pybind11::module &m); +void init_py_controls_generated(pybind11::module &m); +void init_py_enums(pybind11::module &m); +void init_py_formats_generated(pybind11::module &m); +void init_py_geometry(pybind11::module &m); +void init_py_properties_generated(pybind11::module &m); +void init_py_transform(pybind11::module &m); diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in deleted file mode 100644 index e49b6e91..00000000 --- a/src/py/libcamera/py_properties_generated.cpp.in +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> - * - * Python bindings - Auto-generated properties - * - * This file is auto-generated. Do not edit. - */ - -#include <libcamera/property_ids.h> - -#include <pybind11/pybind11.h> - -namespace py = pybind11; - -class PyProperties -{ -}; - -class PyDraftProperties -{ -}; - -void init_py_properties_generated(py::module& m) -{ - auto controls = py::class_<PyProperties>(m, "properties"); - auto draft = py::class_<PyDraftProperties>(controls, "draft"); - -${controls} -} diff --git a/src/py/libcamera/py_transform.cpp b/src/py/libcamera/py_transform.cpp index f3a0bfaf..768260ff 100644 --- a/src/py/libcamera/py_transform.cpp +++ b/src/py/libcamera/py_transform.cpp @@ -12,6 +12,8 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include "py_main.h" + namespace py = pybind11; using namespace libcamera; diff --git a/src/py/meson.build b/src/py/meson.build index a4586b4a..92280697 100644 --- a/src/py/meson.build +++ b/src/py/meson.build @@ -1,3 +1,15 @@ # SPDX-License-Identifier: CC0-1.0 +py3_dep = dependency('python3', required : get_option('pycamera')) +pybind11_dep = dependency('pybind11', required : get_option('pycamera')) + +pycamera_enabled = py3_dep.found() and pybind11_dep.found() +if not pycamera_enabled + subdir_done() +endif + subdir('libcamera') + +pycamera_devenv = environment() +pycamera_devenv.prepend('PYTHONPATH', meson.current_build_dir()) +meson.add_devenv(pycamera_devenv) diff --git a/src/v4l2/meson.build b/src/v4l2/meson.build index ab4b35dd..2c040414 100644 --- a/src/v4l2/meson.build +++ b/src/v4l2/meson.build @@ -1,12 +1,11 @@ # SPDX-License-Identifier: CC0-1.0 -if not get_option('v4l2') - v4l2_enabled = false +v4l2_enabled = get_option('v4l2').allowed() + +if not v4l2_enabled subdir_done() endif -v4l2_enabled = true - v4l2_compat_sources = files([ 'v4l2_camera.cpp', 'v4l2_camera_file.cpp', @@ -24,6 +23,7 @@ v4l2_compat_cpp_args = [ '-U_FILE_OFFSET_BITS', '-D_FILE_OFFSET_BITS=32', '-D_LARGEFILE64_SOURCE', + '-U_TIME_BITS', '-fvisibility=hidden', ] @@ -44,4 +44,5 @@ cdata.set('LIBCAMERA_V4L2_SO', get_option('prefix') / libcamera_libexecdir / 'v4 configure_file(input : 'libcamerify.in', output : 'libcamerify', configuration : cdata, - install_dir : get_option('bindir')) + install_dir : get_option('bindir'), + install_tag : 'bin') diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp index 7b97c2d5..94d138cd 100644 --- a/src/v4l2/v4l2_camera.cpp +++ b/src/v4l2/v4l2_camera.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_camera.cpp - V4L2 compatibility camera + * V4L2 compatibility camera */ #include "v4l2_camera.h" @@ -12,13 +12,15 @@ #include <libcamera/base/log.h> +#include <libcamera/control_ids.h> + using namespace libcamera; LOG_DECLARE_CATEGORY(V4L2Compat) V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera) - : camera_(camera), isRunning_(false), bufferAllocator_(nullptr), - efd_(-1), bufferAvailableCount_(0) + : camera_(camera), controls_(controls::controls), isRunning_(false), + bufferAllocator_(nullptr), efd_(-1), bufferAvailableCount_(0) { camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete); } @@ -202,10 +204,12 @@ int V4L2Camera::streamOn() if (isRunning_) return 0; - int ret = camera_->start(); + int ret = camera_->start(&controls_); if (ret < 0) return ret == -EACCES ? -EBUSY : ret; + controls_.clear(); + isRunning_ = true; for (Request *req : pendingRequests_) { @@ -265,6 +269,8 @@ int V4L2Camera::qbuf(unsigned int index) return 0; } + request->controls().merge(std::move(controls_)); + ret = camera_->queueRequest(request); if (ret < 0) { LOG(V4L2Compat, Error) << "Can't queue request"; diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h index d3483444..9bd161b9 100644 --- a/src/v4l2/v4l2_camera.h +++ b/src/v4l2/v4l2_camera.h @@ -2,19 +2,21 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_camera.h - V4L2 compatibility camera + * V4L2 compatibility camera */ #pragma once #include <deque> -#include <utility> +#include <memory> +#include <vector> #include <libcamera/base/mutex.h> #include <libcamera/base/semaphore.h> #include <libcamera/base/shared_fd.h> #include <libcamera/camera.h> +#include <libcamera/controls.h> #include <libcamera/framebuffer.h> #include <libcamera/framebuffer_allocator.h> @@ -49,6 +51,9 @@ public: const libcamera::Size &size, libcamera::StreamConfiguration *streamConfigOut); + libcamera::ControlList &controls() { return controls_; } + const libcamera::ControlInfoMap &controlInfo() { return camera_->controls(); } + int allocBuffers(unsigned int count); void freeBuffers(); int getBufferFd(unsigned int index); @@ -70,6 +75,8 @@ private: std::shared_ptr<libcamera::Camera> camera_; std::unique_ptr<libcamera::CameraConfiguration> config_; + libcamera::ControlList controls_; + bool isRunning_; libcamera::Mutex bufferLock_; diff --git a/src/v4l2/v4l2_camera_file.cpp b/src/v4l2/v4l2_camera_file.cpp index 0a41587c..d8fe854b 100644 --- a/src/v4l2/v4l2_camera_file.cpp +++ b/src/v4l2/v4l2_camera_file.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * v4l2_camera_file.h - V4L2 compatibility camera file information + * V4L2 compatibility camera file information */ #include "v4l2_camera_file.h" diff --git a/src/v4l2/v4l2_camera_file.h b/src/v4l2/v4l2_camera_file.h index 1a7b6a63..1212989e 100644 --- a/src/v4l2/v4l2_camera_file.h +++ b/src/v4l2/v4l2_camera_file.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * v4l2_camera_file.h - V4L2 compatibility camera file information + * V4L2 compatibility camera file information */ #pragma once diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp index 341f7902..559ffc61 100644 --- a/src/v4l2/v4l2_camera_proxy.cpp +++ b/src/v4l2/v4l2_camera_proxy.cpp @@ -2,13 +2,12 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_camera_proxy.cpp - Proxy to V4L2 compatibility camera + * Proxy to V4L2 compatibility camera */ #include "v4l2_camera_proxy.h" #include <algorithm> -#include <array> #include <errno.h> #include <numeric> #include <set> @@ -23,9 +22,11 @@ #include <libcamera/base/utils.h> #include <libcamera/camera.h> +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> #include <libcamera/formats.h> -#include "libcamera/internal/formats.h" +#include "libcamera/internal/v4l2_pixelformat.h" #include "v4l2_camera.h" #include "v4l2_camera_file.h" @@ -34,6 +35,7 @@ #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) using namespace libcamera; +using namespace std::literals::chrono_literals; LOG_DECLARE_CATEGORY(V4L2Compat) @@ -193,6 +195,29 @@ void V4L2CameraProxy::setFmtFromConfig(const StreamConfiguration &streamConfig) v4l2PixFormat_.xfer_func = V4L2_XFER_FUNC_DEFAULT; sizeimage_ = streamConfig.frameSize; + + const ControlInfoMap &controls = vcam_->controlInfo(); + const auto &it = controls.find(&controls::FrameDurationLimits); + + if (it != controls.end()) { + const int64_t duration = it->second.def().get<int64_t>(); + + v4l2TimePerFrame_.numerator = duration; + v4l2TimePerFrame_.denominator = 1000000; + } else { + /* + * Default to 30fps if the camera doesn't expose the + * FrameDurationLimits control. + * + * \todo Remove this once all pipeline handlers implement the + * control + */ + LOG(V4L2Compat, Warning) + << "Camera does not support FrameDurationLimits"; + + v4l2TimePerFrame_.numerator = 333333; + v4l2TimePerFrame_.denominator = 1000000; + } } void V4L2CameraProxy::querycap(std::shared_ptr<Camera> camera) @@ -756,6 +781,55 @@ int V4L2CameraProxy::vidioc_streamoff(V4L2CameraFile *file, int *arg) return ret; } +int V4L2CameraProxy::vidioc_g_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg) +{ + LOG(V4L2Compat, Debug) + << "[" << file->description() << "] " << __func__ << "()"; + + if (!validateBufferType(arg->type)) + return -EINVAL; + + memset(&arg->parm, 0, sizeof(arg->parm)); + + arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + arg->parm.capture.timeperframe = v4l2TimePerFrame_; + + return 0; +} + +int V4L2CameraProxy::vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg) +{ + LOG(V4L2Compat, Debug) + << "[" << file->description() << "] " << __func__ << "()"; + + if (!validateBufferType(arg->type)) + return -EINVAL; + + /* + * Store the frame duration if it is valid, otherwise keep the current + * value. + * + * \todo The provided value should be adjusted based on the camera + * capabilities. + */ + if (arg->parm.capture.timeperframe.numerator && + arg->parm.capture.timeperframe.denominator) + v4l2TimePerFrame_ = arg->parm.capture.timeperframe; + + memset(&arg->parm, 0, sizeof(arg->parm)); + + arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + arg->parm.capture.timeperframe = v4l2TimePerFrame_; + + /* Apply the frame duration. */ + utils::Duration frameDuration = 1.0s * v4l2TimePerFrame_.numerator + / v4l2TimePerFrame_.denominator; + int64_t uDuration = frameDuration.get<std::micro>(); + vcam_->controls().set(controls::FrameDurationLimits, { uDuration, uDuration }); + + return 0; +} + const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = { VIDIOC_QUERYCAP, VIDIOC_ENUM_FRAMESIZES, @@ -776,6 +850,8 @@ const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = { VIDIOC_EXPBUF, VIDIOC_STREAMON, VIDIOC_STREAMOFF, + VIDIOC_G_PARM, + VIDIOC_S_PARM, }; int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg) @@ -863,6 +939,12 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void case VIDIOC_STREAMOFF: ret = vidioc_streamoff(file, static_cast<int *>(arg)); break; + case VIDIOC_G_PARM: + ret = vidioc_g_parm(file, static_cast<struct v4l2_streamparm *>(arg)); + break; + case VIDIOC_S_PARM: + ret = vidioc_s_parm(file, static_cast<struct v4l2_streamparm *>(arg)); + break; default: ret = -ENOTTY; break; diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h index 8a0195e1..5aa352c3 100644 --- a/src/v4l2/v4l2_camera_proxy.h +++ b/src/v4l2/v4l2_camera_proxy.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_camera_proxy.h - Proxy to V4L2 compatibility camera + * Proxy to V4L2 compatibility camera */ #pragma once @@ -67,6 +67,8 @@ private: int vidioc_expbuf(V4L2CameraFile *file, struct v4l2_exportbuffer *arg); int vidioc_streamon(V4L2CameraFile *file, int *arg); int vidioc_streamoff(V4L2CameraFile *file, int *arg); + int vidioc_g_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg); + int vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg); bool hasOwnership(V4L2CameraFile *file); int acquire(V4L2CameraFile *file); @@ -84,6 +86,7 @@ private: struct v4l2_capability capabilities_; struct v4l2_pix_format v4l2PixFormat_; + struct v4l2_fract v4l2TimePerFrame_; std::vector<struct v4l2_buffer> buffers_; std::map<void *, unsigned int> mmaps_; diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp index 1765fb5d..ff833f57 100644 --- a/src/v4l2/v4l2_compat.cpp +++ b/src/v4l2/v4l2_compat.cpp @@ -2,17 +2,19 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_compat.cpp - V4L2 compatibility layer + * V4L2 compatibility layer */ #include "v4l2_compat_manager.h" -#include <errno.h> +#include <assert.h> #include <fcntl.h> #include <stdarg.h> +#include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> +#include <unistd.h> #include <libcamera/base/utils.h> @@ -28,71 +30,97 @@ using namespace libcamera; va_end(ap); \ } +namespace { + +/* + * Determine if the flags require a further mode arguments that needs to be + * parsed from va_args. + */ +bool needs_mode(int flags) +{ + return (flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE); +} + +} /* namespace */ + extern "C" { LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...) { mode_t mode = 0; - if (oflag & O_CREAT || oflag & O_TMPFILE) + if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag, mode); } -/* _FORTIFY_SOURCE redirects open to __open_2 */ -LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag) -{ - return open(path, oflag); -} - #ifndef open64 LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...) { mode_t mode = 0; - if (oflag & O_CREAT || oflag & O_TMPFILE) + if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return V4L2CompatManager::instance()->openat(AT_FDCWD, path, oflag | O_LARGEFILE, mode); } - -LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag) -{ - return open(path, oflag); -} #endif LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...) { mode_t mode = 0; - if (oflag & O_CREAT || oflag & O_TMPFILE) + if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode); } -LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag) -{ - return openat(dirfd, path, oflag); -} - #ifndef openat64 LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...) { mode_t mode = 0; - if (oflag & O_CREAT || oflag & O_TMPFILE) + if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return V4L2CompatManager::instance()->openat(dirfd, path, oflag | O_LARGEFILE, mode); } +#endif -LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag) +/* + * _FORTIFY_SOURCE redirects open* to __open*_2. Disable the + * -Wmissing-declarations warnings, as the functions won't be declared if + * _FORTIFY_SOURCE is not in use. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-declarations" + +LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag) +{ + assert(!needs_mode(oflag)); + return open(path, oflag); +} + +LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag) { + assert(!needs_mode(oflag)); + return open64(path, oflag); +} + +LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag) +{ + assert(!needs_mode(oflag)); return openat(dirfd, path, oflag); } -#endif + +LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag) +{ + assert(!needs_mode(oflag)); + return openat64(dirfd, path, oflag); +} + +#pragma GCC diagnostic pop LIBCAMERA_PUBLIC int dup(int oldfd) { @@ -125,7 +153,11 @@ LIBCAMERA_PUBLIC int munmap(void *addr, size_t length) return V4L2CompatManager::instance()->munmap(addr, length); } +#if HAVE_POSIX_IOCTL +LIBCAMERA_PUBLIC int ioctl(int fd, int request, ...) +#else LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...) +#endif { void *arg; extract_va_arg(void *, arg, request); diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp index 5e8cdb4f..f53fb300 100644 --- a/src/v4l2/v4l2_compat_manager.cpp +++ b/src/v4l2/v4l2_compat_manager.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_compat_manager.cpp - V4L2 compatibility manager + * V4L2 compatibility manager */ #include "v4l2_compat_manager.h" @@ -10,7 +10,6 @@ #include <dlfcn.h> #include <fcntl.h> #include <map> -#include <stdarg.h> #include <string.h> #include <sys/eventfd.h> #include <sys/mman.h> diff --git a/src/v4l2/v4l2_compat_manager.h b/src/v4l2/v4l2_compat_manager.h index 64af9a8c..f7c6f122 100644 --- a/src/v4l2/v4l2_compat_manager.h +++ b/src/v4l2/v4l2_compat_manager.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_compat_manager.h - V4L2 compatibility manager + * V4L2 compatibility manager */ #pragma once diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 04b6271f..b08d6990 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 /googletest-release* +/libpisp /libyaml /libyuv /packagecache diff --git a/subprojects/libpisp.wrap b/subprojects/libpisp.wrap new file mode 100644 index 00000000..8b62c036 --- /dev/null +++ b/subprojects/libpisp.wrap @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: CC0-1.0 + +[wrap-git] +url = https://github.com/raspberrypi/libpisp.git +revision = v1.2.0 +depth = 1 diff --git a/test/bayer-format.cpp b/test/bayer-format.cpp index 54f03487..f8d19804 100644 --- a/test/bayer-format.cpp +++ b/test/bayer-format.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Sebastian Fricke * - * bayer_format.cpp - BayerFormat class tests + * BayerFormat class tests */ #include <iostream> diff --git a/test/byte-stream-buffer.cpp b/test/byte-stream-buffer.cpp index 04ff0571..04aab3d2 100644 --- a/test/byte-stream-buffer.cpp +++ b/test/byte-stream-buffer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * byte_stream_buffer.cpp - ByteStreamBuffer tests + * ByteStreamBuffer tests */ #include <array> diff --git a/test/camera-sensor.cpp b/test/camera-sensor.cpp index 2a17cc79..869c7889 100644 --- a/test/camera-sensor.cpp +++ b/test/camera-sensor.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera-sensor.cpp - Camera sensor tests + * Camera sensor tests */ #include <algorithm> @@ -52,8 +52,8 @@ protected: return TestFail; } - sensor_ = new CameraSensor(entity); - if (sensor_->init() < 0) { + sensor_ = CameraSensorFactoryBase::create(entity); + if (!sensor_) { cerr << "Unable to initialise camera sensor" << endl; return TestFail; } @@ -100,7 +100,7 @@ protected: MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_BGR888_1X24 }, Size(1024, 768)); - if (format.mbus_code != MEDIA_BUS_FMT_SBGGR10_1X10 || + if (format.code != MEDIA_BUS_FMT_SBGGR10_1X10 || format.size != Size(4096, 2160)) { cerr << "Failed to get a suitable format, expected 4096x2160-0x" << utils::hex(MEDIA_BUS_FMT_SBGGR10_1X10) @@ -118,13 +118,12 @@ protected: void cleanup() { - delete sensor_; } private: std::unique_ptr<DeviceEnumerator> enumerator_; std::shared_ptr<MediaDevice> media_; - CameraSensor *sensor_; + std::unique_ptr<CameraSensor> sensor_; CameraLens *lens_; }; diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp index 92884004..815d1cae 100644 --- a/test/camera/buffer_import.cpp +++ b/test/camera/buffer_import.cpp @@ -63,6 +63,8 @@ protected: request->reuse(); request->addBuffer(stream, buffer); camera_->queueRequest(request); + + dispatcher_->interrupt(); } int init() override @@ -76,6 +78,8 @@ protected: return TestFail; } + dispatcher_ = Thread::current()->eventDispatcher(); + return TestPass; } @@ -133,17 +137,20 @@ protected: } } - EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + const unsigned int nFrames = cfg.bufferCount * 2; Timer timer; - timer.start(1000ms); - while (timer.isRunning()) - dispatcher->processEvents(); + timer.start(500ms * nFrames); + while (timer.isRunning()) { + dispatcher_->processEvents(); + if (completeRequestsCount_ > nFrames) + break; + } - if (completeRequestsCount_ < cfg.bufferCount * 2) { + if (completeRequestsCount_ < nFrames) { std::cout << "Failed to capture enough frames (got " << completeRequestsCount_ << " expected at least " - << cfg.bufferCount * 2 << ")" << std::endl; + << nFrames << ")" << std::endl; return TestFail; } @@ -161,6 +168,8 @@ protected: } private: + EventDispatcher *dispatcher_; + std::vector<std::unique_ptr<Request>> requests_; unsigned int completeBuffersCount_; diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp index de824083..8766fb19 100644 --- a/test/camera/capture.cpp +++ b/test/camera/capture.cpp @@ -59,6 +59,8 @@ protected: request->reuse(); request->addBuffer(stream, buffer); camera_->queueRequest(request); + + dispatcher_->interrupt(); } int init() override @@ -73,6 +75,7 @@ protected: } allocator_ = new FrameBufferAllocator(camera_); + dispatcher_ = Thread::current()->eventDispatcher(); return TestPass; } @@ -135,19 +138,20 @@ protected: } } - EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); + unsigned int nFrames = allocator_->buffers(stream).size() * 2; Timer timer; - timer.start(1000ms); - while (timer.isRunning()) - dispatcher->processEvents(); - - unsigned int nbuffers = allocator_->buffers(stream).size(); + timer.start(500ms * nFrames); + while (timer.isRunning()) { + dispatcher_->processEvents(); + if (completeRequestsCount_ > nFrames) + break; + } - if (completeRequestsCount_ < nbuffers * 2) { + if (completeRequestsCount_ < nFrames) { cout << "Failed to capture enough frames (got " << completeRequestsCount_ << " expected at least " - << nbuffers * 2 << ")" << endl; + << nFrames * 2 << ")" << endl; return TestFail; } @@ -164,6 +168,8 @@ protected: return TestPass; } + EventDispatcher *dispatcher_; + std::vector<std::unique_ptr<Request>> requests_; std::unique_ptr<CameraConfiguration> config_; diff --git a/test/controls/control_info.cpp b/test/controls/control_info.cpp index 1176a502..e1bb43f0 100644 --- a/test/controls/control_info.cpp +++ b/test/controls/control_info.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_info.cpp - ControlInfo tests + * ControlInfo tests */ #include <iostream> diff --git a/test/controls/control_info_map.cpp b/test/controls/control_info_map.cpp index 29b33515..b0be14b5 100644 --- a/test/controls/control_info_map.cpp +++ b/test/controls/control_info_map.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_info.cpp - ControlInfoMap tests + * ControlInfoMap tests */ #include <iostream> diff --git a/test/controls/control_list.cpp b/test/controls/control_list.cpp index c03f230e..e27325c3 100644 --- a/test/controls/control_list.cpp +++ b/test/controls/control_list.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_list.cpp - ControlList tests + * ControlList tests */ #include <iostream> @@ -196,6 +196,56 @@ protected: return TestFail; } + /* + * Create two lists with overlapping controls. Merge them with + * overwriteExisting = true, verifying that the existing control + * values *get* overwritten. + */ + mergeList.clear(); + mergeList.set(controls::Brightness, 0.7f); + mergeList.set(controls::Saturation, 0.4f); + + list.clear(); + list.set(controls::Brightness, 0.5f); + list.set(controls::Contrast, 1.1f); + + mergeList.merge(list, ControlList::MergePolicy::OverwriteExisting); + if (mergeList.size() != 3) { + cout << "Merged list should contain three elements" << endl; + return TestFail; + } + + if (list.size() != 2) { + cout << "The list to merge should contain two elements" + << endl; + return TestFail; + } + + if (!mergeList.get(controls::Brightness) || + !mergeList.get(controls::Contrast) || + !mergeList.get(controls::Saturation)) { + cout << "Merged list does not contain all controls" << endl; + return TestFail; + } + + if (mergeList.get(controls::Brightness) != 0.5f) { + cout << "Brightness control value did not change after merging lists" + << endl; + return TestFail; + } + + if (mergeList.get(controls::Contrast) != 1.1f) { + cout << "Contrast control value changed after merging lists" + << endl; + return TestFail; + } + + if (mergeList.get(controls::Saturation) != 0.4f) { + cout << "Saturation control value changed after merging lists" + << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/controls/control_value.cpp b/test/controls/control_value.cpp index ad8e05d0..5084fd0c 100644 --- a/test/controls/control_value.cpp +++ b/test/controls/control_value.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_value.cpp - ControlValue tests + * ControlValue tests */ #include <algorithm> @@ -110,6 +110,86 @@ protected: } /* + * Unsigned Integer16 type. + */ + value.set(static_cast<uint16_t>(42)); + if (value.isNone() || value.isArray() || + value.type() != ControlTypeUnsigned16) { + cerr << "Control type mismatch after setting to uint16_t" << endl; + return TestFail; + } + + if (value.get<uint16_t>() != 42) { + cerr << "Control value mismatch after setting to uint16_t" << endl; + return TestFail; + } + + if (value.toString() != "42") { + cerr << "Control string mismatch after setting to uint16_t" << endl; + return TestFail; + } + + std::array<uint16_t, 4> uint16s{ 3, 14, 15, 9 }; + value.set(Span<uint16_t>(uint16s)); + if (value.isNone() || !value.isArray() || + value.type() != ControlTypeUnsigned16) { + cerr << "Control type mismatch after setting to uint16_t array" << endl; + return TestFail; + } + + Span<const uint16_t> uint16sResult = value.get<Span<const uint16_t>>(); + if (uint16s.size() != uint16sResult.size() || + !std::equal(uint16s.begin(), uint16s.end(), uint16sResult.begin())) { + cerr << "Control value mismatch after setting to uint16_t array" << endl; + return TestFail; + } + + if (value.toString() != "[ 3, 14, 15, 9 ]") { + cerr << "Control string mismatch after setting to uint16_t array" << endl; + return TestFail; + } + + /* + * Unsigned Integer32 type. + */ + value.set(static_cast<uint32_t>(42)); + if (value.isNone() || value.isArray() || + value.type() != ControlTypeUnsigned32) { + cerr << "Control type mismatch after setting to uint32_t" << endl; + return TestFail; + } + + if (value.get<uint32_t>() != 42) { + cerr << "Control value mismatch after setting to uint32_t" << endl; + return TestFail; + } + + if (value.toString() != "42") { + cerr << "Control string mismatch after setting to uint32_t" << endl; + return TestFail; + } + + std::array<uint32_t, 4> uint32s{ 3, 14, 15, 9 }; + value.set(Span<uint32_t>(uint32s)); + if (value.isNone() || !value.isArray() || + value.type() != ControlTypeUnsigned32) { + cerr << "Control type mismatch after setting to uint32_t array" << endl; + return TestFail; + } + + Span<const uint32_t> uint32sResult = value.get<Span<const uint32_t>>(); + if (uint32s.size() != uint32sResult.size() || + !std::equal(uint32s.begin(), uint32s.end(), uint32sResult.begin())) { + cerr << "Control value mismatch after setting to uint32_t array" << endl; + return TestFail; + } + + if (value.toString() != "[ 3, 14, 15, 9 ]") { + cerr << "Control string mismatch after setting to uint32_t array" << endl; + return TestFail; + } + + /* * Integer32 type. */ value.set(0x42000000); diff --git a/test/delayed_controls.cpp b/test/delayed_controls.cpp index a8ce9828..7bd30e7a 100644 --- a/test/delayed_controls.cpp +++ b/test/delayed_controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * delayed_controls.cpp - libcamera delayed controls test + * libcamera delayed controls test */ #include <iostream> diff --git a/test/event-dispatcher.cpp b/test/event-dispatcher.cpp index 9b07ab2b..f71c1c0d 100644 --- a/test/event-dispatcher.cpp +++ b/test/event-dispatcher.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event-dispatcher.cpp - Event dispatcher test + * Event dispatcher test */ #include <chrono> diff --git a/test/event-thread.cpp b/test/event-thread.cpp index ef8a52c3..5499bbf8 100644 --- a/test/event-thread.cpp +++ b/test/event-thread.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event-thread.cpp - Threaded event test + * Threaded event test */ #include <chrono> @@ -11,6 +11,7 @@ #include <unistd.h> #include <libcamera/base/event_notifier.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -84,10 +85,17 @@ private: class EventThreadTest : public Test { protected: + int init() + { + thread_.start(); + + handler_ = new EventHandler(); + + return TestPass; + } + int run() { - Thread thread; - thread.start(); /* * Fire the event notifier and then move the notifier to a @@ -97,23 +105,33 @@ protected: * different thread will correctly process already pending * events in the new thread. */ - EventHandler handler; - handler.notify(); - handler.moveToThread(&thread); + handler_->notify(); + handler_->moveToThread(&thread_); this_thread::sleep_for(chrono::milliseconds(100)); - /* Must stop thread before destroying the handler. */ - thread.exit(0); - thread.wait(); - - if (!handler.notified()) { + if (!handler_->notified()) { cout << "Thread event handling test failed" << endl; return TestFail; } return TestPass; } + + void cleanup() + { + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + handler_->deleteLater(); + thread_.exit(0); + thread_.wait(); + } + +private: + EventHandler *handler_; + Thread thread_; }; TEST_REGISTER(EventThreadTest) diff --git a/test/event.cpp b/test/event.cpp index 19dceae1..9f7b1ed4 100644 --- a/test/event.cpp +++ b/test/event.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * event.cpp - Event test + * Event test */ #include <iostream> diff --git a/test/fence.cpp b/test/fence.cpp index 1e38bc2f..8095b228 100644 --- a/test/fence.cpp +++ b/test/fence.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * fence.cpp - Fence test + * Fence test */ #include <iostream> @@ -43,7 +43,6 @@ private: void signalFence(); - std::unique_ptr<Fence> fence_; EventDispatcher *dispatcher_; UniqueFD eventFd_; UniqueFD eventFd2_; @@ -58,8 +57,11 @@ private: bool expectedCompletionResult_ = true; bool setFence_ = true; - unsigned int completedRequest_; - + /* + * Request IDs track the number of requests that have completed. They + * are one-based, and don't wrap. + */ + unsigned int completedRequestId_; unsigned int signalledRequestId_; unsigned int expiredRequestId_; unsigned int nbuffers_; @@ -127,8 +129,19 @@ int FenceTest::init() return TestFail; } - signalledRequestId_ = nbuffers_ - 2; - expiredRequestId_ = nbuffers_ - 1; + completedRequestId_ = 0; + + /* + * All but two requests are queued without a fence. Request + * expiredRequestId_ will be queued with a fence that we won't signal + * (which is then expected to expire), and request signalledRequestId_ + * will be queued with a fence that gets signalled. Select nbuffers_ + * and nbuffers_ * 2 for those two requests, to space them by a few + * frames while still not requiring a long time for the test to + * complete. + */ + expiredRequestId_ = nbuffers_; + signalledRequestId_ = nbuffers_ * 2; return TestPass; } @@ -190,16 +203,16 @@ void FenceTest::requestRequeue(Request *request) const Request::BufferMap &buffers = request->buffers(); const Stream *stream = buffers.begin()->first; FrameBuffer *buffer = buffers.begin()->second; - uint64_t cookie = request->cookie(); request->reuse(); - if (cookie == signalledRequestId_ && setFence_) { + if (completedRequestId_ == signalledRequestId_ - nbuffers_ && setFence_) { /* - * The second time this request is queued add a fence to it. - * - * The main loop signals it by using a timer to write to the - * efd2_ file descriptor before the fence expires. + * This is the request that will be used to test fence + * signalling when it completes next time. Add a fence to it, + * using efd2_. The main loop will signal the fence by using a + * timer to write to the efd2_ file descriptor before the fence + * expires. */ std::unique_ptr<Fence> fence = std::make_unique<Fence>(std::move(eventFd2_)); @@ -214,16 +227,15 @@ void FenceTest::requestRequeue(Request *request) void FenceTest::requestComplete(Request *request) { - uint64_t cookie = request->cookie(); - completedRequest_ = cookie; + completedRequestId_++; /* - * The last request is expected to fail as its fence has not been - * signaled. + * Request expiredRequestId_ is expected to fail as its fence has not + * been signalled. * * Validate the fence status but do not re-queue it. */ - if (cookie == expiredRequestId_) { + if (completedRequestId_ == expiredRequestId_) { if (validateExpiredRequest(request) != TestPass) expectedCompletionResult_ = false; @@ -231,7 +243,7 @@ void FenceTest::requestComplete(Request *request) return; } - /* Validate all requests but the last. */ + /* Validate all other requests. */ if (validateRequest(request) != TestPass) { expectedCompletionResult_ = false; @@ -272,15 +284,16 @@ int FenceTest::run() } int ret; - if (i == expiredRequestId_) { + if (i == expiredRequestId_ - 1) { /* This request will have a fence, and it will expire. */ - fence_ = std::make_unique<Fence>(std::move(eventFd_)); - if (!fence_->isValid()) { + std::unique_ptr<Fence> fence = + std::make_unique<Fence>(std::move(eventFd_)); + if (!fence->isValid()) { cerr << "Fence should be valid" << endl; return TestFail; } - ret = request->addBuffer(stream_, buffer.get(), std::move(fence_)); + ret = request->addBuffer(stream_, buffer.get(), std::move(fence)); } else { /* All other requests will have no Fence. */ ret = request->addBuffer(stream_, buffer.get()); @@ -314,15 +327,21 @@ int FenceTest::run() Timer fenceTimer; fenceTimer.timeout.connect(this, &FenceTest::signalFence); - /* Loop for one second. */ + /* + * Loop long enough for all requests to complete, allowing 500ms per + * request. + */ Timer timer; - timer.start(1000ms); - while (timer.isRunning() && expectedCompletionResult_) { - if (completedRequest_ == signalledRequestId_ && setFence_) + timer.start(500ms * (signalledRequestId_ + 1)); + while (timer.isRunning() && expectedCompletionResult_ && + completedRequestId_ <= signalledRequestId_ + 1) { + if (completedRequestId_ == signalledRequestId_ - 1 && setFence_) /* - * signalledRequestId_ has just completed and it has - * been re-queued with a fence. Start the timer to - * signal the fence in 10 msec. + * The request just before signalledRequestId_ has just + * completed. Request signalledRequestId_ has been + * queued with a fence, and libcamera is likely already + * waiting on the fence, or will soon. Start the timer + * to signal the fence in 10 msec. */ fenceTimer.start(10ms); diff --git a/test/file.cpp b/test/file.cpp index 5c978ebf..170e6ccd 100644 --- a/test/file.cpp +++ b/test/file.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * file.cpp - File I/O operations tests + * File I/O operations tests */ #include <fstream> diff --git a/test/flags.cpp b/test/flags.cpp index 2177e247..85c34788 100644 --- a/test/flags.cpp +++ b/test/flags.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * flags.cpp - Flags tests + * Flags tests */ #include <iostream> diff --git a/test/geometry.cpp b/test/geometry.cpp index 008d51ea..11df043b 100644 --- a/test/geometry.cpp +++ b/test/geometry.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * geometry.cpp - Geometry classes tests + * Geometry classes tests */ #include <iostream> @@ -481,6 +481,31 @@ protected: return TestFail; } + Point topLeft(3, 3); + Point bottomRight(30, 30); + Point topRight(30, 3); + Point bottomLeft(3, 30); + Rectangle rect1(topLeft, bottomRight); + Rectangle rect2(topRight, bottomLeft); + Rectangle rect3(bottomRight, topLeft); + Rectangle rect4(bottomLeft, topRight); + + if (rect1 != rect2 || rect1 != rect3 || rect1 != rect4) { + cout << "Point-to-point construction failed" << endl; + return TestFail; + } + + Rectangle f1 = Rectangle(100, 200, 3000, 2000); + Rectangle f2 = Rectangle(200, 300, 1500, 1000); + /* Bottom right quarter of the corresponding frames. */ + Rectangle r1 = Rectangle(100 + 1500, 200 + 1000, 1500, 1000); + Rectangle r2 = Rectangle(200 + 750, 300 + 500, 750, 500); + if (r1.transformedBetween(f1, f2) != r2 || + r2.transformedBetween(f2, f1) != r1) { + cout << "Rectangle::transformedBetween() test failed" << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp index 237af8cd..521c60b8 100644 --- a/test/gstreamer/gstreamer_device_provider_test.cpp +++ b/test/gstreamer/gstreamer_device_provider_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Umang Jain <umang.jain@ideasonboard.com> * - * gstreamer_single_stream_test.cpp - GStreamer single stream capture test + * GStreamer single stream capture test */ #include <vector> @@ -52,19 +52,12 @@ protected: for (l = devices; l != NULL; l = g_list_next(l)) { GstDevice *device = GST_DEVICE(l->data); g_autofree gchar *gst_name; - bool matched = false; g_autoptr(GstElement) element = gst_device_create_element(device, NULL); g_object_get(element, "camera-name", &gst_name, NULL); - for (auto name : cameraNames) { - if (strcmp(name.c_str(), gst_name) == 0) { - matched = true; - break; - } - } - - if (!matched) + if (std::find(cameraNames.begin(), cameraNames.end(), gst_name) == + cameraNames.end()) return TestFail; } diff --git a/test/gstreamer/gstreamer_memory_lifetime_test.cpp b/test/gstreamer/gstreamer_memory_lifetime_test.cpp new file mode 100644 index 00000000..1738cf56 --- /dev/null +++ b/test/gstreamer/gstreamer_memory_lifetime_test.cpp @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Nicolas Dufresne + * + * gstreamer_memory_lifetime_test.cpp - GStreamer memory lifetime test + */ + +#include <iostream> +#include <unistd.h> + +#include <gst/app/app.h> +#include <gst/gst.h> + +#include "gstreamer_test.h" +#include "test.h" + +using namespace std; + +class GstreamerMemoryLifetimeTest : public GstreamerTest, public Test +{ +public: + GstreamerMemoryLifetimeTest() + : GstreamerTest() + { + } + +protected: + int init() override + { + if (status_ != TestPass) + return status_; + + appsink_ = gst_element_factory_make("appsink", nullptr); + if (!appsink_) { + g_printerr("Your installation is missing 'appsink'\n"); + return TestFail; + } + g_object_ref_sink(appsink_); + + return createPipeline(); + } + + int run() override + { + /* Build the pipeline */ + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, appsink_, nullptr); + if (gst_element_link(libcameraSrc_, appsink_) != TRUE) { + g_printerr("Elements could not be linked.\n"); + return TestFail; + } + + if (startPipeline() != TestPass) + return TestFail; + + sample_ = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), GST_SECOND * 5); + if (!sample_) { + /* Failed to obtain a sample. Abort the test */ + gst_element_set_state(pipeline_, GST_STATE_NULL); + return TestFail; + } + + /* + * Keep the sample referenced and set the pipeline state to + * NULL. This causes the libcamerasrc element to synchronously + * release resources it holds. The sample will be released + * later in cleanup(). + * + * The test case verifies that libcamerasrc keeps alive long + * enough all the resources that are needed until memory + * allocated for frames gets freed. We return TestPass at this + * stage, and any use-after-free will be caught by the test + * crashing in cleanup(). + */ + gst_element_set_state(pipeline_, GST_STATE_NULL); + + return TestPass; + } + + void cleanup() override + { + g_clear_pointer(&sample_, gst_sample_unref); + g_clear_object(&appsink_); + } + +private: + GstElement *appsink_; + GstSample *sample_; +}; + +TEST_REGISTER(GstreamerMemoryLifetimeTest) diff --git a/test/gstreamer/gstreamer_multi_stream_test.cpp b/test/gstreamer/gstreamer_multi_stream_test.cpp index cd669308..263d1e86 100644 --- a/test/gstreamer/gstreamer_multi_stream_test.cpp +++ b/test/gstreamer/gstreamer_multi_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_multi_stream_test.cpp - GStreamer multi stream capture test + * GStreamer multi stream capture test */ #include <iostream> diff --git a/test/gstreamer/gstreamer_single_stream_test.cpp b/test/gstreamer/gstreamer_single_stream_test.cpp index a0dd12cf..3ef2d323 100644 --- a/test/gstreamer/gstreamer_single_stream_test.cpp +++ b/test/gstreamer/gstreamer_single_stream_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_single_stream_test.cpp - GStreamer single stream capture test + * GStreamer single stream capture test */ #include <iostream> @@ -29,30 +29,21 @@ protected: if (status_ != TestPass) return status_; - const gchar *streamDescription = "videoconvert ! fakesink"; - g_autoptr(GError) error0 = NULL; - stream0_ = gst_parse_bin_from_description_full(streamDescription, TRUE, - NULL, - GST_PARSE_FLAG_FATAL_ERRORS, - &error0); - - if (!stream0_) { - g_printerr("Bin could not be created (%s)\n", error0->message); + fakesink_ = gst_element_factory_make("fakesink", nullptr); + if (!fakesink_) { + g_printerr("Your installation is missing 'fakesink'\n"); return TestFail; } - g_object_ref_sink(stream0_); - - if (createPipeline() != TestPass) - return TestFail; + g_object_ref_sink(fakesink_); - return TestPass; + return createPipeline(); } int run() override { /* Build the pipeline */ - gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, stream0_, NULL); - if (gst_element_link(libcameraSrc_, stream0_) != TRUE) { + gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, fakesink_, nullptr); + if (!gst_element_link(libcameraSrc_, fakesink_)) { g_printerr("Elements could not be linked.\n"); return TestFail; } @@ -68,11 +59,11 @@ protected: void cleanup() override { - g_clear_object(&stream0_); + g_clear_object(&fakesink_); } private: - GstElement *stream0_; + GstElement *fakesink_; }; TEST_REGISTER(GstreamerSingleStreamTest) diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp index 6ad0c15c..a15fef0e 100644 --- a/test/gstreamer/gstreamer_test.cpp +++ b/test/gstreamer/gstreamer_test.cpp @@ -9,12 +9,17 @@ #include <libcamera/base/utils.h> +#if HAVE_ASAN +#include <sanitizer/asan_interface.h> +#endif + #include "gstreamer_test.h" #include "test.h" using namespace std; +#if HAVE_ASAN extern "C" { const char *__asan_default_options() { @@ -26,20 +31,20 @@ const char *__asan_default_options() return "detect_leaks=false"; } } +#endif GstreamerTest::GstreamerTest(unsigned int numStreams) : pipeline_(nullptr), libcameraSrc_(nullptr) { /* - * GStreamer by default spawns a process to run the - * gst-plugin-scanner helper. If libcamera is compiled with ASan - * enabled, and as GStreamer is most likely not, this causes the - * ASan link order check to fail when gst-plugin-scanner - * dlopen()s the plugin as many libraries will have already been - * loaded by then. Fix this issue by disabling spawning of a - * child helper process when scanning the build directory for - * plugins. - */ + * GStreamer by default spawns a process to run the gst-plugin-scanner + * helper. If libcamera is compiled with ASan enabled, and as GStreamer + * is most likely not, this causes the ASan link order check to fail + * when gst-plugin-scanner dlopen()s the plugin as many libraries will + * have already been loaded by then. Fix this issue by disabling + * spawning of a child helper process when scanning the build directory + * for plugins. + */ gst_registry_fork_set_enabled(false); /* Initialize GStreamer */ @@ -53,23 +58,6 @@ GstreamerTest::GstreamerTest(unsigned int numStreams) } /* - * Remove the system libcamera plugin, if any, and add the - * plugin from the build directory. - */ - GstRegistry *registry = gst_registry_get(); - g_autoptr(GstPlugin) plugin = gst_registry_lookup(registry, "libgstlibcamera.so"); - if (plugin) - gst_registry_remove_plugin(registry, plugin); - - std::string path = libcamera::utils::libcameraBuildPath() + "src/gstreamer"; - if (!gst_registry_scan_path(registry, path.c_str())) { - g_printerr("Failed to add plugin to registry\n"); - - status_ = TestFail; - return; - } - - /* * Atleast one camera should be available with numStreams streams, * otherwise skip the test entirely. */ diff --git a/test/gstreamer/gstreamer_test.h b/test/gstreamer/gstreamer_test.h index aa2261e2..abb37c1b 100644 --- a/test/gstreamer/gstreamer_test.h +++ b/test/gstreamer/gstreamer_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Vedant Paranjape * - * gstreamer_test.cpp - GStreamer test base class + * GStreamer test base class */ #pragma once diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build index a5c003b6..e066c582 100644 --- a/test/gstreamer/meson.build +++ b/test/gstreamer/meson.build @@ -8,14 +8,24 @@ gstreamer_tests = [ {'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']}, {'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']}, {'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']}, + {'name': 'memory_lifetime_test', 'sources': ['gstreamer_memory_lifetime_test.cpp']}, ] gstreamer_dep = dependency('gstreamer-1.0', required : true) +gstapp_dep = dependency('gstreamer-app-1.0', required : true) + +gstreamer_test_args = [] + +if asan_enabled + gstreamer_test_args += ['-D', 'HAVE_ASAN=1'] +endif foreach test : gstreamer_tests exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp', - dependencies : [libcamera_private, gstreamer_dep], + cpp_args : gstreamer_test_args, + dependencies : [libcamera_private, gstreamer_dep, gstapp_dep], link_with : test_libraries, include_directories : test_includes_internal) - test(test['name'], exe, suite : 'gstreamer', is_parallel : false) + test(test['name'], exe, suite : 'gstreamer', is_parallel : false, + env : gst_env, should_fail : test.get('should_fail', false)) endforeach diff --git a/test/hotplug-cameras.cpp b/test/hotplug-cameras.cpp index 5d9260a2..530e9a31 100644 --- a/test/hotplug-cameras.cpp +++ b/test/hotplug-cameras.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Umang Jain <email@uajain.com> * - * hotplug-cameras.cpp - Test cameraAdded/cameraRemoved signals in CameraManager + * Test cameraAdded/cameraRemoved signals in CameraManager */ #include <dirent.h> diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp index 051ef96e..b8178366 100644 --- a/test/ipa/ipa_interface_test.cpp +++ b/test/ipa/ipa_interface_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_interface_test.cpp - Test the IPA interface + * Test the IPA interface */ #include <fcntl.h> @@ -16,14 +16,15 @@ #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/event_notifier.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> +#include "libcamera/internal/camera_manager.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/ipa_module.h" #include "libcamera/internal/pipeline_handler.h" -#include "libcamera/internal/process.h" #include "test.h" @@ -43,20 +44,20 @@ public: { delete notifier_; ipa_.reset(); - ipaManager_.reset(); + cameraManager_.reset(); } protected: int init() override { - ipaManager_ = make_unique<IPAManager>(); + cameraManager_ = make_unique<CameraManager>(); /* Create a pipeline handler for vimc. */ const std::vector<PipelineHandlerFactoryBase *> &factories = PipelineHandlerFactoryBase::factories(); for (const PipelineHandlerFactoryBase *factory : factories) { - if (factory->name() == "PipelineHandlerVimc") { - pipe_ = factory->create(nullptr); + if (factory->name() == "vimc") { + pipe_ = factory->create(cameraManager_.get()); break; } } @@ -170,11 +171,9 @@ private: } } - ProcessManager processManager_; - std::shared_ptr<PipelineHandler> pipe_; std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_; - std::unique_ptr<IPAManager> ipaManager_; + std::unique_ptr<CameraManager> cameraManager_; enum ipa::vimc::IPAOperationCode trace_; EventNotifier *notifier_; int fd_; diff --git a/test/ipa/ipa_module_test.cpp b/test/ipa/ipa_module_test.cpp index bd5e0e4c..1c97da32 100644 --- a/test/ipa/ipa_module_test.cpp +++ b/test/ipa/ipa_module_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * ipa_module_test.cpp - Test loading of the VIMC IPA module and verify its info + * Test loading of the VIMC IPA module and verify its info */ #include <iostream> @@ -57,7 +57,7 @@ protected: const struct IPAModuleInfo testInfo = { IPA_MODULE_API_VERSION, 0, - "PipelineHandlerVimc", + "vimc", "vimc", }; diff --git a/test/ipa/libipa/fixedpoint.cpp b/test/ipa/libipa/fixedpoint.cpp new file mode 100644 index 00000000..99eb662d --- /dev/null +++ b/test/ipa/libipa/fixedpoint.cpp @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + * + * Fixed / Floating point utility tests + */ + +#include <cmath> +#include <iostream> +#include <map> +#include <stdint.h> + +#include "../src/ipa/libipa/fixedpoint.h" + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +class FixedPointUtilsTest : public Test +{ +protected: + /* R for real, I for integer */ + template<unsigned int IntPrec, unsigned int FracPrec, typename I, typename R> + int testFixedToFloat(I input, R expected) + { + R out = fixedToFloatingPoint<IntPrec, FracPrec, R>(input); + R prec = 1.0 / (1 << FracPrec); + if (std::abs(out - expected) > prec) { + cerr << "Reverse conversion expected " << input + << " to convert to " << expected + << ", got " << out << std::endl; + return TestFail; + } + + return TestPass; + } + + template<unsigned int IntPrec, unsigned int FracPrec, typename T> + int testSingleFixedPoint(double input, T expected) + { + T ret = floatingToFixedPoint<IntPrec, FracPrec, T>(input); + if (ret != expected) { + cerr << "Expected " << input << " to convert to " + << expected << ", got " << ret << std::endl; + return TestFail; + } + + /* + * The precision check is fairly arbitrary but is based on what + * the rkisp1 is capable of in the crosstalk module. + */ + double f = fixedToFloatingPoint<IntPrec, FracPrec, double>(ret); + if (std::abs(f - input) > 0.005) { + cerr << "Reverse conversion expected " << ret + << " to convert to " << input + << ", got " << f << std::endl; + return TestFail; + } + + return TestPass; + } + + int testFixedPoint() + { + /* + * The second 7.992 test is to test that unused bits don't + * affect the result. + */ + std::map<double, uint16_t> testCases = { + { 7.992, 0x3ff }, + { 0.2, 0x01a }, + { -0.2, 0x7e6 }, + { -0.8, 0x79a }, + { -0.4, 0x7cd }, + { -1.4, 0x74d }, + { -8, 0x400 }, + { 0, 0 }, + }; + + int ret; + for (const auto &testCase : testCases) { + ret = testSingleFixedPoint<4, 7, uint16_t>(testCase.first, + testCase.second); + if (ret != TestPass) + return ret; + } + + /* Special case with a superfluous one in the unused bits */ + ret = testFixedToFloat<4, 7, uint16_t, double>(0xbff, 7.992); + if (ret != TestPass) + return ret; + + return TestPass; + } + + int run() + { + /* fixed point conversion test */ + if (testFixedPoint() != TestPass) + return TestFail; + + return TestPass; + } +}; + +TEST_REGISTER(FixedPointUtilsTest) diff --git a/test/ipa/libipa/histogram.cpp b/test/ipa/libipa/histogram.cpp new file mode 100644 index 00000000..77ff31a6 --- /dev/null +++ b/test/ipa/libipa/histogram.cpp @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Histogram tests + */ + +#include "../src/ipa/libipa/histogram.h" + +#include <cmath> +#include <iostream> +#include <map> +#include <stdint.h> + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +#define ASSERT_EQ(a, b) \ + if (!((a) == (b))) { \ + std::cout << #a " != " #b << std::endl; \ + return TestFail; \ + } + +class HistogramTest : public Test +{ +protected: + int run() + { + auto hist = Histogram({ { 50, 50 } }); + + ASSERT_EQ(hist.bins(), 2); + ASSERT_EQ(hist.total(), 100); + + ASSERT_EQ(hist.cumulativeFrequency(1.0), 50); + ASSERT_EQ(hist.cumulativeFrequency(1.5), 75); + ASSERT_EQ(hist.cumulativeFrequency(2.0), 100); + + ASSERT_EQ(hist.quantile(0.0), 0.0); + ASSERT_EQ(hist.quantile(1.0), 2.0); + ASSERT_EQ(hist.quantile(0.5), 1.0); + + /* Test quantile in the middle of a bin. */ + ASSERT_EQ(hist.quantile(0.75), 1.5); + + /* Test quantile smaller than the smallest histogram step. */ + ASSERT_EQ(hist.quantile(0.001), 0.002); + + ASSERT_EQ(hist.interQuantileMean(0.0, 1.0), 1.0); + ASSERT_EQ(hist.interQuantileMean(0.0, 0.5), 0.5); + ASSERT_EQ(hist.interQuantileMean(0.5, 1.0), 1.5); + + /* Test interquantile mean that starts and ends in the middle of a bin. */ + ASSERT_EQ(hist.interQuantileMean(0.25, 0.75), 1.0); + + /* Test small ranges at the borders of the histogram. */ + ASSERT_EQ(hist.interQuantileMean(0.0, 0.1), 0.1); + ASSERT_EQ(hist.interQuantileMean(0.9, 1.0), 1.9); + + return TestPass; + } +}; + +TEST_REGISTER(HistogramTest) diff --git a/test/ipa/libipa/interpolator.cpp b/test/ipa/libipa/interpolator.cpp new file mode 100644 index 00000000..6abb7760 --- /dev/null +++ b/test/ipa/libipa/interpolator.cpp @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Interpolator tests + */ + +#include "../src/ipa/libipa/interpolator.h" + +#include <cmath> +#include <iostream> +#include <map> +#include <stdint.h> +#include <stdio.h> + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +#define ASSERT_EQ(a, b) \ + if ((a) != (b)) { \ + printf(#a " != " #b "\n"); \ + return TestFail; \ + } + +class InterpolatorTest : public Test +{ +protected: + int run() + { + Interpolator<int> interpolator; + interpolator.setData({ { 10, 100 }, { 20, 200 }, { 30, 300 } }); + + ASSERT_EQ(interpolator.getInterpolated(0), 100); + ASSERT_EQ(interpolator.getInterpolated(10), 100); + ASSERT_EQ(interpolator.getInterpolated(20), 200); + ASSERT_EQ(interpolator.getInterpolated(25), 250); + ASSERT_EQ(interpolator.getInterpolated(30), 300); + ASSERT_EQ(interpolator.getInterpolated(40), 300); + + interpolator.setQuantization(10); + unsigned int q = 0; + ASSERT_EQ(interpolator.getInterpolated(25, &q), 300); + ASSERT_EQ(q, 30); + ASSERT_EQ(interpolator.getInterpolated(24, &q), 200); + ASSERT_EQ(q, 20); + + return TestPass; + } +}; + +TEST_REGISTER(InterpolatorTest) diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build new file mode 100644 index 00000000..8c63ebd8 --- /dev/null +++ b/test/ipa/libipa/meson.build @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: CC0-1.0 + +libipa_test = [ + {'name': 'fixedpoint', 'sources': ['fixedpoint.cpp']}, + {'name': 'histogram', 'sources': ['histogram.cpp']}, + {'name': 'interpolator', 'sources': ['interpolator.cpp']}, +] + +foreach test : libipa_test + exe = executable(test['name'], test['sources'], + dependencies : [libcamera_private, libipa_dep], + implicit_include_directories : false, + link_with : [test_libraries], + include_directories : [test_includes_internal, + '../../../src/ipa/libipa/']) + + test(test['name'], exe, suite : 'ipa', + should_fail : test.get('should_fail', false)) +endforeach diff --git a/test/ipa/meson.build b/test/ipa/meson.build index 180b0da0..ceed15ba 100644 --- a/test/ipa/meson.build +++ b/test/ipa/meson.build @@ -1,15 +1,17 @@ # SPDX-License-Identifier: CC0-1.0 +subdir('libipa') + ipa_test = [ {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']}, {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']}, ] foreach test : ipa_test - exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers, - dependencies : libcamera_private, - link_with : [libipa, test_libraries], - include_directories : [libipa_includes, test_includes_internal]) + exe = executable(test['name'], test['sources'], + dependencies : [libcamera_private, libipa_dep], + link_with : [test_libraries], + include_directories : [test_includes_internal]) test(test['name'], exe, suite : 'ipa') endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp index 304e613b..f39bd986 100644 --- a/test/ipc/unixsocket.cpp +++ b/test/ipc/unixsocket.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * unixsocket.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> @@ -15,6 +15,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <vector> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/thread.h> @@ -34,6 +35,8 @@ using namespace libcamera; using namespace std; using namespace std::chrono_literals; +namespace { + int calculateLength(int fd) { lseek(fd, 0, 0); @@ -43,6 +46,8 @@ int calculateLength(int fd) return size; } +} /* namespace */ + class UnixSocketTestSlave { public: @@ -336,14 +341,14 @@ protected: for (unsigned int i = 0; i < std::size(strings); i++) { unsigned int len = strlen(strings[i]); - char buf[len]; + std::vector<char> buf(len); close(fds[i]); - if (read(response.fds[0], &buf, len) <= 0) + if (read(response.fds[0], buf.data(), len) <= 0) return TestFail; - if (memcmp(buf, strings[i], len)) + if (memcmp(buf.data(), strings[i], len)) return TestFail; } @@ -431,7 +436,7 @@ private: if (ret) return ret; - timeout.start(200ms); + timeout.start(2s); while (!callDone_) { if (!timeout.isRunning()) { cerr << "Call timeout!" << endl; diff --git a/test/ipc/unixsocket_ipc.cpp b/test/ipc/unixsocket_ipc.cpp index 3ee6017e..df7d9c2b 100644 --- a/test/ipc/unixsocket_ipc.cpp +++ b/test/ipc/unixsocket_ipc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * unixsocket_ipc.cpp - Unix socket IPC test + * Unix socket IPC test */ #include <algorithm> diff --git a/test/libtest/buffer_source.h b/test/libtest/buffer_source.h index 0cc71aa5..495da8a9 100644 --- a/test/libtest/buffer_source.h +++ b/test/libtest/buffer_source.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * buffer_source.h - libcamera camera test helper to create FrameBuffers + * libcamera camera test helper to create FrameBuffers */ #pragma once diff --git a/test/libtest/camera_test.h b/test/libtest/camera_test.h index 0b178bc2..713b503f 100644 --- a/test/libtest/camera_test.h +++ b/test/libtest/camera_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * camera_test.h - libcamera camera test base class + * libcamera camera test base class */ #pragma once diff --git a/test/libtest/test.cpp b/test/libtest/test.cpp index af37b4dd..4e03def9 100644 --- a/test/libtest/test.cpp +++ b/test/libtest/test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * test.cpp - libcamera test base class + * libcamera test base class */ #include <stdlib.h> diff --git a/test/libtest/test.h b/test/libtest/test.h index 23b07743..3a90885d 100644 --- a/test/libtest/test.h +++ b/test/libtest/test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * test.h - libcamera test base class + * libcamera test base class */ #pragma once diff --git a/test/log/log_api.cpp b/test/log/log_api.cpp index 53118960..0b999738 100644 --- a/test/log/log_api.cpp +++ b/test/log/log_api.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * log.cpp - log API test + * log API test */ #include <algorithm> diff --git a/test/log/log_process.cpp b/test/log/log_process.cpp index 966b80cf..9609e23d 100644 --- a/test/log/log_process.cpp +++ b/test/log/log_process.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * log_process.cpp - Logging in isolated child process test + * Logging in isolated child process test */ #include <fcntl.h> @@ -81,12 +81,13 @@ protected: return TestFail; } - timeout.start(200ms); + timeout.start(2s); while (timeout.isRunning()) dispatcher->processEvents(); if (exitStatus_ != Process::NormalExit) { - cerr << "process did not exit normally" << endl; + cerr << "process did not exit normally: " << exitStatus_ + << endl; return TestFail; } @@ -115,8 +116,11 @@ protected: close(fd); string str(buf); - if (str.find(message) == string::npos) + if (str.find(message) == string::npos) { + cerr << "Received message is not correct (received " + << str.length() << " bytes)" << endl; return TestFail; + } return TestPass; } @@ -136,7 +140,7 @@ private: ProcessManager processManager_; Process proc_; - Process::ExitStatus exitStatus_; + Process::ExitStatus exitStatus_ = Process::NotExited; string logPath_; int exitCode_; int num_; diff --git a/test/matrix.cpp b/test/matrix.cpp new file mode 100644 index 00000000..4afae2da --- /dev/null +++ b/test/matrix.cpp @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Matrix tests + */ + +#include "libcamera/internal/matrix.h" + +#include <cmath> +#include <iostream> + +#include "test.h" + +using namespace libcamera; + +#define ASSERT_EQ(a, b) \ + if ((a) != (b)) { \ + std::cout << #a " != " #b << " (line " << __LINE__ << ")" \ + << std::endl; \ + return TestFail; \ + } + +class MatrixTest : public Test +{ +protected: + int run() + { + Matrix<double, 3, 3> m1; + + ASSERT_EQ(m1[0][0], 0.0); + ASSERT_EQ(m1[0][1], 0.0); + + constexpr Matrix<float, 2, 2> m2 = Matrix<float, 2, 2>().identity(); + ASSERT_EQ(m2[0][0], 1.0); + ASSERT_EQ(m2[0][1], 0.0); + ASSERT_EQ(m2[1][0], 0.0); + ASSERT_EQ(m2[1][1], 1.0); + + Matrix<float, 2, 2> m3{ { 2.0, 0.0, 0.0, 2.0 } }; + Matrix<float, 2, 2> m4 = m3.inverse(); + + Matrix<float, 2, 2> m5 = m3 * m4; + ASSERT_EQ(m5[0][0], 1.0); + ASSERT_EQ(m5[0][1], 0.0); + ASSERT_EQ(m5[1][0], 0.0); + ASSERT_EQ(m5[1][1], 1.0); + + return TestPass; + } +}; + +TEST_REGISTER(MatrixTest) diff --git a/test/media_device/media_device_link_test.cpp b/test/media_device/media_device_link_test.cpp index e11f6b78..31528000 100644 --- a/test/media_device/media_device_link_test.cpp +++ b/test/media_device/media_device_link_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_link_test.cpp - Tests link handling on VIMC media device + * Tests link handling on VIMC media device */ #include <iostream> diff --git a/test/media_device/media_device_print_test.cpp b/test/media_device/media_device_print_test.cpp index cdec5b8d..63aeed48 100644 --- a/test/media_device/media_device_print_test.cpp +++ b/test/media_device/media_device_print_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018-2019, Google Inc. * - * media_device_print_test.cpp - Print out media devices + * Print out media devices */ #include <iostream> diff --git a/test/media_device/media_device_test.cpp b/test/media_device/media_device_test.cpp index 1397d123..3e41d0f0 100644 --- a/test/media_device/media_device_test.cpp +++ b/test/media_device/media_device_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_test.cpp - libcamera media device test base class + * libcamera media device test base class */ #include <iostream> diff --git a/test/media_device/media_device_test.h b/test/media_device/media_device_test.h index 9b226f1a..5223b760 100644 --- a/test/media_device/media_device_test.h +++ b/test/media_device/media_device_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * media_device_test.h - libcamera media device test base class + * libcamera media device test base class */ #pragma once diff --git a/test/meson.build b/test/meson.build index 189e1428..52f04364 100644 --- a/test/meson.build +++ b/test/meson.build @@ -60,6 +60,7 @@ internal_tests = [ {'name': 'file', 'sources': ['file.cpp']}, {'name': 'flags', 'sources': ['flags.cpp']}, {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']}, + {'name': 'matrix', 'sources': ['matrix.cpp']}, {'name': 'message', 'sources': ['message.cpp']}, {'name': 'object', 'sources': ['object.cpp']}, {'name': 'object-delete', 'sources': ['object-delete.cpp']}, @@ -69,9 +70,11 @@ internal_tests = [ {'name': 'signal-threads', 'sources': ['signal-threads.cpp']}, {'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]}, {'name': 'timer', 'sources': ['timer.cpp']}, + {'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true}, {'name': 'timer-thread', 'sources': ['timer-thread.cpp']}, {'name': 'unique-fd', 'sources': ['unique-fd.cpp']}, {'name': 'utils', 'sources': ['utils.cpp']}, + {'name': 'vector', 'sources': ['vector.cpp']}, {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']}, ] @@ -88,10 +91,11 @@ foreach test : public_tests exe = executable(test['name'], test['sources'], dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_public) - test(test['name'], exe) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach foreach test : internal_tests @@ -102,10 +106,11 @@ foreach test : internal_tests exe = executable(test['name'], test['sources'], dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_internal) - test(test['name'], exe) + test(test['name'], exe, should_fail : test.get('should_fail', false)) endforeach foreach test : internal_non_parallel_tests @@ -116,8 +121,11 @@ foreach test : internal_non_parallel_tests exe = executable(test['name'], test['sources'], dependencies : deps, + implicit_include_directories : false, link_with : test_libraries, include_directories : test_includes_internal) - test(test['name'], exe, is_parallel : false) + test(test['name'], exe, + is_parallel : false, + should_fail : test.get('should_fail', false)) endforeach diff --git a/test/message.cpp b/test/message.cpp index d148a13d..19e6646d 100644 --- a/test/message.cpp +++ b/test/message.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * message.cpp - Messages test + * Messages test */ #include <chrono> @@ -11,6 +11,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include "test.h" @@ -92,25 +93,6 @@ private: bool success_; }; -class SlowMessageReceiver : public Object -{ -protected: - void message(Message *msg) - { - if (msg->type() != Message::None) { - Object::message(msg); - return; - } - - /* - * Don't access any member of the object here (including the - * vtable) as the object will be deleted by the main thread - * while we're sleeping. - */ - this_thread::sleep_for(chrono::milliseconds(100)); - } -}; - class MessageTest : public Test { protected: @@ -127,16 +109,19 @@ protected: return TestFail; } - MessageReceiver receiver; - receiver.moveToThread(&thread_); + MessageReceiver *receiver = new MessageReceiver(); + receiver->moveToThread(&thread_); thread_.start(); - receiver.postMessage(std::make_unique<Message>(Message::None)); + receiver->postMessage(std::make_unique<Message>(Message::None)); this_thread::sleep_for(chrono::milliseconds(100)); - switch (receiver.status()) { + MessageReceiver::Status status = receiver->status(); + receiver->deleteLater(); + + switch (status) { case MessageReceiver::NoMessage: cout << "No message received" << endl; return TestFail; @@ -148,28 +133,12 @@ protected: } /* - * Test for races between message delivery and object deletion. - * Failures result in assertion errors, there is no need for - * explicit checks. - */ - SlowMessageReceiver *slowReceiver = new SlowMessageReceiver(); - slowReceiver->moveToThread(&thread_); - slowReceiver->postMessage(std::make_unique<Message>(Message::None)); - - this_thread::sleep_for(chrono::milliseconds(10)); - - delete slowReceiver; - - this_thread::sleep_for(chrono::milliseconds(100)); - - /* * Test recursive calls to Thread::dispatchMessages(). Messages * should be delivered correctly, without crashes or memory * leaks. Two messages need to be posted to ensure we don't only * test the simple case of a queue containing a single message. */ - std::unique_ptr<RecursiveMessageReceiver> recursiveReceiver = - std::make_unique<RecursiveMessageReceiver>(); + RecursiveMessageReceiver *recursiveReceiver = new RecursiveMessageReceiver(); recursiveReceiver->moveToThread(&thread_); recursiveReceiver->postMessage(std::make_unique<Message>(Message::None)); @@ -177,7 +146,10 @@ protected: this_thread::sleep_for(chrono::milliseconds(10)); - if (!recursiveReceiver->success()) { + bool success = recursiveReceiver->success(); + recursiveReceiver->deleteLater(); + + if (!success) { cout << "Recursive message delivery failed" << endl; return TestFail; } diff --git a/test/object-delete.cpp b/test/object-delete.cpp index eabefe93..676c3970 100644 --- a/test/object-delete.cpp +++ b/test/object-delete.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * object.cpp - Object deletion tests + * Object deletion tests */ #include <iostream> @@ -33,10 +33,10 @@ public: unsigned int *deleteCount_; }; -class NewThread : public Thread +class DeleterThread : public Thread { public: - NewThread(Object *obj) + DeleterThread(Object *obj) : object_(obj) { } @@ -63,9 +63,9 @@ protected: unsigned int count = 0; TestObject *obj = new TestObject(&count); - NewThread thread(obj); - thread.start(); - thread.wait(); + DeleterThread delThread(obj); + delThread.start(); + delThread.wait(); Thread::current()->dispatchMessages(Message::Type::DeferredDelete); @@ -89,6 +89,26 @@ protected: return TestFail; } + /* + * Test that deleteLater() works properly when called just + * before the object's thread exits. + */ + Thread boundThread; + boundThread.start(); + + count = 0; + obj = new TestObject(&count); + obj->moveToThread(&boundThread); + + obj->deleteLater(); + boundThread.exit(); + boundThread.wait(); + + if (count != 1) { + cout << "Object deletion right before thread exit failed (" << count << ")" << endl; + return TestFail; + } + return TestPass; } }; diff --git a/test/object-invoke.cpp b/test/object-invoke.cpp index b1c0f473..def1e61e 100644 --- a/test/object-invoke.cpp +++ b/test/object-invoke.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object-invoke.cpp - Cross-thread Object method invocation test + * Cross-thread Object method invocation test */ #include <iostream> diff --git a/test/object.cpp b/test/object.cpp index cbd0d3ec..95dc1ef3 100644 --- a/test/object.cpp +++ b/test/object.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * object.cpp - Object tests + * Object tests */ #include <iostream> diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp index cb6940c6..e9f5e7e9 100644 --- a/test/process/process_test.cpp +++ b/test/process/process_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * process_test.cpp - Process test + * Process test */ #include <iostream> diff --git a/test/public-api.cpp b/test/public-api.cpp index a1cebcf9..b1336f75 100644 --- a/test/public-api.cpp +++ b/test/public-api.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * public-api.cpp - Public API validation + * Public API validation */ #include <libcamera/libcamera.h> diff --git a/test/py/meson.build b/test/py/meson.build index 0b679d31..b922e857 100644 --- a/test/py/meson.build +++ b/test/py/meson.build @@ -13,15 +13,25 @@ if asan_runtime_missing subdir_done() endif +py_env = environment() + pymod = import('python') py3 = pymod.find_installation('python3') pypathdir = meson.project_build_root() / 'src' / 'py' -py_env = ['PYTHONPATH=' + pypathdir] +py_env.append('PYTHONPATH', pypathdir) if asan_enabled + py_env.append('LD_PRELOAD', asan_runtime) + + # Preload the C++ standard library to work around a bug in ASan when + # dynamically loading C++ .so modules. + stdlib = run_command(cxx, '-print-file-name=' + cxx_stdlib + '.so', + check : true).stdout().strip() + py_env.append('LD_PRELOAD', stdlib) + # Disable leak detection as the Python interpreter is full of leaks. - py_env += ['LD_PRELOAD=' + asan_runtime, 'ASAN_OPTIONS=detect_leaks=0'] + py_env.append('ASAN_OPTIONS', 'detect_leaks=0') endif test('pyunittests', diff --git a/test/py/unittests.py b/test/py/unittests.py index 1caea98e..8cb850d4 100755 --- a/test/py/unittests.py +++ b/test/py/unittests.py @@ -66,7 +66,7 @@ class SimpleTestMethods(BaseTestCase): libcam.log_set_level('Camera', 'FATAL') with self.assertRaises(RuntimeError): cam.acquire() - libcam.log_set_level('Camera', 'ERROR') + libcam.log_set_level('Camera', 'INFO') cam.release() diff --git a/test/serialization/control_serialization.cpp b/test/serialization/control_serialization.cpp index a507d98a..06c572b7 100644 --- a/test/serialization/control_serialization.cpp +++ b/test/serialization/control_serialization.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * control_serialization.cpp - Serialize and deserialize controls + * Serialize and deserialize controls */ #include <iostream> diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp index 4670fe46..dd696885 100644 --- a/test/serialization/generated_serializer/generated_serializer_test.cpp +++ b/test/serialization/generated_serializer/generated_serializer_test.cpp @@ -2,10 +2,11 @@ /* * Copyright (C) 2020, Google Inc. * - * generated_serializer_test.cpp - Test generated serializer + * Test generated serializer */ #include <algorithm> +#include <iostream> #include <tuple> #include <vector> diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build index 6f8794c1..ae08e9be 100644 --- a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build +++ b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build @@ -9,7 +9,8 @@ mojom = custom_target('test_mojom_module', '--output-root', meson.project_build_root(), '--input-root', meson.project_source_root(), '--mojoms', '@INPUT@' - ]) + ], + env : py_build_env) # test_ipa_interface.h generated_test_header = custom_target('test_ipa_interface_h', @@ -23,7 +24,8 @@ generated_test_header = custom_target('test_ipa_interface_h', '--libcamera_generate_header', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) # test_ipa_serializer.h generated_test_serializer = custom_target('test_ipa_serializer_h', @@ -37,4 +39,5 @@ generated_test_serializer = custom_target('test_ipa_serializer_h', '--libcamera_generate_serializer', '--libcamera_output_path=@OUTPUT@', './' +'@INPUT@' - ]) + ], + env : py_build_env) diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp index 377ecdb0..afea93a6 100644 --- a/test/serialization/ipa_data_serializer_test.cpp +++ b/test/serialization/ipa_data_serializer_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer + * Test serializing/deserializing with IPADataSerializer */ #include <algorithm> @@ -29,7 +29,7 @@ using namespace std; using namespace libcamera; static const ControlInfoMap Controls = ControlInfoMap({ - { &controls::AeEnable, ControlInfo(false, true) }, + { &controls::DebugMetadataEnable, ControlInfo(false, true) }, { &controls::ExposureTime, ControlInfo(0, 999999) }, { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) }, { &controls::ColourGains, ControlInfo(0.0f, 32.0f) }, diff --git a/test/serialization/serialization_test.cpp b/test/serialization/serialization_test.cpp index 11d0f0f3..af9969fd 100644 --- a/test/serialization/serialization_test.cpp +++ b/test/serialization/serialization_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * serialization_test.cpp - Base class for serialization tests + * Base class for serialization tests */ #include "serialization_test.h" diff --git a/test/serialization/serialization_test.h b/test/serialization/serialization_test.h index 609f9fdf..760e3721 100644 --- a/test/serialization/serialization_test.h +++ b/test/serialization/serialization_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * serialization_test.h - Base class for serialization tests + * Base class for serialization tests */ #pragma once diff --git a/test/shared-fd.cpp b/test/shared-fd.cpp index 997d7be1..57199dfe 100644 --- a/test/shared-fd.cpp +++ b/test/shared-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * shared_fd.cpp - SharedFD test + * SharedFD test */ #include <fcntl.h> diff --git a/test/signal-threads.cpp b/test/signal-threads.cpp index d5e2eb66..c4789c83 100644 --- a/test/signal-threads.cpp +++ b/test/signal-threads.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal-threads.cpp - Cross-thread signal delivery test + * Cross-thread signal delivery test */ #include <chrono> @@ -10,6 +10,7 @@ #include <thread> #include <libcamera/base/message.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/utils.h> @@ -58,15 +59,20 @@ private: class SignalThreadsTest : public Test { protected: - int run() + int init() { - SignalReceiver receiver; - signal_.connect(&receiver, &SignalReceiver::slot); + receiver_ = new SignalReceiver(); + signal_.connect(receiver_, &SignalReceiver::slot); + + return TestPass; + } + int run() + { /* Test that a signal is received in the main thread. */ signal_.emit(0); - switch (receiver.status()) { + switch (receiver_->status()) { case SignalReceiver::NoSignal: cout << "No signal received for direct connection" << endl; return TestFail; @@ -82,8 +88,8 @@ protected: * Move the object to a thread and verify that the signal is * correctly delivered, with the correct data. */ - receiver.reset(); - receiver.moveToThread(&thread_); + receiver_->reset(); + receiver_->moveToThread(&thread_); thread_.start(); @@ -91,7 +97,7 @@ protected: this_thread::sleep_for(chrono::milliseconds(100)); - switch (receiver.status()) { + switch (receiver_->status()) { case SignalReceiver::NoSignal: cout << "No signal received for message connection" << endl; return TestFail; @@ -103,7 +109,7 @@ protected: break; } - if (receiver.value() != 42) { + if (receiver_->value() != 42) { cout << "Signal received with incorrect value" << endl; return TestFail; } @@ -113,11 +119,13 @@ protected: void cleanup() { + receiver_->deleteLater(); thread_.exit(0); thread_.wait(); } private: + SignalReceiver *receiver_; Thread thread_; Signal<int> signal_; diff --git a/test/signal.cpp b/test/signal.cpp index 5c6b304d..3f596b22 100644 --- a/test/signal.cpp +++ b/test/signal.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * signal.cpp - Signal test + * Signal test */ #include <iostream> diff --git a/test/span.cpp b/test/span.cpp index abf3a5d6..4b9f3279 100644 --- a/test/span.cpp +++ b/test/span.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2020, Google Inc. * - * span.cpp - Span tests + * Span tests */ /* @@ -143,9 +143,9 @@ protected: Span<const int>{ v }; /* Span<float>{ v }; */ - Span<const int>{ v }; - /* Span<int>{ v }; */ - /* Span<const float>{ v }; */ + Span<const int>{ cv }; + /* Span<int>{ cv }; */ + /* Span<const float>{ cv }; */ Span<int> dynamicSpan{ i }; Span<int>{ dynamicSpan }; diff --git a/test/stream/stream_colorspace.cpp b/test/stream/stream_colorspace.cpp index 1b7afe65..4c904c4c 100644 --- a/test/stream/stream_colorspace.cpp +++ b/test/stream/stream_colorspace.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Ideas on Board Oy. * - * stream_colorspace.cpp - Stream colorspace adjustment test + * Stream colorspace adjustment test */ #include <iostream> diff --git a/test/stream/stream_formats.cpp b/test/stream/stream_formats.cpp index 99fa0385..553b59aa 100644 --- a/test/stream/stream_formats.cpp +++ b/test/stream/stream_formats.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * stream_formats.cpp - StreamFormats test + * StreamFormats test */ #include <iostream> diff --git a/test/threads.cpp b/test/threads.cpp index 8f366c9d..c00d95a4 100644 --- a/test/threads.cpp +++ b/test/threads.cpp @@ -2,16 +2,18 @@ /* * Copyright (C) 2019, Google Inc. * - * threads.cpp - Threads test + * Threads test */ #include <chrono> #include <iostream> #include <memory> #include <pthread.h> +#include <sched.h> #include <thread> #include <time.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include "test.h" @@ -50,14 +52,8 @@ protected: { cancelled_ = true; - /* - * Cancel the thread and call a guaranteed cancellation point - * (nanosleep). - */ pthread_cancel(pthread_self()); - - struct timespec req{ 0, 100*000*000 }; - nanosleep(&req, nullptr); + pthread_testcancel(); cancelled_ = false; } @@ -66,6 +62,27 @@ private: bool &cancelled_; }; +class CpuSetTester : public Object +{ +public: + CpuSetTester(unsigned int cpuset) + : cpuset_(cpuset) {} + + bool testCpuSet() + { + int ret = sched_getcpu(); + if (static_cast<int>(cpuset_) != ret) { + cout << "Invalid cpuset: " << ret << ", expecting: " << cpuset_ << endl; + return false; + } + + return true; + } + +private: + const unsigned int cpuset_; +}; + class ThreadTest : public Test { protected: @@ -165,6 +182,23 @@ protected: return TestFail; } + const unsigned int numCpus = std::thread::hardware_concurrency(); + for (unsigned int i = 0; i < numCpus; ++i) { + thread = std::make_unique<Thread>(); + const std::array<const unsigned int, 1> cpus{ i }; + thread->setThreadAffinity(cpus); + thread->start(); + + CpuSetTester tester(i); + tester.moveToThread(thread.get()); + + if (!tester.invokeMethod(&CpuSetTester::testCpuSet, ConnectionTypeBlocking)) + return TestFail; + + thread->exit(0); + thread->wait(); + } + return TestPass; } diff --git a/test/timer-fail.cpp b/test/timer-fail.cpp new file mode 100644 index 00000000..0ced6441 --- /dev/null +++ b/test/timer-fail.cpp @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Threaded timer failure test + */ + +#include <chrono> +#include <iostream> + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/object.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/timer.h> + +#include "test.h" + +using namespace libcamera; +using namespace std; +using namespace std::chrono_literals; + +class TimeoutHandler : public Object +{ +public: + TimeoutHandler() + : timer_(this), timeout_(false) + { + timer_.timeout.connect(this, &TimeoutHandler::timeoutHandler); + } + + void start() + { + timer_.start(100ms); + } + + bool timeout() const + { + return timeout_; + } + +private: + void timeoutHandler() + { + timeout_ = true; + } + + Timer timer_; + bool timeout_; +}; + +class TimerFailTest : public Test +{ +protected: + int init() + { + thread_.start(); + + timeout_ = new TimeoutHandler(); + timeout_->moveToThread(&thread_); + + return TestPass; + } + + int run() + { + /* + * Test that the forbidden operation of starting the timer from + * another thread results in a failure. We need to interrupt the + * event dispatcher to make sure we don't succeed simply because + * the event dispatcher hasn't noticed the timer restart. + */ + timeout_->start(); + thread_.eventDispatcher()->interrupt(); + + this_thread::sleep_for(chrono::milliseconds(200)); + + /* + * The wrong start() call should result in an assertion in debug + * builds, and a timeout in release builds. The test is + * therefore marked in meson.build as expected to fail. We need + * to return TestPass in the unexpected (usually known as + * "fail") case, and TestFail otherwise. + */ + if (timeout_->timeout()) { + cout << "Timer start from wrong thread succeeded unexpectedly" + << endl; + return TestPass; + } + + return TestFail; + } + + void cleanup() + { + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + timeout_->deleteLater(); + thread_.exit(0); + thread_.wait(); + } + +private: + TimeoutHandler *timeout_; + Thread thread_; +}; + +TEST_REGISTER(TimerFailTest) diff --git a/test/timer-thread.cpp b/test/timer-thread.cpp index 61821753..55e5cfdf 100644 --- a/test/timer-thread.cpp +++ b/test/timer-thread.cpp @@ -2,13 +2,14 @@ /* * Copyright (C) 2019, Google Inc. * - * timer-thread.cpp - Threaded timer test + * Threaded timer test */ #include <chrono> #include <iostream> #include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> @@ -28,12 +29,6 @@ public: timer_.start(100ms); } - void restart() - { - timeout_ = false; - timer_.start(100ms); - } - bool timeout() const { return timeout_; @@ -55,7 +50,9 @@ protected: int init() { thread_.start(); - timeout_.moveToThread(&thread_); + + timeout_ = new TimeoutHandler(); + timeout_->moveToThread(&thread_); return TestPass; } @@ -68,39 +65,27 @@ protected: */ this_thread::sleep_for(chrono::milliseconds(200)); - if (!timeout_.timeout()) { + if (!timeout_->timeout()) { cout << "Timer expiration test failed" << endl; return TestFail; } - /* - * Test that starting the timer from another thread fails. We - * need to interrupt the event dispatcher to make sure we don't - * succeed simply because the event dispatcher hasn't noticed - * the timer restart. - */ - timeout_.restart(); - thread_.eventDispatcher()->interrupt(); - - this_thread::sleep_for(chrono::milliseconds(200)); - - if (timeout_.timeout()) { - cout << "Timer restart test failed" << endl; - return TestFail; - } - return TestPass; } void cleanup() { - /* Must stop thread before destroying timeout. */ + /* + * Object class instances must be destroyed from the thread + * they live in. + */ + timeout_->deleteLater(); thread_.exit(0); thread_.wait(); } private: - TimeoutHandler timeout_; + TimeoutHandler *timeout_; Thread thread_; }; diff --git a/test/timer.cpp b/test/timer.cpp index 0f01c3cb..2eacc059 100644 --- a/test/timer.cpp +++ b/test/timer.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * timer.cpp - Timer test + * Timer test */ #include <chrono> diff --git a/test/transform.cpp b/test/transform.cpp index fbc0308c..4ec7a1eb 100644 --- a/test/transform.cpp +++ b/test/transform.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2023, Ideas On Board Oy * - * transform.cpp - Transform and Orientation tests + * Transform and Orientation tests */ #include <iostream> diff --git a/test/unique-fd.cpp b/test/unique-fd.cpp index eb3b591f..e556439e 100644 --- a/test/unique-fd.cpp +++ b/test/unique-fd.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Google Inc. * - * unique-fd.cpp - UniqueFD test + * UniqueFD test */ #include <fcntl.h> diff --git a/test/utils.cpp b/test/utils.cpp index fc56e14e..d25475cb 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * utils.cpp - Miscellaneous utility tests + * Miscellaneous utility tests */ #include <iostream> @@ -176,6 +176,14 @@ protected: std::ostringstream os; std::string ref; + os << utils::hex(static_cast<int8_t>(0x42)) << " "; + ref += "0x42 "; + os << utils::hex(static_cast<uint8_t>(0x42)) << " "; + ref += "0x42 "; + os << utils::hex(static_cast<int16_t>(0x42)) << " "; + ref += "0x0042 "; + os << utils::hex(static_cast<uint16_t>(0x42)) << " "; + ref += "0x0042 "; os << utils::hex(static_cast<int32_t>(0x42)) << " "; ref += "0x00000042 "; os << utils::hex(static_cast<uint32_t>(0x42)) << " "; @@ -184,6 +192,15 @@ protected: ref += "0x0000000000000042 "; os << utils::hex(static_cast<uint64_t>(0x42)) << " "; ref += "0x0000000000000042 "; + + os << utils::hex(static_cast<int8_t>(0x42), 6) << " "; + ref += "0x000042 "; + os << utils::hex(static_cast<uint8_t>(0x42), 1) << " "; + ref += "0x42 "; + os << utils::hex(static_cast<int16_t>(0x42), 6) << " "; + ref += "0x000042 "; + os << utils::hex(static_cast<uint16_t>(0x42), 1) << " "; + ref += "0x42 "; os << utils::hex(static_cast<int32_t>(0x42), 4) << " "; ref += "0x0042 "; os << utils::hex(static_cast<uint32_t>(0x42), 1) << " "; diff --git a/test/v4l2_compat/v4l2_compat_test.py b/test/v4l2_compat/v4l2_compat_test.py index bd89d496..443babc2 100755 --- a/test/v4l2_compat/v4l2_compat_test.py +++ b/test/v4l2_compat/v4l2_compat_test.py @@ -4,7 +4,7 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# v4l2_compat_test.py - Test the V4L2 compatibility layer +# Test the V4L2 compatibility layer import argparse import glob diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.cpp b/test/v4l2_subdevice/v4l2_subdevice_test.cpp index d8fbfd9f..c349c9e3 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.cpp +++ b/test/v4l2_subdevice/v4l2_subdevice_test.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.cpp - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #include <iostream> diff --git a/test/v4l2_subdevice/v4l2_subdevice_test.h b/test/v4l2_subdevice/v4l2_subdevice_test.h index e73c583b..89b78302 100644 --- a/test/v4l2_subdevice/v4l2_subdevice_test.h +++ b/test/v4l2_subdevice/v4l2_subdevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * v4l2_subdevice_test.h - VIMC-based V4L2 subdevice test + * VIMC-based V4L2 subdevice test */ #pragma once diff --git a/test/v4l2_videodevice/capture_async.cpp b/test/v4l2_videodevice/capture_async.cpp index 42e1e671..67366461 100644 --- a/test/v4l2_videodevice/capture_async.cpp +++ b/test/v4l2_videodevice/capture_async.cpp @@ -61,10 +61,12 @@ protected: if (ret) return TestFail; - timeout.start(10000ms); + const unsigned int nFrames = 30; + + timeout.start(500ms * nFrames); while (timeout.isRunning()) { dispatcher->processEvents(); - if (frames > 30) + if (frames > nFrames) break; } @@ -73,8 +75,9 @@ protected: return TestFail; } - if (frames < 30) { - std::cout << "Failed to capture 30 frames within timeout." << std::endl; + if (frames < nFrames) { + std::cout << "Failed to capture " << nFrames + << " frames within timeout." << std::endl; return TestFail; } diff --git a/test/v4l2_videodevice/controls.cpp b/test/v4l2_videodevice/controls.cpp index 0f603b85..b0130295 100644 --- a/test/v4l2_videodevice/controls.cpp +++ b/test/v4l2_videodevice/controls.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2019, Google Inc. * - * controls.cpp - V4L2 device controls handling test + * V4L2 device controls handling test */ #include <algorithm> diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp index 125aafd6..9fbd24cc 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.cpp +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -64,8 +64,8 @@ int V4L2VideoDeviceTest::init() format.size.height = 480; if (driver_ == "vimc") { - sensor_ = new CameraSensor(media_->getEntityByName("Sensor A")); - if (sensor_->init()) + sensor_ = CameraSensorFactoryBase::create(media_->getEntityByName("Sensor A")); + if (!sensor_) return TestFail; debayer_ = new V4L2Subdevice(media_->getEntityByName("Debayer A")); @@ -75,7 +75,7 @@ int V4L2VideoDeviceTest::init() format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8); V4L2SubdeviceFormat subformat = {}; - subformat.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8; + subformat.code = MEDIA_BUS_FMT_SBGGR8_1X8; subformat.size = format.size; if (sensor_->setFormat(&subformat)) @@ -98,6 +98,5 @@ void V4L2VideoDeviceTest::cleanup() capture_->close(); delete debayer_; - delete sensor_; delete capture_; } diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.h b/test/v4l2_videodevice/v4l2_videodevice_test.h index d2de1a6d..7c9003ec 100644 --- a/test/v4l2_videodevice/v4l2_videodevice_test.h +++ b/test/v4l2_videodevice/v4l2_videodevice_test.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2018, Google Inc. * - * vl42device_test.h - libcamera v4l2device test base class + * libcamera v4l2device test base class */ #pragma once @@ -36,7 +36,7 @@ protected: std::string entity_; std::unique_ptr<libcamera::DeviceEnumerator> enumerator_; std::shared_ptr<libcamera::MediaDevice> media_; - libcamera::CameraSensor *sensor_; + std::unique_ptr<libcamera::CameraSensor> sensor_; libcamera::V4L2Subdevice *debayer_; libcamera::V4L2VideoDevice *capture_; std::vector<std::unique_ptr<libcamera::FrameBuffer>> buffers_; diff --git a/test/vector.cpp b/test/vector.cpp new file mode 100644 index 00000000..4fae960d --- /dev/null +++ b/test/vector.cpp @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Vector tests + */ + +#include "libcamera/internal/vector.h" + +#include <cmath> +#include <iostream> + +#include "test.h" + +using namespace libcamera; + +#define ASSERT_EQ(a, b) \ +if ((a) != (b)) { \ + std::cout << #a " != " #b << " (line " << __LINE__ << ")" \ + << std::endl; \ + return TestFail; \ +} + +class VectorTest : public Test +{ +protected: + int run() + { + Vector<double, 3> v1{ 0.0 }; + + ASSERT_EQ(v1[0], 0.0); + ASSERT_EQ(v1[1], 0.0); + ASSERT_EQ(v1[2], 0.0); + + ASSERT_EQ(v1.length(), 0.0); + ASSERT_EQ(v1.length2(), 0.0); + + Vector<double, 3> v2{{ 1.0, 4.0, 8.0 }}; + + ASSERT_EQ(v2[0], 1.0); + ASSERT_EQ(v2[1], 4.0); + ASSERT_EQ(v2[2], 8.0); + + ASSERT_EQ(v2.x(), 1.0); + ASSERT_EQ(v2.y(), 4.0); + ASSERT_EQ(v2.z(), 8.0); + + ASSERT_EQ(v2.r(), 1.0); + ASSERT_EQ(v2.g(), 4.0); + ASSERT_EQ(v2.b(), 8.0); + + ASSERT_EQ(v2.length2(), 81.0); + ASSERT_EQ(v2.length(), 9.0); + ASSERT_EQ(v2.sum(), 13.0); + + Vector<double, 3> v3{ v2 }; + + ASSERT_EQ(v2, v3); + + v3 = Vector<double, 3>{{ 4.0, 4.0, 4.0 }}; + + ASSERT_EQ(v2 + v3, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }})); + ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }})); + ASSERT_EQ(v2 - v3, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }})); + ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }})); + ASSERT_EQ(v2 * v3, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }})); + ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }})); + ASSERT_EQ(v2 / v3, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }})); + ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }})); + + ASSERT_EQ(v2.min(v3), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }})); + ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }})); + ASSERT_EQ(v2.max(v3), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }})); + ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }})); + + ASSERT_EQ(v2.dot(v3), 52.0); + + v2 += v3; + ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }})); + v2 -= v3; + ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }})); + v2 *= v3; + ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }})); + v2 /= v3; + ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }})); + + v2 += 4.0; + ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }})); + v2 -= 4.0; + ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }})); + v2 *= 4.0; + ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }})); + v2 /= 4.0; + ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }})); + + return TestPass; + } +}; + +TEST_REGISTER(VectorTest) diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp index 2d92463a..1b22c87b 100644 --- a/test/yaml-parser.cpp +++ b/test/yaml-parser.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2022, Google Inc. * - * yaml-parser.cpp - YAML parser operations tests + * YAML parser operations tests */ #include <array> @@ -34,10 +34,12 @@ static const string testYaml = "list:\n" " - James\n" " - Mary\n" + " - \n" "dictionary:\n" " a: 1\n" " c: 3\n" " b: 2\n" + " empty:\n" "level1:\n" " level2:\n" " - [1, 2]\n" @@ -430,9 +432,10 @@ protected: if (testObjectType(listObj, "list", Type::List) != TestPass) return TestFail; - static constexpr std::array<const char *, 2> listValues{ + static constexpr std::array<const char *, 3> listValues{ "James", "Mary", + "", }; if (listObj.size() != listValues.size()) { @@ -465,16 +468,23 @@ protected: i++; } + /* Ensure that empty objects get parsed as empty strings. */ + if (!listObj[2].isValue()) { + cerr << "Empty object is not a value" << std::endl; + return TestFail; + } + /* Test dictionary object */ auto &dictObj = (*root)["dictionary"]; if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass) return TestFail; - static constexpr std::array<std::pair<const char *, int>, 3> dictValues{ { + static constexpr std::array<std::pair<const char *, int>, 4> dictValues{ { { "a", 1 }, { "c", 3 }, { "b", 2 }, + { "empty", -100 }, } }; size_t dictSize = dictValues.size(); @@ -505,7 +515,7 @@ protected: return TestFail; } - if (elem.get<int32_t>(0) != item.second) { + if (elem.get<int32_t>(-100) != item.second) { std::cerr << "Dictionary element " << i << " has wrong value" << std::endl; return TestFail; @@ -514,6 +524,42 @@ protected: i++; } + /* Ensure that empty objects get parsed as empty strings. */ + if (!dictObj["empty"].isValue()) { + cerr << "Empty object is not of type value" << std::endl; + return TestFail; + } + + /* Ensure that keys without values are added to a dict. */ + if (!dictObj.contains("empty")) { + cerr << "Empty element is missing in dict" << std::endl; + return TestFail; + } + + /* Test access to nonexistent member. */ + if (dictObj["nonexistent"].get<std::string>("default") != "default") { + cerr << "Accessing nonexistent dict entry fails to return default" << std::endl; + return TestFail; + } + + /* Test nonexistent object has value type empty. */ + if (!dictObj["nonexistent"].isEmpty()) { + cerr << "Accessing nonexistent object returns non-empty object" << std::endl; + return TestFail; + } + + /* Test explicit cast to bool on an empty object returns true. */ + if (!!dictObj["empty"] != true) { + cerr << "Casting empty entry to bool returns false" << std::endl; + return TestFail; + } + + /* Test explicit cast to bool on nonexistent object returns false. */ + if (!!dictObj["nonexistent"] != false) { + cerr << "Casting nonexistent dict entry to bool returns true" << std::endl; + return TestFail; + } + /* Make sure utils::map_keys() works on the adapter. */ (void)utils::map_keys(dictObj.asDict()); diff --git a/utils/abi-compat.sh b/utils/abi-compat.sh index c936ac05..31f61e32 100755 --- a/utils/abi-compat.sh +++ b/utils/abi-compat.sh @@ -156,15 +156,16 @@ create_abi_dump() { # Generate a minimal libcamera build. "lib" and "prefix" are # defined explicitly to avoid system default ambiguities. meson setup "$build" "$worktree" \ - -Dlibdir=lib \ - -Dprefix=/usr/local/ \ - -Ddocumentation=disabled \ -Dcam=disabled \ - -Dqcam=disabled \ + -Ddocumentation=disabled \ -Dgstreamer=disabled \ -Dlc-compliance=disabled \ - -Dtracing=disabled \ - -Dpipelines= + -Dlibdir=lib \ + -Dpipelines= \ + -Dprefix=/usr/local/ \ + -Dpycamera=disabled \ + -Dqcam=disabled \ + -Dtracing=disabled ninja -C "$build" DESTDIR="$install" ninja -C "$build" install diff --git a/utils/checkstyle.py b/utils/checkstyle.py index 836ea80f..f6229bbd 100755 --- a/utils/checkstyle.py +++ b/utils/checkstyle.py @@ -1,10 +1,10 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2018, Google Inc. # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# checkstyle.py - A patch style checker script based on clang-format +# A patch style checker script based on clang-format # # TODO: # @@ -23,7 +23,6 @@ import subprocess import sys dependencies = { - 'clang-format': True, 'git': True, } @@ -168,6 +167,12 @@ def parse_diff(diff): hunk = DiffHunk(line) elif hunk is not None: + # Work around https://github.com/python/cpython/issues/46395 + # See https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html + if line[-1] != '\n': + hunk.append(line + '\n') + line = '\\ No newline at end of file\n' + hunk.append(line) if hunk: @@ -205,36 +210,66 @@ class CommitFile: class Commit: def __init__(self, commit): - self.commit = commit + self._commit = commit + self._author = None self._trailers = [] self._parse() - def _parse_trailers(self, lines): - for index in range(1, len(lines)): - line = lines[index] - if not line: - break + def _parse_commit(self): + # Get and parse the commit message. + ret = subprocess.run(['git', 'show', '--format=%H%n%an <%ae>%n%s%n%b', + '--no-patch', self.commit], + stdout=subprocess.PIPE).stdout.decode('utf-8') + lines = ret.splitlines() + + self._commit = lines[0] + self._author = lines[1] + self._title = lines[2] + self._body = lines[3:] - self._trailers.append(line) + # Parse the trailers. Feed git-interpret-trailers with a full commit + # message that includes both the title and the body, as it otherwise + # fails to find trailers when the body contains trailers only. + message = self._title + '\n\n' + '\n'.join(self._body) + trailers = subprocess.run(['git', 'interpret-trailers', '--parse'], + input=message.encode('utf-8'), + stdout=subprocess.PIPE).stdout.decode('utf-8') - return index + self._trailers = trailers.splitlines() def _parse(self): - # Get the commit title and list of files. - ret = subprocess.run(['git', 'show', '--format=%s%n%(trailers:only,unfold)', '--name-status', + self._parse_commit() + + # Get the list of files. Use an empty format specifier to suppress the + # commit message completely. + ret = subprocess.run(['git', 'show', '--format=', '--name-status', self.commit], stdout=subprocess.PIPE).stdout.decode('utf-8') - lines = ret.splitlines() - - self._title = lines[0] + self._files = [CommitFile(f) for f in ret.splitlines()] - index = self._parse_trailers(lines) - self._files = [CommitFile(f) for f in lines[index:] if f] + def __repr__(self): + return '\n'.join([ + f'commit {self.commit}', + f'Author: {self.author}', + f'', + f' {self.title}', + '', + '\n'.join([line and f' {line}' or '' for line in self._body]), + 'Trailers:', + ] + self.trailers) def files(self, filter='AMR'): return [f.filename for f in self._files if f.status in filter] @property + def author(self): + return self._author + + @property + def commit(self): + return self._commit + + @property def title(self): return self._title @@ -272,20 +307,14 @@ class StagedChanges(Commit): class Amendment(Commit): def __init__(self): - Commit.__init__(self, '') + Commit.__init__(self, 'HEAD') def _parse(self): - # Create a title using HEAD commit and parse the trailers. - ret = subprocess.run(['git', 'show', '--format=%H %s%n%(trailers:only,unfold)', - '--no-patch'], - stdout=subprocess.PIPE).stdout.decode('utf-8') - lines = ret.splitlines() + self._parse_commit() - self._title = 'Amendment of ' + lines[0].strip() + self._title = f'Amendment of "{self.title}"' - self._parse_trailers(lines) - - # Extract the list of modified files + # Extract the list of modified files. ret = subprocess.run(['git', 'diff', '--staged', '--name-status', 'HEAD~'], stdout=subprocess.PIPE).stdout.decode('utf-8') self._files = [CommitFile(f) for f in ret.splitlines()] @@ -304,40 +333,83 @@ class Amendment(Commit): class ClassRegistry(type): def __new__(cls, clsname, bases, attrs): newclass = super().__new__(cls, clsname, bases, attrs) - if bases: - bases[0].subclasses.append(newclass) - bases[0].subclasses.sort(key=lambda x: getattr(x, 'priority', 0), - reverse=True) + if bases and bases[0] != CheckerBase: + base = bases[0] + + if not hasattr(base, 'subclasses'): + base.subclasses = [] + base.subclasses.append(newclass) + base.subclasses.sort(key=lambda x: getattr(x, 'priority', 0), + reverse=True) return newclass -# ------------------------------------------------------------------------------ -# Commit Checkers -# +class CheckerBase(metaclass=ClassRegistry): + @classmethod + def instances(cls, obj, names): + for instance in cls.subclasses: + if names and instance.__name__ not in names: + continue + if instance.supports(obj): + yield instance + + @classmethod + def supports(cls, obj): + if hasattr(cls, 'commit_types'): + return type(obj) in cls.commit_types -class CommitChecker(metaclass=ClassRegistry): - subclasses = [] + if hasattr(cls, 'patterns'): + for pattern in cls.patterns: + if fnmatch.fnmatch(os.path.basename(obj), pattern): + return True - def __init__(self): - pass + return False - # - # Class methods - # @classmethod - def checkers(cls, names): - for checker in cls.subclasses: - if names and checker.__name__ not in names: - continue - yield checker + def all_patterns(cls): + patterns = set() + for instance in cls.subclasses: + if hasattr(instance, 'patterns'): + patterns.update(instance.patterns) + + return patterns + + @classmethod + def check_dependencies(cls): + if not hasattr(cls, 'dependencies'): + return [] + + issues = [] + + for command in cls.dependencies: + if command not in dependencies: + dependencies[command] = shutil.which(command) + + if not dependencies[command]: + issues.append(CommitIssue(f'Missing {command} to run {cls.__name__}')) + + return issues + + +# ------------------------------------------------------------------------------ +# Commit Checkers +# + +class CommitChecker(CheckerBase): + pass class CommitIssue(object): def __init__(self, msg): self.msg = msg + def __str__(self): + return f'{Colours.fg(Colours.Yellow)}{self.msg}{Colours.reset()}' + class HeaderAddChecker(CommitChecker): + commit_types = (Commit, StagedChanges, Amendment) + @classmethod def check(cls, commit, top_level): issues = [] @@ -382,6 +454,8 @@ class HeaderAddChecker(CommitChecker): class TitleChecker(CommitChecker): + commit_types = (Commit,) + prefix_regex = re.compile(r'^([a-zA-Z0-9_.-]+: )+') release_regex = re.compile(r'libcamera v[0-9]+\.[0-9]+\.[0-9]+') @@ -389,11 +463,6 @@ class TitleChecker(CommitChecker): def check(cls, commit, top_level): title = commit.title - # Skip the check when validating staged changes (as done through a - # pre-commit hook) as there is no title to check in that case. - if isinstance(commit, StagedChanges): - return [] - # Ignore release commits, they don't need a prefix. if TitleChecker.release_regex.fullmatch(title): return [] @@ -449,6 +518,8 @@ class TitleChecker(CommitChecker): class TrailersChecker(CommitChecker): + commit_types = (Commit,) + commit_regex = re.compile(r'[0-9a-f]{12}[0-9a-f]* \(".*"\)') coverity_regex = re.compile(r'Coverity CID=.*') @@ -471,6 +542,7 @@ class TrailersChecker(CommitChecker): known_trailers = { 'Acked-by': email_regex, 'Bug': link_regex, + 'Co-developed-by': email_regex, 'Fixes': commit_regex, 'Link': link_regex, 'Reported-by': validate_reported_by, @@ -486,6 +558,8 @@ class TrailersChecker(CommitChecker): def check(cls, commit, top_level): issues = [] + sob_found = False + for trailer in commit.trailers: match = TrailersChecker.trailer_regex.fullmatch(trailer) if not match: @@ -508,6 +582,13 @@ class TrailersChecker(CommitChecker): issues.append(CommitIssue(f"Malformed value '{value}' for commit trailer '{key}'")) continue + if key == 'Signed-off-by': + if value == commit.author: + sob_found = True + + if not sob_found: + issues.append(CommitIssue(f"No 'Signed-off-by' trailer matching author '{commit.author}', see Documentation/contributing.rst")) + return issues @@ -515,64 +596,76 @@ class TrailersChecker(CommitChecker): # Style Checkers # -class StyleChecker(metaclass=ClassRegistry): - subclasses = [] +class StyleChecker(CheckerBase): + pass - def __init__(self): - pass - # - # Class methods - # - @classmethod - def checkers(cls, filename, names): - for checker in cls.subclasses: - if names and checker.__name__ not in names: - continue - if checker.supports(filename): - yield checker +class StyleIssue(object): + def __init__(self, line_number, position, line, msg): + self.line_number = line_number + self.position = position + self.line = line + self.msg = msg - @classmethod - def supports(cls, filename): - for pattern in cls.patterns: - if fnmatch.fnmatch(os.path.basename(filename), pattern): - return True - return False + def __str__(self): + s = [] + s.append(f'{Colours.fg(Colours.Yellow)}#{self.line_number}: {self.msg}{Colours.reset()}') + if self.line is not None: + s.append(f'{Colours.fg(Colours.Yellow)}+{self.line.rstrip()}{Colours.reset()}') + + if self.position is not None: + # Align the position marker by using the original line with + # all characters except for tabs replaced with spaces. This + # ensures proper alignment regardless of how the code is + # indented. + start = self.position[0] + prefix = ''.join([c if c == '\t' else ' ' for c in self.line[:start]]) + length = self.position[1] - start - 1 + s.append(f' {prefix}^{"~" * length}') + + return '\n'.join(s) + + +class HexValueChecker(StyleChecker): + patterns = ('*.c', '*.cpp', '*.h') + + regex = re.compile(r'\b0[xX][0-9a-fA-F]+\b') @classmethod - def all_patterns(cls): - patterns = set() - for checker in cls.subclasses: - patterns.update(checker.patterns) + def check(cls, content, line_numbers): + issues = [] - return patterns + for line_number in line_numbers: + line = content[line_number - 1] + match = HexValueChecker.regex.search(line) + if not match: + continue + value = match.group(0) + if value == value.lower(): + continue -class StyleIssue(object): - def __init__(self, line_number, line, msg): - self.line_number = line_number - self.line = line - self.msg = msg + issues.append(StyleIssue(line_number, match.span(0), line, + f'Use lowercase hex constant {value.lower()}')) + + return issues class IncludeChecker(StyleChecker): patterns = ('*.cpp', '*.h') - headers = ('assert', 'ctype', 'errno', 'fenv', 'float', 'inttypes', - 'limits', 'locale', 'setjmp', 'signal', 'stdarg', 'stddef', - 'stdint', 'stdio', 'stdlib', 'string', 'time', 'uchar', 'wchar', - 'wctype') - include_regex = re.compile('^#include <c([a-z]*)>') - - def __init__(self, content): - super().__init__() - self.__content = content + headers = ('cassert', 'cctype', 'cerrno', 'cfenv', 'cfloat', 'cinttypes', + 'climits', 'clocale', 'csetjmp', 'csignal', 'cstdarg', 'cstddef', + 'cstdint', 'cstdio', 'cstdlib', 'cstring', 'ctime', 'cuchar', + 'cwchar', 'cwctype', 'math.h') + include_regex = re.compile(r'^#include <([a-z.]*)>') - def check(self, line_numbers): + @classmethod + def check(self, content, line_numbers): issues = [] for line_number in line_numbers: - line = self.__content[line_number - 1] + line = content[line_number - 1] match = IncludeChecker.include_regex.match(line) if not match: continue @@ -581,28 +674,34 @@ class IncludeChecker(StyleChecker): if header not in IncludeChecker.headers: continue - issues.append(StyleIssue(line_number, line, - 'C compatibility header <%s.h> is preferred' % header)) + if header.endswith('.h'): + header_type = 'C++' + header = 'c' + header[:-2] + else: + header_type = 'C compatibility' + header = header[1:] + '.h' + + issues.append(StyleIssue(line_number, match.span(1), line, + f'{header_type} header <{header}> is preferred')) return issues class LogCategoryChecker(StyleChecker): - log_regex = re.compile('\\bLOG\((Debug|Info|Warning|Error|Fatal)\)') + log_regex = re.compile(r'\bLOG\((Debug|Info|Warning|Error|Fatal)\)') patterns = ('*.cpp',) - def __init__(self, content): - super().__init__() - self.__content = content - - def check(self, line_numbers): + @classmethod + def check(cls, content, line_numbers): issues = [] for line_number in line_numbers: - line = self.__content[line_number-1] - if not LogCategoryChecker.log_regex.search(line): + line = content[line_number - 1] + match = LogCategoryChecker.log_regex.search(line) + if not match: continue - issues.append(StyleIssue(line_number, line, 'LOG() should use categories')) + issues.append(StyleIssue(line_number, match.span(1), line, + 'LOG() should use categories')) return issues @@ -610,70 +709,30 @@ class LogCategoryChecker(StyleChecker): class MesonChecker(StyleChecker): patterns = ('meson.build',) - def __init__(self, content): - super().__init__() - self.__content = content - - def check(self, line_numbers): + @classmethod + def check(cls, content, line_numbers): issues = [] for line_number in line_numbers: - line = self.__content[line_number-1] - if line.find('\t') != -1: - issues.append(StyleIssue(line_number, line, 'meson.build should use spaces for indentation')) - return issues - - -class Pep8Checker(StyleChecker): - patterns = ('*.py',) - results_regex = re.compile('stdin:([0-9]+):([0-9]+)(.*)') - - def __init__(self, content): - super().__init__() - self.__content = content - - def check(self, line_numbers): - issues = [] - data = ''.join(self.__content).encode('utf-8') - - try: - ret = subprocess.run(['pycodestyle', '--ignore=E501', '-'], - input=data, stdout=subprocess.PIPE) - except FileNotFoundError: - issues.append(StyleIssue(0, None, 'Please install pycodestyle to validate python additions')) - return issues - - results = ret.stdout.decode('utf-8').splitlines() - for item in results: - search = re.search(Pep8Checker.results_regex, item) - line_number = int(search.group(1)) - position = int(search.group(2)) - msg = search.group(3) - - if line_number in line_numbers: - line = self.__content[line_number - 1] - issues.append(StyleIssue(line_number, line, msg)) - + line = content[line_number - 1] + pos = line.find('\t') + if pos != -1: + issues.append(StyleIssue(line_number, [pos, pos], line, + 'meson.build should use spaces for indentation')) return issues class ShellChecker(StyleChecker): + dependencies = ('shellcheck',) patterns = ('*.sh',) - results_line_regex = re.compile('In - line ([0-9]+):') - - def __init__(self, content): - super().__init__() - self.__content = content + results_line_regex = re.compile(r'In - line ([0-9]+):') - def check(self, line_numbers): + @classmethod + def check(cls, content, line_numbers): issues = [] - data = ''.join(self.__content).encode('utf-8') + data = ''.join(content).encode('utf-8') - try: - ret = subprocess.run(['shellcheck', '-Cnever', '-'], - input=data, stdout=subprocess.PIPE) - except FileNotFoundError: - issues.append(StyleIssue(0, None, 'Please install shellcheck to validate shell script additions')) - return issues + ret = subprocess.run(['shellcheck', '-Cnever', '-'], + input=data, stdout=subprocess.PIPE) results = ret.stdout.decode('utf-8').splitlines() for nr, item in enumerate(results): @@ -685,11 +744,8 @@ class ShellChecker(StyleChecker): line = results[nr + 1] msg = results[nr + 2] - # Determined, but not yet used - position = msg.find('^') + 1 - if line_number in line_numbers: - issues.append(StyleIssue(line_number, line, msg)) + issues.append(StyleIssue(line_number, None, line, msg)) return issues @@ -698,40 +754,12 @@ class ShellChecker(StyleChecker): # Formatters # -class Formatter(metaclass=ClassRegistry): - subclasses = [] - - def __init__(self): - pass - - # - # Class methods - # - @classmethod - def formatters(cls, filename, names): - for formatter in cls.subclasses: - if names and formatter.__name__ not in names: - continue - if formatter.supports(filename): - yield formatter - - @classmethod - def supports(cls, filename): - for pattern in cls.patterns: - if fnmatch.fnmatch(os.path.basename(filename), pattern): - return True - return False - - @classmethod - def all_patterns(cls): - patterns = set() - for formatter in cls.subclasses: - patterns.update(formatter.patterns) - - return patterns +class Formatter(CheckerBase): + pass class CLangFormatter(Formatter): + dependencies = ('clang-format',) patterns = ('*.c', '*.cpp', '*.h') priority = -1 @@ -746,7 +774,8 @@ class CLangFormatter(Formatter): class DoxygenFormatter(Formatter): patterns = ('*.c', '*.cpp') - return_regex = re.compile(' +\\* +\\\\return +[a-z]') + oneliner_regex = re.compile(r'^ +\* +\\(brief|param|return)\b.*\.$') + return_regex = re.compile(r' +\* +\\return +[a-z]') @classmethod def format(cls, filename, data): @@ -761,6 +790,7 @@ class DoxygenFormatter(Formatter): lines.append(line) continue + line = cls.oneliner_regex.sub(lambda m: m.group(0)[:-1], line) line = cls.return_regex.sub(lambda m: m.group(0)[:-1] + m.group(0)[-1].upper(), line) if line.find('*/') != -1: @@ -806,7 +836,7 @@ class DPointerFormatter(Formatter): class IncludeOrderFormatter(Formatter): patterns = ('*.cpp', '*.h') - include_regex = re.compile('^#include (["<])([^">]*)([">])') + include_regex = re.compile(r'^#include (["<])([^">]*)([">])') @classmethod def format(cls, filename, data): @@ -858,6 +888,17 @@ class IncludeOrderFormatter(Formatter): return '\n'.join(lines) +class Pep8Formatter(Formatter): + dependencies = ('autopep8',) + patterns = ('*.py',) + + @classmethod + def format(cls, filename, data): + ret = subprocess.run(['autopep8', '--ignore=E501', '-'], + input=data.encode('utf-8'), stdout=subprocess.PIPE) + return ret.stdout.decode('utf-8') + + class StripTrailingSpaceFormatter(Formatter): patterns = ('*.c', '*.cpp', '*.h', '*.py', 'meson.build') @@ -873,6 +914,24 @@ class StripTrailingSpaceFormatter(Formatter): # Style checking # +def check_commit(top_level, commit, checkers): + issues = [] + + # Apply the commit checkers first. + for checker in CommitChecker.instances(commit, checkers): + issues_ = checker.check_dependencies() + if issues_: + issues += issues_ + continue + + issues += checker.check(commit, top_level) + + for issue in issues: + print(issue) + + return len(issues) + + def check_file(top_level, commit, filename, checkers): # Extract the line numbers touched by the commit. commit_diff = commit.get_diff(top_level, filename) @@ -888,9 +947,15 @@ def check_file(top_level, commit, filename, checkers): # Format the file after the commit with all formatters and compute the diff # between the unformatted and formatted contents. after = commit.get_file(filename) + issues = [] formatted = after - for formatter in Formatter.formatters(filename, checkers): + for formatter in Formatter.instances(filename, checkers): + issues_ = formatter.check_dependencies() + if issues_: + issues += issues_ + continue + formatted = formatter.format(filename, formatted) after = after.splitlines(True) @@ -903,11 +968,14 @@ def check_file(top_level, commit, filename, checkers): formatted_diff = [hunk for hunk in formatted_diff if hunk.intersects(lines)] # Check for code issues not related to formatting. - issues = [] - for checker in StyleChecker.checkers(filename, checkers): - checker = checker(after) + for checker in StyleChecker.instances(filename, checkers): + issues_ = checker.check_dependencies() + if issues_: + issues += issues_ + continue + for hunk in commit_diff: - issues += checker.check(hunk.side('to').touched) + issues += checker.check(after, hunk.side('to').touched) # Print the detected issues. if len(issues) == 0 and len(formatted_diff) == 0: @@ -921,13 +989,9 @@ def check_file(top_level, commit, filename, checkers): print(hunk) if len(issues): - issues = sorted(issues, key=lambda i: i.line_number) + issues = sorted(issues, key=lambda i: getattr(i, 'line_number', -1)) for issue in issues: - print('%s#%u: %s%s' % (Colours.fg(Colours.Yellow), issue.line_number, - issue.msg, Colours.reset())) - if issue.line is not None: - print('%s+%s%s' % (Colours.fg(Colours.Yellow), issue.line.rstrip(), - Colours.reset())) + print(issue) return len(formatted_diff) + len(issues) @@ -939,13 +1003,8 @@ def check_style(top_level, commit, checkers): print(title) print(separator) - issues = 0 - # Apply the commit checkers first. - for checker in CommitChecker.checkers(checkers): - for issue in checker.check(commit, top_level): - print('%s%s%s' % (Colours.fg(Colours.Yellow), issue.msg, Colours.reset())) - issues += 1 + issues = check_commit(top_level, commit, checkers) # Filter out files we have no checker for. patterns = set() @@ -1017,7 +1076,7 @@ def main(argv): if args.checkers: args.checkers = args.checkers.split(',') - # Check for required dependencies. + # Check for required common dependencies. for command, mandatory in dependencies.items(): found = shutil.which(command) if mandatory and not found: diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py new file mode 100644 index 00000000..e5161048 --- /dev/null +++ b/utils/codegen/controls.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2019, Google Inc. +# +# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# +# Helper classes to handle source code generation for libcamera controls + + +class ControlEnum(object): + def __init__(self, data): + self.__data = data + + @property + def description(self): + """The enum description""" + return self.__data.get('description') + + @property + def name(self): + """The enum name""" + return self.__data.get('name') + + @property + def value(self): + """The enum value""" + return self.__data.get('value') + + +class Control(object): + def __init__(self, name, data, vendor, mode): + self.__name = name + self.__data = data + self.__enum_values = None + self.__size = None + self.__vendor = vendor + + enum_values = data.get('enum') + if enum_values is not None: + self.__enum_values = [ControlEnum(enum) for enum in enum_values] + + size = self.__data.get('size') + if size is not None: + if len(size) == 0: + raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension') + + # Compute the total number of elements in the array. If any of the + # array dimension is a string, the array is variable-sized. + num_elems = 1 + for dim in size: + if type(dim) is str: + num_elems = 0 + break + + dim = int(dim) + if dim <= 0: + raise RuntimeError(f'Control `{self.__name}` size must have positive values only') + + num_elems *= dim + + self.__size = num_elems + + if mode == 'properties': + self.__direction = 'out' + else: + direction = self.__data.get('direction') + if direction is None: + raise RuntimeError(f'Control `{self.__name}` missing required field `direction`') + if direction not in ['in', 'out', 'inout']: + raise RuntimeError(f'Control `{self.__name}` direction `{direction}` is invalid; must be one of `in`, `out`, or `inout`') + self.__direction = direction + + @property + def description(self): + """The control description""" + return self.__data.get('description') + + @property + def enum_values(self): + """The enum values, if the control is an enumeration""" + if self.__enum_values is None: + return + for enum in self.__enum_values: + yield enum + + @property + def enum_values_count(self): + """The number of enum values, if the control is an enumeration""" + if self.__enum_values is None: + return 0 + return len(self.__enum_values) + + @property + def is_enum(self): + """Is the control an enumeration""" + return self.__enum_values is not None + + @property + def vendor(self): + """The vendor string, or None""" + return self.__vendor + + @property + def name(self): + """The control name (CamelCase)""" + return self.__name + + @property + def type(self): + typ = self.__data.get('type') + size = self.__data.get('size') + + if typ == 'string': + return 'std::string' + + if self.__size is None: + return typ + + if self.__size: + return f"Span<const {typ}, {self.__size}>" + else: + return f"Span<const {typ}>" + + @property + def direction(self): + in_flag = 'ControlId::Direction::In' + out_flag = 'ControlId::Direction::Out' + + if self.__direction == 'inout': + return f'{in_flag} | {out_flag}' + if self.__direction == 'in': + return in_flag + if self.__direction == 'out': + return out_flag + + @property + def element_type(self): + return self.__data.get('type') + + @property + def size(self): + return self.__size diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py new file mode 100755 index 00000000..59b716c1 --- /dev/null +++ b/utils/codegen/gen-controls.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2019, Google Inc. +# +# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# +# Generate control definitions from YAML + +import argparse +import jinja2 +import os +import sys +import yaml + +from controls import Control + + +def snake_case(s): + return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_') + + +def format_description(description): + description = description.strip('\n').split('\n') + for i in range(1, len(description)): + line = description[i] + description[i] = (line and ' * ' or ' *') + line + return '\n'.join(description) + + +def extend_control(ctrl, id, ranges): + ctrl.id = ranges[ctrl.vendor] + id + 1 + + if ctrl.vendor != 'libcamera': + ctrl.namespace = f'{ctrl.vendor}::' + else: + ctrl.namespace = '' + + return ctrl + + +def main(argv): + + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'], + help='Mode of operation') + parser.add_argument('--output', '-o', metavar='file', type=str, + help='Output file name. Defaults to standard output if not specified.') + parser.add_argument('--ranges', '-r', type=str, required=True, + help='Control id range reservation file.') + parser.add_argument('--template', '-t', dest='template', type=str, required=True, + help='Template file name.') + parser.add_argument('input', type=str, nargs='+', + help='Input file name.') + + args = parser.parse_args(argv[1:]) + + ranges = {} + with open(args.ranges, 'rb') as f: + data = open(args.ranges, 'rb').read() + ranges = yaml.safe_load(data)['ranges'] + + controls = {} + for input in args.input: + data = yaml.safe_load(open(input, 'rb').read()) + + vendor = data['vendor'] + if vendor not in ranges.keys(): + raise RuntimeError(f'Control id range is not defined for vendor {vendor}') + + ctrls = controls.setdefault(vendor, []) + + for i, ctrl in enumerate(data['controls']): + ctrl = Control(*ctrl.popitem(), vendor, args.mode) + ctrls.append(extend_control(ctrl, i, ranges)) + + # Sort the vendors by range numerical value + controls = [[vendor, ctrls] for vendor, ctrls in controls.items()] + controls.sort(key=lambda item: ranges[item[0]]) + + filename = { + 'controls': 'control_ids', + 'properties': 'property_ids', + }[args.mode] + + data = { + 'filename': filename, + 'mode': args.mode, + 'controls': controls, + } + + env = jinja2.Environment() + env.filters['format_description'] = format_description + env.filters['snake_case'] = snake_case + template = env.from_string(open(args.template, 'r', encoding='utf-8').read()) + string = template.render(data) + + if args.output: + output = open(args.output, 'w', encoding='utf-8') + output.write(string) + output.close() + else: + sys.stdout.write(string) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/gen-formats.py b/utils/codegen/gen-formats.py index da79a8bb..0c0932a5 100755 --- a/utils/gen-formats.py +++ b/utils/codegen/gen-formats.py @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# gen-formats.py - Generate formats definitions from YAML +# Generate formats definitions from YAML import argparse import re diff --git a/utils/codegen/gen-gst-controls.py b/utils/codegen/gen-gst-controls.py new file mode 100755 index 00000000..4ca76049 --- /dev/null +++ b/utils/codegen/gen-gst-controls.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2019, Google Inc. +# Copyright (C) 2024, Jaslo Ziska +# +# Authors: +# Laurent Pinchart <laurent.pinchart@ideasonboard.com> +# Jaslo Ziska <jaslo@ziska.de> +# +# Generate gstreamer control properties from YAML + +import argparse +import jinja2 +import re +import sys +import yaml + +from controls import Control + + +exposed_controls = [ + 'AeEnable', 'AeMeteringMode', 'AeConstraintMode', 'AeExposureMode', + 'ExposureValue', 'ExposureTime', 'ExposureTimeMode', + 'AnalogueGain', 'AnalogueGainMode', 'AeFlickerPeriod', + 'Brightness', 'Contrast', 'AwbEnable', 'AwbMode', 'ColourGains', + 'Saturation', 'Sharpness', 'ColourCorrectionMatrix', 'ScalerCrop', + 'DigitalGain', 'AfMode', 'AfRange', 'AfSpeed', 'AfMetering', 'AfWindows', + 'LensPosition', 'Gamma', +] + + +def find_common_prefix(strings): + prefix = strings[0] + + for string in strings[1:]: + while string[:len(prefix)] != prefix and prefix: + prefix = prefix[:len(prefix) - 1] + if not prefix: + break + + return prefix + + +def format_description(description): + # Substitute doxygen keywords \sa (see also) and \todo + description = re.sub(r'\\sa((?: \w+)+)', + lambda match: 'See also: ' + ', '.join( + map(kebab_case, match.group(1).strip().split(' ')) + ) + '.', description) + description = re.sub(r'\\todo', 'Todo:', description) + + description = description.strip().split('\n') + return '\n'.join([ + '"' + line.replace('\\', r'\\').replace('"', r'\"') + ' "' for line in description if line + ]).rstrip() + + +# Custom filter to allow indenting by a string prior to Jinja version 3.0 +# +# This function can be removed and the calls to indent_str() replaced by the +# built-in indent() filter when dropping Jinja versions older than 3.0 +def indent_str(s, indention): + s += '\n' + + lines = s.splitlines() + rv = lines.pop(0) + + if lines: + rv += '\n' + '\n'.join( + indention + line if line else line for line in lines + ) + + return rv + + +def snake_case(s): + return ''.join([ + c.isupper() and ('_' + c.lower()) or c for c in s + ]).strip('_') + + +def kebab_case(s): + return snake_case(s).replace('_', '-') + + +def extend_control(ctrl): + if ctrl.vendor != 'libcamera': + ctrl.namespace = f'{ctrl.vendor}::' + ctrl.vendor_prefix = f'{ctrl.vendor}-' + else: + ctrl.namespace = '' + ctrl.vendor_prefix = '' + + ctrl.is_array = ctrl.size is not None + + if ctrl.is_enum: + # Remove common prefix from enum variant names + prefix = find_common_prefix([enum.name for enum in ctrl.enum_values]) + for enum in ctrl.enum_values: + enum.gst_name = kebab_case(enum.name.removeprefix(prefix)) + + ctrl.gtype = 'enum' + ctrl.default = '0' + elif ctrl.element_type == 'bool': + ctrl.gtype = 'boolean' + ctrl.default = 'false' + elif ctrl.element_type == 'float': + ctrl.gtype = 'float' + ctrl.default = '0' + ctrl.min = '-G_MAXFLOAT' + ctrl.max = 'G_MAXFLOAT' + elif ctrl.element_type == 'int32_t': + ctrl.gtype = 'int' + ctrl.default = '0' + ctrl.min = 'G_MININT' + ctrl.max = 'G_MAXINT' + elif ctrl.element_type == 'int64_t': + ctrl.gtype = 'int64' + ctrl.default = '0' + ctrl.min = 'G_MININT64' + ctrl.max = 'G_MAXINT64' + elif ctrl.element_type == 'uint8_t': + ctrl.gtype = 'uchar' + ctrl.default = '0' + ctrl.min = '0' + ctrl.max = 'G_MAXUINT8' + elif ctrl.element_type == 'Rectangle': + ctrl.is_rectangle = True + ctrl.default = '0' + ctrl.min = '0' + ctrl.max = 'G_MAXINT' + else: + raise RuntimeError(f'The type `{ctrl.element_type}` is unknown') + + return ctrl + + +def main(argv): + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument('--output', '-o', metavar='file', type=str, + help='Output file name. Defaults to standard output if not specified.') + parser.add_argument('--template', '-t', dest='template', type=str, required=True, + help='Template file name.') + parser.add_argument('input', type=str, nargs='+', + help='Input file name.') + + args = parser.parse_args(argv[1:]) + + controls = {} + for input in args.input: + data = yaml.safe_load(open(input, 'rb').read()) + + vendor = data['vendor'] + ctrls = controls.setdefault(vendor, []) + + for ctrl in data['controls']: + ctrl = Control(*ctrl.popitem(), vendor, mode='controls') + + if ctrl.name in exposed_controls: + ctrls.append(extend_control(ctrl)) + + data = {'controls': list(controls.items())} + + env = jinja2.Environment() + env.filters['format_description'] = format_description + env.filters['indent_str'] = indent_str + env.filters['snake_case'] = snake_case + env.filters['kebab_case'] = kebab_case + template = env.from_string(open(args.template, 'r', encoding='utf-8').read()) + string = template.render(data) + + if args.output: + with open(args.output, 'w', encoding='utf-8') as output: + output.write(string) + else: + sys.stdout.write(string) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/gen-header.sh b/utils/codegen/gen-header.sh index 8b66c5dd..c78f0859 100755 --- a/utils/gen-header.sh +++ b/utils/codegen/gen-header.sh @@ -1,7 +1,7 @@ #!/bin/sh -src_dir="$1" -dst_file="$2" +dst_file="$1" +shift cat <<EOF > "$dst_file" /* SPDX-License-Identifier: LGPL-2.1-or-later */ @@ -9,16 +9,15 @@ cat <<EOF > "$dst_file" /* * Copyright (C) 2018-2019, Google Inc. * - * libcamera.h - libcamera public API + * libcamera public API */ #pragma once EOF -headers=$(for header in "$src_dir"/*.h "$src_dir"/*.h.in ; do +headers=$(for header in "$@" ; do header=$(basename "$header") - header="${header%.in}" echo "$header" done | sort) diff --git a/utils/gen-ipa-pub-key.py b/utils/codegen/gen-ipa-pub-key.py index a4a1f7b7..dc3e7d5f 100755 --- a/utils/gen-ipa-pub-key.py +++ b/utils/codegen/gen-ipa-pub-key.py @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# ipa-gen-key.py - Generate the IPA module signing public key +# Generate the IPA module signing public key import string import subprocess diff --git a/utils/tracepoints/gen-tp-header.py b/utils/codegen/gen-tp-header.py index a454615e..6769c7ce 100755 --- a/utils/tracepoints/gen-tp-header.py +++ b/utils/codegen/gen-tp-header.py @@ -4,9 +4,8 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# gen-tp-header.py - Generate header file to contain lttng tracepoints +# Generate header file to contain lttng tracepoints -import datetime import jinja2 import pathlib import os @@ -20,7 +19,6 @@ def main(argv): output = argv[2] template = argv[3] - year = datetime.datetime.now().year path = pathlib.Path(output).absolute().relative_to(argv[1]) source = '' @@ -28,7 +26,7 @@ def main(argv): source += open(fname, 'r', encoding='utf-8').read() + '\n\n' template = jinja2.Template(open(template, 'r', encoding='utf-8').read()) - string = template.render(year=year, path=path, source=source) + string = template.render(path=path, source=source) f = open(output, 'w', encoding='utf-8').write(string) diff --git a/utils/ipc/extract-docs.py b/utils/codegen/ipc/extract-docs.py index 8f7fff9f..61f44cae 100755 --- a/utils/ipc/extract-docs.py +++ b/utils/codegen/ipc/extract-docs.py @@ -4,15 +4,15 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# extract-docs.py - Extract doxygen documentation from mojom files +# Extract doxygen documentation from mojom files import argparse import re import sys -regex_block_start = re.compile('^\/\*\*$') -regex_block_end = re.compile('^ \*\/$') -regex_spdx = re.compile('^\/\* SPDX-License-Identifier: .* \*\/$') +regex_block_start = re.compile(r'^/\*\*$') +regex_block_end = re.compile(r'^ \*/$') +regex_spdx = re.compile(r'^/\* SPDX-License-Identifier: .* \*/$') def main(argv): @@ -38,7 +38,7 @@ def main(argv): /* * Copyright (C) 2021, Google Inc. * - * {pipeline}_ipa_interface.cpp - Docs file for generated {pipeline}.mojom + * Docs file for generated {pipeline}.mojom * * This file is auto-generated. Do not edit. */ diff --git a/utils/ipc/generate.py b/utils/codegen/ipc/generate.py index 8771e0a6..dfbe659b 100755 --- a/utils/ipc/generate.py +++ b/utils/codegen/ipc/generate.py @@ -4,18 +4,25 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# generate.py - Run mojo code generator for generating libcamera IPC files +# Run mojo code generator for generating libcamera IPC files import os import sys -# TODO set sys.pycache_prefix for >= python3.8 -sys.dont_write_bytecode = True +sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/bindings') import mojo.public.tools.bindings.mojom_bindings_generator as generator def _GetModulePath(path, output_dir): - return os.path.join(output_dir, path.relative_path()) + return os.path.join(output_dir, path.relative_path()) + + +# Disable the attribute checker to support our custom attributes. Ideally we +# should add the attributes to the list of allowed attributes in +# utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py, but +# we're trying hard to use the upstream mojom as-is. +if hasattr(generator, '_BUILTIN_CHECKS'): + del generator._BUILTIN_CHECKS['attributes'] # Override the mojo code generator's generator list to only contain our # libcamera generator diff --git a/utils/ipc/generators/__init__.py b/utils/codegen/ipc/generators/__init__.py index e69de29b..e69de29b 100644 --- a/utils/ipc/generators/__init__.py +++ b/utils/codegen/ipc/generators/__init__.py diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl index c60b99b8..3942e570 100644 --- a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl @@ -7,7 +7,7 @@ /* * Copyright (C) 2020, Google Inc. * - * core_ipa_interface.h - libcamera core definitions for Image Processing Algorithms + * libcamera core definitions for Image Processing Algorithms * * This file is auto-generated. Do not edit. */ @@ -15,8 +15,13 @@ #pragma once {% if has_map %}#include <map>{% endif %} +{% if has_string %}#include <string>{% endif %} {% if has_array %}#include <vector>{% endif %} +#include <libcamera/controls.h> +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + #include <libcamera/ipa/ipa_interface.h> namespace libcamera { diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl index 5738a1aa..ac84963d 100644 --- a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl @@ -8,7 +8,7 @@ /* * Copyright (C) 2020, Google Inc. * - * core_ipa_serializer.h - Data serializer for core libcamera definitions for IPA + * Data serializer for core libcamera definitions for IPA * * This file is auto-generated. Do not edit. */ @@ -31,13 +31,8 @@ template<> class IPADataSerializer<{{struct|name}}> { public: -{{- serializer.serializer(struct, "")}} -{%- if struct|has_fd %} -{{serializer.deserializer_fd(struct, "")}} -{%- else %} -{{serializer.deserializer_no_fd(struct, "")}} -{{serializer.deserializer_fd_simple(struct, "")}} -{%- endif %} +{{- serializer.serializer(struct)}} +{{- serializer.deserializer(struct)}} }; {% endfor %} diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/codegen/ipc/generators/libcamera_templates/definition_functions.tmpl index 8b8509f3..8b8509f3 100644 --- a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/definition_functions.tmpl diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/codegen/ipc/generators/libcamera_templates/meson.build index 70664eab..70664eab 100644 --- a/utils/ipc/generators/libcamera_templates/meson.build +++ b/utils/codegen/ipc/generators/libcamera_templates/meson.build diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl index 160601f7..5d70ea6a 100644 --- a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl @@ -7,19 +7,27 @@ /* * Copyright (C) 2020, Google Inc. * - * {{module_name}}_ipa_interface.h - Image Processing Algorithm interface for {{module_name}} + * Image Processing Algorithm interface for {{module_name}} * * This file is auto-generated. Do not edit. */ #pragma once -#include <libcamera/ipa/core_ipa_interface.h> -#include <libcamera/ipa/ipa_interface.h> - {% if has_map %}#include <map>{% endif %} +{% if has_string %}#include <string>{% endif %} {% if has_array %}#include <vector>{% endif %} +#include <libcamera/base/flags.h> +#include <libcamera/base/signal.h> + +#include <libcamera/controls.h> +#include <libcamera/framebuffer.h> +#include <libcamera/geometry.h> + +#include <libcamera/ipa/core_ipa_interface.h> +#include <libcamera/ipa/ipa_interface.h> + namespace libcamera { {%- if has_namespace %} {% for ns in namespace %} diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl index c37c4941..9a3aadbd 100644 --- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl @@ -8,7 +8,7 @@ /* * Copyright (C) 2020, Google Inc. * - * {{module_name}}_ipa_proxy.cpp - Image Processing Algorithm proxy for {{module_name}} + * Image Processing Algorithm proxy for {{module_name}} * * This file is auto-generated. Do not edit. */ @@ -127,13 +127,13 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data) {{proxy_funcs.func_sig(proxy_name, method)}} { if (isolate_) - {{"return " if method|method_return_value != "void"}}{{method.mojom_name}}IPC( + return {{method.mojom_name}}IPC( {%- for param in method|method_param_names -%} {{param}}{{- ", " if not loop.last}} {%- endfor -%} ); else - {{"return " if method|method_return_value != "void"}}{{method.mojom_name}}Thread( + return {{method.mojom_name}}Thread( {%- for param in method|method_param_names -%} {{param}}{{- ", " if not loop.last}} {%- endfor -%} @@ -159,25 +159,23 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data) state_ = ProxyRunning; thread_.start(); - {{ "return " if method|method_return_value != "void" -}} - proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking + return proxy_.invokeMethod(&ThreadProxy::start, ConnectionTypeBlocking {{- ", " if method|method_param_names}} {%- for param in method|method_param_names -%} {{param}}{{- ", " if not loop.last}} {%- endfor -%} ); {%- elif not method|is_async %} - {{ "return " if method|method_return_value != "void" -}} - ipa_->{{method.mojom_name}}( + return ipa_->{{method.mojom_name}}( {%- for param in method|method_param_names -%} {{param}}{{- ", " if not loop.last}} {%- endfor -%} ); {% elif method|is_async %} ASSERT(state_ == ProxyRunning); - proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued, + proxy_.invokeMethod(&ThreadProxy::{{method.mojom_name}}, ConnectionTypeQueued {%- for param in method|method_param_names -%} - {{param}}{{- ", " if not loop.last}} + , {{param}} {%- endfor -%} ); {%- endif %} @@ -206,7 +204,7 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data) ); {%- endif %} if (_ret < 0) { - LOG(IPAProxy, Error) << "Failed to call {{method.mojom_name}}"; + LOG(IPAProxy, Error) << "Failed to call {{method.mojom_name}}: " << _ret; {%- if method|method_return_value != "void" %} return static_cast<{{method|method_return_value}}>(_ret); {%- else %} @@ -235,14 +233,11 @@ void {{proxy_name}}::recvMessage(const IPCMessage &data) } void {{proxy_name}}::{{method.mojom_name}}IPC( - std::vector<uint8_t>::const_iterator data, - size_t dataSize, + [[maybe_unused]] std::vector<uint8_t>::const_iterator data, + [[maybe_unused]] size_t dataSize, [[maybe_unused]] const std::vector<SharedFD> &fds) { -{%- for param in method.parameters %} - {{param|name}} {{param.mojom_name}}; -{%- endfor %} -{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, false, true, 'dataSize')}} +{{proxy_funcs.deserialize_call(method.parameters, 'data', 'fds', false, true, true, 'dataSize')}} {{method.mojom_name}}.emit({{method.parameters|params_comma_sep}}); } {% endfor %} diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl index ed270f5c..a0312a7c 100644 --- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl @@ -8,7 +8,7 @@ /* * Copyright (C) 2020, Google Inc. * - * {{module_name}}_ipa_proxy.h - Image Processing Algorithm proxy for {{module_name}} + * Image Processing Algorithm proxy for {{module_name}} * * This file is auto-generated. Do not edit. */ @@ -18,6 +18,7 @@ #include <libcamera/ipa/ipa_interface.h> #include <libcamera/ipa/{{module_name}}_ipa_interface.h> +#include <libcamera/base/object.h> #include <libcamera/base/thread.h> #include "libcamera/internal/control_serializer.h" @@ -43,15 +44,6 @@ public: {{proxy_funcs.func_sig(proxy_name, method, "", false, true)|indent(8, true)}}; {% endfor %} -{%- for method in interface_event.methods %} - Signal< -{%- for param in method.parameters -%} - {{"const " if not param|is_pod}}{{param|name}}{{" &" if not param|is_pod and not param|is_enum}} - {{- ", " if not loop.last}} -{%- endfor -%} -> {{method.mojom_name}}; -{% endfor %} - private: void recvMessage(const IPCMessage &data); diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl index b65dc4cf..1f990d3f 100644 --- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl @@ -8,7 +8,7 @@ /* * Copyright (C) 2020, Google Inc. * - * {{module_name}}_ipa_proxy_worker.cpp - Image Processing Algorithm proxy worker for {{module_name}} + * Image Processing Algorithm proxy worker for {{module_name}} * * This file is auto-generated. Do not edit. */ diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl index 8b709705..65a7dd11 100644 --- a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl @@ -8,7 +8,7 @@ /* * Copyright (C) 2020, Google Inc. * - * {{module_name}}_ipa_serializer.h - Image Processing Algorithm data serializer for {{module_name}} + * Image Processing Algorithm data serializer for {{module_name}} * * This file is auto-generated. Do not edit. */ @@ -32,13 +32,8 @@ template<> class IPADataSerializer<{{struct|name_full}}> { public: -{{- serializer.serializer(struct, namespace_str)}} -{%- if struct|has_fd %} -{{serializer.deserializer_fd(struct, namespace_str)}} -{%- else %} -{{serializer.deserializer_no_fd(struct, namespace_str)}} -{{serializer.deserializer_fd_simple(struct, namespace_str)}} -{%- endif %} +{{- serializer.serializer(struct)}} +{{- serializer.deserializer(struct)}} }; {% endfor %} diff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/codegen/ipc/generators/libcamera_templates/proxy_functions.tmpl index 2be65d43..25476990 100644 --- a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/proxy_functions.tmpl @@ -34,7 +34,7 @@ thread_.exit(); thread_.wait(); - Thread::current()->dispatchMessages(Message::Type::InvokeMessage); + Thread::current()->dispatchMessages(Message::Type::InvokeMessage, this); state_ = ProxyStopped; {%- endmacro -%} @@ -186,7 +186,7 @@ IPADataSerializer<{{param|name}}>::deserialize( {% for param in params|with_fds %} {%- if loop.first %} const size_t {{param.mojom_name}}FdStart = 0; -{%- elif not loop.last %} +{%- else %} const size_t {{param.mojom_name}}FdStart = {{loop.previtem.mojom_name}}FdStart + {{loop.previtem.mojom_name}}FdsSize; {%- endif %} {%- endfor %} diff --git a/utils/ipc/generators/libcamera_templates/serializer.tmpl b/utils/codegen/ipc/generators/libcamera_templates/serializer.tmpl index 323e1293..d07836cc 100644 --- a/utils/ipc/generators/libcamera_templates/serializer.tmpl +++ b/utils/codegen/ipc/generators/libcamera_templates/serializer.tmpl @@ -28,7 +28,7 @@ # # \todo Avoid intermediate vectors #} -{%- macro serializer_field(field, namespace, loop) %} +{%- macro serializer_field(field, loop) %} {%- if field|is_pod or field|is_enum %} std::vector<uint8_t> {{field.mojom_name}}; std::tie({{field.mojom_name}}, std::ignore) = @@ -94,7 +94,7 @@ # Generate code to deserialize \a field into object ret. # This code is meant to be used by the IPADataSerializer specialization. #} -{%- macro deserializer_field(field, namespace, loop) %} +{%- macro deserializer_field(field, loop) %} {% if field|is_pod or field|is_enum %} {%- set field_size = (field|bit_width|int / 8)|int %} {{- check_data_size(field_size, 'dataSize', field.mojom_name, 'data')}} @@ -182,7 +182,7 @@ # Generate code for IPADataSerializer specialization, for serializing # \a struct. #} -{%- macro serializer(struct, namespace) %} +{%- macro serializer(struct) %} static std::tuple<std::vector<uint8_t>, std::vector<SharedFD>> serialize(const {{struct|name_full}} &data, {%- if struct|needs_control_serializer %} @@ -196,7 +196,7 @@ std::vector<SharedFD> retFds; {%- endif %} {%- for field in struct.fields %} -{{serializer_field(field, namespace, loop)}} +{{serializer_field(field, loop)}} {%- endfor %} {% if struct|has_fd %} return {retData, retFds}; @@ -213,7 +213,7 @@ # Generate code for IPADataSerializer specialization, for deserializing # \a struct, in the case that \a struct has file descriptors. #} -{%- macro deserializer_fd(struct, namespace) %} +{%- macro deserializer_fd(struct) %} static {{struct|name_full}} deserialize(std::vector<uint8_t> &data, std::vector<SharedFD> &fds, @@ -245,7 +245,7 @@ size_t dataSize = std::distance(dataBegin, dataEnd); [[maybe_unused]] size_t fdsSize = std::distance(fdsBegin, fdsEnd); {%- for field in struct.fields -%} -{{deserializer_field(field, namespace, loop)}} +{{deserializer_field(field, loop)}} {%- endfor %} return ret; } @@ -258,7 +258,7 @@ # \a struct, in the case that \a struct has no file descriptors but requires # deserializers with file descriptors. #} -{%- macro deserializer_fd_simple(struct, namespace) %} +{%- macro deserializer_fd_simple(struct) %} static {{struct|name_full}} deserialize(std::vector<uint8_t> &data, [[maybe_unused]] std::vector<SharedFD> &fds, @@ -285,7 +285,7 @@ # Generate code for IPADataSerializer specialization, for deserializing # \a struct, in the case that \a struct does not have file descriptors. #} -{%- macro deserializer_no_fd(struct, namespace) %} +{%- macro deserializer_no_fd(struct) %} static {{struct|name_full}} deserialize(std::vector<uint8_t> &data, {%- if struct|needs_control_serializer %} @@ -312,8 +312,22 @@ size_t dataSize = std::distance(dataBegin, dataEnd); {%- for field in struct.fields -%} -{{deserializer_field(field, namespace, loop)}} +{{deserializer_field(field, loop)}} {%- endfor %} return ret; } {%- endmacro %} + +{# + # \brief Deserialize a struct + # + # Generate code for IPADataSerializer specialization, for deserializing \a struct. + #} +{%- macro deserializer(struct) %} +{%- if struct|has_fd %} +{{deserializer_fd(struct)}} +{%- else %} +{{deserializer_no_fd(struct)}} +{{deserializer_fd_simple(struct)}} +{%- endif %} +{%- endmacro %} diff --git a/utils/ipc/generators/meson.build b/utils/codegen/ipc/generators/meson.build index 504f1a46..504f1a46 100644 --- a/utils/ipc/generators/meson.build +++ b/utils/codegen/ipc/generators/meson.build diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/codegen/ipc/generators/mojom_libcamera_generator.py index 1a629f9d..eff29a5b 100644 --- a/utils/ipc/generators/mojom_libcamera_generator.py +++ b/utils/codegen/ipc/generators/mojom_libcamera_generator.py @@ -4,7 +4,7 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# mojom_libcamera_generator.py - Generates libcamera files from a mojom.Module. +# Generates libcamera files from a mojom.Module. import argparse import datetime @@ -72,7 +72,7 @@ def ParamsCommaSep(l): def GetDefaultValue(element): if element.default is not None: return element.default - if type(element.kind) == mojom.Kind: + if type(element.kind) == mojom.ValueKind: return '0' if IsFlags(element): return '' @@ -166,7 +166,7 @@ def MethodParamOutputs(method): return method.response_parameters[1:] def MethodParamsHaveFd(parameters): - return len([x for x in parameters if HasFd(x)]) > 0 + return any(x for x in parameters if HasFd(x)) def MethodInputHasFd(method): return MethodParamsHaveFd(method.parameters) @@ -369,7 +369,7 @@ def ValidateNamespace(namespace): if namespace == '': raise Exception('Must have a namespace') - if not re.match('^ipa\.[0-9A-Za-z_]+', namespace): + if not re.match(r'^ipa\.[0-9A-Za-z_]+', namespace): raise Exception('Namespace must be of the form "ipa.{pipeline_name}"') def ValidateInterfaces(interfaces): @@ -465,8 +465,9 @@ class Generator(generator.Generator): 'cmd_event_enum_name': '_%sEventCmd' % self.module_name, 'consts': self.module.constants, 'enums': self.module.enums, - 'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0, - 'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0, + 'has_array': any(x for x in self.module.kinds.keys() if x[0] == 'a'), + 'has_map': any(x for x in self.module.kinds.keys() if x[0] == 'm'), + 'has_string': any(x for x in self.module.kinds.keys() if x[0] == 's'), 'has_namespace': self.module.mojom_namespace != '', 'interface_event': GetEventInterface(self.module.interfaces), 'interface_main': GetMainInterface(self.module.interfaces), @@ -484,8 +485,9 @@ class Generator(generator.Generator): return { 'consts': self.module.constants, 'enums_gen_header': [x for x in self.module.enums if x.attributes is None or 'skipHeader' not in x.attributes], - 'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0, - 'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0, + 'has_array': any(x for x in self.module.kinds.keys() if x[0] == 'a'), + 'has_map': any(x for x in self.module.kinds.keys() if x[0] == 'm'), + 'has_string': any(x for x in self.module.kinds.keys() if x[0] == 's'), 'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes], 'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes], } diff --git a/utils/ipc/meson.build b/utils/codegen/ipc/meson.build index 973a5417..f77bf324 100644 --- a/utils/ipc/meson.build +++ b/utils/codegen/ipc/meson.build @@ -13,6 +13,7 @@ mojom_docs_extractor = find_program('./extract-docs.py') mojom_templates = custom_target('mojom_templates', input : mojom_template_files, output : 'libcamera_templates.zip', - command : [mojom_generator, '-o', '@OUTDIR@', 'precompile']) + command : [mojom_generator, '-o', '@OUTDIR@', 'precompile'], + env : py_build_env) mojom_templates_dir = meson.current_build_dir() diff --git a/utils/ipc/mojo/README b/utils/codegen/ipc/mojo/README index d5c24fc3..961cabd2 100644 --- a/utils/ipc/mojo/README +++ b/utils/codegen/ipc/mojo/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from 9c138d992bfc of Chromium. Do not +Files in this directory are imported from 9be4263648d7 of Chromium. Do not modify them manually. diff --git a/utils/ipc/mojo/public/LICENSE b/utils/codegen/ipc/mojo/public/LICENSE index 972bb2ed..513e8a6a 100644 --- a/utils/ipc/mojo/public/LICENSE +++ b/utils/codegen/ipc/mojo/public/LICENSE @@ -1,4 +1,4 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. +// Copyright 2014 The Chromium Authors // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/utils/ipc/mojo/public/tools/.style.yapf b/utils/codegen/ipc/mojo/public/tools/.style.yapf index b4ebbe24..b4ebbe24 100644 --- a/utils/ipc/mojo/public/tools/.style.yapf +++ b/utils/codegen/ipc/mojo/public/tools/.style.yapf diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/BUILD.gn index eb6391a6..5328a34a 100644 --- a/utils/ipc/mojo/public/tools/BUILD.gn +++ b/utils/codegen/ipc/mojo/public/tools/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,7 +10,11 @@ group("mojo_python_unittests") { "run_all_python_unittests.py", "//testing/scripts/run_isolated_script_test.py", ] - deps = [ "//mojo/public/tools/mojom/mojom:tests" ] + deps = [ + "//mojo/public/tools/bindings:tests", + "//mojo/public/tools/mojom:tests", + "//mojo/public/tools/mojom/mojom:tests", + ] data_deps = [ "//testing:test_scripts_shared", "//third_party/catapult/third_party/typ/", diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn index 3e242532..eeca73ea 100644 --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn +++ b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn @@ -1,24 +1,27 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. +# Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/python.gni") import("//mojo/public/tools/bindings/mojom.gni") import("//third_party/jinja2/jinja2.gni") -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. -python2_action("precompile_templates") { +action("precompile_templates") { sources = mojom_generator_sources sources += [ + "$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl", @@ -26,7 +29,6 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl", - "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl", "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", "$mojom_generator_root/generators/cpp_templates/module.h.tmpl", @@ -65,9 +67,6 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/java_templates/struct.java.tmpl", "$mojom_generator_root/generators/java_templates/union.java.tmpl", "$mojom_generator_root/generators/js_templates/enum_definition.tmpl", - "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl", - "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl", - "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl", "$mojom_generator_root/generators/js_templates/fuzzing.tmpl", "$mojom_generator_root/generators/js_templates/interface_definition.tmpl", "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl", @@ -93,8 +92,11 @@ python2_action("precompile_templates") { "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl", "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl", "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl", + "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl", "$mojom_generator_root/generators/ts_templates/module_definition.tmpl", - "$mojom_generator_root/generators/ts_templates/mojom.tmpl", + "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/ts_templates/union_definition.tmpl", ] script = mojom_generator_script @@ -102,8 +104,8 @@ python2_action("precompile_templates") { outputs = [ "$target_gen_dir/cpp_templates.zip", "$target_gen_dir/java_templates.zip", - "$target_gen_dir/mojolpm_templates.zip", "$target_gen_dir/js_templates.zip", + "$target_gen_dir/mojolpm_templates.zip", "$target_gen_dir/ts_templates.zip", ] args = [ @@ -113,3 +115,17 @@ python2_action("precompile_templates") { "precompile", ] } + +group("tests") { + data = [ + mojom_generator_script, + "checks/mojom_attributes_check_unittest.py", + "checks/mojom_interface_feature_check_unittest.py", + "checks/mojom_restrictions_checks_unittest.py", + "mojom_bindings_generator_unittest.py", + "//tools/diagnosis/crbug_1001171.py", + "//third_party/markupsafe/", + ] + data += mojom_generator_sources + data += jinja2_sources +} diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/codegen/ipc/mojo/public/tools/bindings/README.md index 43882450..b27b2d01 100644 --- a/utils/ipc/mojo/public/tools/bindings/README.md +++ b/utils/codegen/ipc/mojo/public/tools/bindings/README.md @@ -96,7 +96,7 @@ for message parameters. | `string` | UTF-8 encoded string. | `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`. | `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant. -| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. +| `map<S, T>` | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. | `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle. | `handle<message_pipe>` | Generic message pipe handle. | `handle<shared_buffer>` | Shared buffer handle. @@ -188,8 +188,8 @@ struct StringPair { }; enum AnEnum { - YES, - NO + kYes, + kNo }; interface SampleInterface { @@ -209,7 +209,7 @@ struct AllTheThings { uint64 unsigned_64bit_value; float float_value_32bit; double float_value_64bit; - AnEnum enum_value = AnEnum.YES; + AnEnum enum_value = AnEnum.kYes; // Strings may be nullable. string? maybe_a_string_maybe_not; @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface: module business.mojom; enum Department { - SALES = 0, - DEV, + kSales = 0, + kDev, }; struct Employee { enum Type { - FULL_TIME, - PART_TIME, + kFullTime, + kPartTime, }; Type type; @@ -315,6 +315,9 @@ struct Employee { }; ``` +C++ constant-style enum value names are preferred as specified in the +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names). + Similar to C-style enums, individual values may be explicitly assigned within an enum definition. By default, values are based at zero and increment by 1 sequentially. @@ -336,8 +339,8 @@ struct Employee { const uint64 kInvalidId = 0; enum Type { - FULL_TIME, - PART_TIME, + kFullTime, + kPartTime, }; uint64 id = kInvalidId; @@ -348,6 +351,37 @@ struct Employee { The effect of nested definitions on generated bindings varies depending on the target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages). +### Features + +Features can be declared with a `name` and `default_state` and can be attached +in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the +feature is disabled at runtime, the method will crash and the interface will +refuse to be bound / instantiated. Features cannot be serialized to be sent over +IPC at this time. + +``` +module experimental.mojom; + +feature kUseElevators { + const string name = "UseElevators"; + const bool default_state = false; +} + +[RuntimeFeature=kUseElevators] +interface Elevator { + // This interface cannot be bound or called if the feature is disabled. +} + +interface Building { + // This method cannot be called if the feature is disabled. + [RuntimeFeature=kUseElevators] + CallElevator(int floor); + + // This method can be called. + RingDoorbell(int volume); +} +``` + ### Interfaces An **interface** is a logical bundle of parameterized request messages. Each @@ -396,20 +430,33 @@ interesting attributes supported today. extreme caution, because it can lead to deadlocks otherwise. * **`[Default]`**: - The `Default` attribute may be used to specify an enumerator value that - will be used if an `Extensible` enumeration does not deserialize to a known - value on the receiver side, i.e. the sender is using a newer version of the - enum. This allows unknown values to be mapped to a well-defined value that can - be appropriately handled. + The `Default` attribute may be used to specify an enumerator value or union + field that will be used if an `Extensible` enumeration or union does not + deserialize to a known value on the receiver side, i.e. the sender is using a + newer version of the enum or union. This allows unknown values to be mapped to + a well-defined value that can be appropriately handled. + + Note: The `Default` field for a union must be of nullable or integral type. + When a union is defaulted to this field, the field takes on the default value + for its type: null for nullable types, and zero/false for integral types. * **`[Extensible]`**: - The `Extensible` attribute may be specified for any enum definition. This - essentially disables builtin range validation when receiving values of the - enum type in a message, allowing older bindings to tolerate unrecognized - values from newer versions of the enum. + The `Extensible` attribute may be specified for any enum or union definition. + For enums, this essentially disables builtin range validation when receiving + values of the enum type in a message, allowing older bindings to tolerate + unrecognized values from newer versions of the enum. - Note: in the future, an `Extensible` enumeration will require that a `Default` - enumerator value also be specified. + If an enum value within an extensible enum definition is affixed with the + `Default` attribute, out-of-range values for the enum will deserialize to that + default value. Only one enum value may be designated as the `Default`. + + Similarly, a union marked `Extensible` will deserialize to its `Default` field + when an unrecognized field is received. Extensible unions MUST specify exactly + one `Default` field, and the field must be of nullable or integral type. When + defaulted to this field, the value is always null/zero/false as appropriate. + + An `Extensible` enumeration REQUIRES that a `Default` value be specified, + so all new extensible enums should specify one. * **`[Native]`**: The `Native` attribute may be specified for an empty struct declaration to @@ -422,7 +469,10 @@ interesting attributes supported today. * **`[MinVersion=N]`**: The `MinVersion` attribute is used to specify the version at which a given field, enum value, interface method, or method parameter was introduced. - See [Versioning](#Versioning) for more details. + See [Versioning](#Versioning) for more details. `MinVersion` does not apply + to interfaces, structs or enums, but to the fields of those types. + `MinVersion` is not a module-global value, but it is ok to pretend it is by + skipping versions when adding fields or parameters. * **`[Stable]`**: The `Stable` attribute specifies that a given mojom type or interface @@ -442,13 +492,73 @@ interesting attributes supported today. string representation as specified by RFC 4122. New UUIDs can be generated with common tools such as `uuidgen`. +* **`[RuntimeFeature=feature]`** + The `RuntimeFeature` attribute should reference a mojo `feature`. If this + feature is enabled (e.g. using `--enable-features={feature.name}`) then the + interface behaves entirely as expected. If the feature is not enabled the + interface cannot be bound to a concrete receiver or remote - attempting to do + so will result in the receiver or remote being reset() to an unbound state. + Note that this is a different concept to the build-time `EnableIf` directive. + `RuntimeFeature` is currently only supported for C++ bindings and has no + effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253). + * **`[EnableIf=value]`**: The `EnableIf` attribute is used to conditionally enable definitions when the mojom is parsed. If the `mojom` target in the GN file does not include the matching `value` in the list of `enabled_features`, the definition will be disabled. This is useful for mojom definitions that only make sense on one platform. Note that the `EnableIf` attribute can only be set once per - definition. + definition and cannot be set at the same time as `EnableIfNot`. Also be aware + that only one condition can be tested, `EnableIf=value,xyz` introduces a new + `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends + only on the feature `value`. Complex conditions can be introduced via + enabled_features in `build.gn` files. + +* **`[EnableIfNot=value]`**: + The `EnableIfNot` attribute is used to conditionally enable definitions when + the mojom is parsed. If the `mojom` target in the GN file includes the + matching `value` in the list of `enabled_features`, the definition will be + disabled. This is useful for mojom definitions that only make sense on all but + one platform. Note that the `EnableIfNot` attribute can only be set once per + definition and cannot be set at the same time as `EnableIf`. + +* **`[ServiceSandbox=value]`**: + The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a + service hosting an implementation of interface will be launched in. This only + applies to `C++` bindings. `value` should match a constant defined in an + imported `sandbox.mojom.Sandbox` enum (for Chromium this is + `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`. + +* **`[RequireContext=enum]`**: + The `RequireContext` attribute is used in Chromium to tag interfaces that + should be passed (as remotes or receivers) only to privileged process + contexts. The process context must be an enum that is imported into the + mojom that defines the tagged interface. `RequireContext` may be used in + future to DCHECK or CHECK if remotes are made available in contexts that + conflict with the one provided in the interface definition. Process contexts + are not the same as the sandbox a process is running in, but will reflect + the set of capabilities provided to the service. + +* **`[AllowedContext=enum]`**: + The `AllowedContext` attribute is used in Chromium to tag methods that pass + remotes or receivers of interfaces that are marked with a `RequireContext` + attribute. The enum provided on the method must be equal or better (lower + numerically) than the one required on the interface being passed. At present + failing to specify an adequate `AllowedContext` value will cause mojom + generation to fail at compile time. In future DCHECKs or CHECKs might be + added to enforce that method is only called from a process context that meets + the given `AllowedContext` value. The enum must of the same type as that + specified in the interface's `RequireContext` attribute. Adding an + `AllowedContext` attribute to a method is a strong indication that you need + a detailed security review of your design - please reach out to the security + team. + +* **`[SupportsUrgent]`**: + The `SupportsUrgent` attribute is used in conjunction with + `mojo::UrgentMessageScope` in Chromium to tag messages as having high + priority. The IPC layer notifies the underlying scheduler upon both receiving + and processing an urgent message. At present, this attribute only affects + channel associated messages in the renderer process. ## Generated Code For Target Languages @@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum: ``` cpp enum AdvancedBoolean { - TRUE = 0, - FALSE = 1, - FILE_NOT_FOUND = 2, + kTrue = 0, + kFalse = 1, + kFileNotFound = 2, }; ``` @@ -550,10 +660,16 @@ See the documentation for *** note **NOTE:** You don't need to worry about versioning if you don't care about -backwards compatibility. Specifically, all parts of Chrome are updated -atomically today and there is not yet any possibility of any two Chrome -processes communicating with two different versions of any given Mojom -interface. +backwards compatibility. Today, all parts of the Chrome browser are +updated atomically and there is not yet any possibility of any two +Chrome processes communicating with two different versions of any given Mojom +interface. On Chrome OS, there are several places where versioning is required. +For example, +[ARC++](https://developer.android.com/chrome-os/intro) +uses versioned mojo to send IPC to the Android container. +Likewise, the +[Lacros](/docs/lacros.md) +browser uses versioned mojo to talk to the ash system UI. *** Services extend their interfaces to support new features over time, and clients @@ -593,8 +709,8 @@ struct Employee { *** note **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be -optional (nullable). See [Primitive Types](#Primitive-Types) for details on -nullable values. +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for +details on nullable values. *** By default, fields belong to version 0. New fields must be appended to the @@ -624,10 +740,10 @@ the following hard constraints: * For any given struct or interface, if any field or method explicitly specifies an ordinal value, all fields or methods must explicitly specify an ordinal value. -* For an *N*-field struct or *N*-method interface, the set of explicitly - assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces - should include placeholder methods to fill the ordinal positions of removed - methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc). +* For an *N*-field struct, the set of explicitly assigned ordinal values must be + limited to the range *[0, N-1]*. Structs should include placeholder fields + to fill the ordinal positions of removed fields (for example "Unused_Field" + or "RemovedField", etc). You may reorder fields, but you must ensure that the ordinal values of existing fields remain unchanged. For example, the following struct remains @@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended that the version number is scoped to the whole interface rather than to any individual parameter list. +``` cpp +// Old version: +interface HumanResourceDatabase { + QueryEmployee(uint64 id) => (Employee? employee); +}; + +// New version: +interface HumanResourceDatabase { + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, + [MinVersion=1] array<uint8>? finger_print); +}; +``` + +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter +list of a request or response method to a destination using an older version of +an interface, unrecognized fields are silently discarded. + Please note that adding a response to a message which did not previously expect a response is a not a backwards-compatible change. @@ -664,17 +798,12 @@ For example: ``` cpp // Old version: interface HumanResourceDatabase { - AddEmployee(Employee employee) => (bool success); QueryEmployee(uint64 id) => (Employee? employee); }; // New version: interface HumanResourceDatabase { - AddEmployee(Employee employee) => (bool success); - - QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) - => (Employee? employee, - [MinVersion=1] array<uint8>? finger_print); + QueryEmployee(uint64 id) => (Employee? employee); [MinVersion=1] AttachFingerPrint(uint64 id, array<uint8> finger_print) @@ -682,10 +811,7 @@ interface HumanResourceDatabase { }; ``` -Similar to [versioned structs](#Versioned-Structs), when you pass the parameter -list of a request or response method to a destination using an older version of -an interface, unrecognized fields are silently discarded. However, if the method -call itself is not recognized, it is considered a validation error and the +If a method call is not recognized, it is considered a validation error and the receiver will close its end of the interface pipe. For example, if a client on version 1 of the above interface sends an `AttachFingerPrint` request to an implementation of version 0, the client will be disconnected. @@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the ``` cpp [Extensible] enum Department { - SALES, - DEV, + kSales, + kDev, }; ``` @@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility: ``` cpp [Extensible] enum Department { - SALES, - DEV, - [MinVersion=1] RESEARCH, + kSales, + kDev, + [MinVersion=1] kResearch, }; ``` @@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition ModuleStatement = AttributeSection "module" Identifier ";" ImportStatement = "import" StringLiteral ";" -Definition = Struct Union Interface Enum Const +Definition = Struct Union Interface Enum Feature Const AttributeSection = <empty> | "[" AttributeList "]" AttributeList = <empty> | NonEmptyAttributeList @@ -809,7 +935,7 @@ InterfaceBody = <empty> | InterfaceBody Const | InterfaceBody Enum | InterfaceBody Method -Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";" +Method = AttributeSection Name Ordinal "(" ParameterList ")" Response ";" ParameterList = <empty> | NonEmptyParameterList NonEmptyParameterList = Parameter | Parameter "," NonEmptyParameterList @@ -847,6 +973,13 @@ EnumValue = AttributeSection Name | AttributeSection Name "=" Integer | AttributeSection Name "=" Identifier +; Note: `feature` is a weak keyword and can appear as, say, a struct field name. +Feature = AttributeSection "feature" Name "{" FeatureBody "}" ";" + | AttributeSection "feature" Name ";" +FeatureBody = <empty> + | FeatureBody FeatureField +FeatureField = AttributeSection TypeSpec Name Default ";" + Const = "const" TypeSpec Name "=" Constant ";" Constant = Literal | Identifier ";" diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py index e69de29b..e69de29b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py new file mode 100644 index 00000000..e6e4f2c9 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py @@ -0,0 +1,170 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Validate mojo attributes are allowed in Chrome before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + +_COMMON_ATTRIBUTES = { + 'EnableIf', + 'EnableIfNot', +} + +# For struct, union & parameter lists. +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'MinVersion', + 'RenamedFrom', +} + +# Note: `Default`` goes on the default _value_, not on the enum. +# Note: [Stable] without [Extensible] is not allowed. +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Extensible', + 'Native', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal. +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Default', + 'MinVersion', +} + +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'RenamedFrom', + 'RequireContext', + 'RuntimeFeature', + 'ServiceSandbox', + 'Stable', + 'Uuid', +} + +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'AllowedContext', + 'MinVersion', + 'NoInterrupt', + 'RuntimeFeature', + 'SupportsUrgent', + 'Sync', + 'UnlimitedSize', +} + +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'JavaConstantsClassName', + 'JavaPackage', +} + +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES + +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'CustomSerializer', + 'JavaClassName', + 'Native', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES + +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | { + 'Extensible', + 'Stable', + 'RenamedFrom', + 'Uuid', +} + +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | { + 'Default', +} + +# TODO(https://crbug.com/1193875) empty this set and remove the allowlist. +_STABLE_ONLY_ALLOWLISTED_ENUMS = { + 'crosapi.mojom.OptionalBool', + 'crosapi.mojom.TriState', +} + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + def _Respell(self, allowed, attribute): + for a in allowed: + if a.lower() == attribute.lower(): + return f" - Did you mean: {a}?" + return "" + + def _CheckAttributes(self, context, allowed, attributes): + if not attributes: + return + for attribute in attributes: + if not attribute in allowed: + # Is there a close misspelling? + hint = self._Respell(allowed, attribute) + raise check.CheckException( + self.module, + f"attribute {attribute} not allowed on {context}{hint}") + + def _CheckEnumAttributes(self, enum): + if enum.attributes: + self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes) + if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes: + full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}" + if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS: + raise check.CheckException( + self.module, + f"[Extensible] required on [Stable] enum {full_name}") + for enumval in enum.fields: + self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES, + enumval.attributes) + + def _CheckInterfaceAttributes(self, interface): + self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES, + interface.attributes) + for method in interface.methods: + self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes) + for param in method.parameters: + self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES, + param.attributes) + if method.response_parameters: + for param in method.response_parameters: + self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES, + param.attributes) + for enum in interface.enums: + self._CheckEnumAttributes(enum) + + def _CheckModuleAttributes(self): + self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes) + + def _CheckStructAttributes(self, struct): + self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes) + for field in struct.fields: + self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES, + field.attributes) + for enum in struct.enums: + self._CheckEnumAttributes(enum) + + def _CheckUnionAttributes(self, union): + self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes) + for field in union.fields: + self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES, + field.attributes) + + def CheckModule(self): + """Note that duplicate attributes are forbidden at the parse phase. + We also do not need to look at the types of any parameters, as they will be + checked where they are defined. Consts do not have attributes so can be + skipped.""" + self._CheckModuleAttributes() + for interface in self.module.interfaces: + self._CheckInterfaceAttributes(interface) + for enum in self.module.enums: + self._CheckEnumAttributes(enum) + for struct in self.module.structs: + self._CheckStructAttributes(struct) + for union in self.module.unions: + self._CheckUnionAttributes(union) diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py new file mode 100644 index 00000000..f1a50a4a --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py @@ -0,0 +1,194 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'attributes' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def _testValid(self, filename, content): + self.WriteFile(filename, content) + self._ParseAndGenerate([filename]) + + def _testThrows(self, filename, content, regexp): + mojoms = [] + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegexp(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('attributes') + self.assertTrue(check_modules['attributes']) + + def testNoAnnotations(self): + # Undecorated mojom should be fine. + self._testValid( + "a.mojom", """ + module a; + struct Bar { int32 a; }; + enum Hello { kValue }; + union Thingy { Bar b; Hello hi; }; + interface Foo { + Foo(int32 a, Hello hi, Thingy t) => (Bar b); + }; + """) + + def testValidAnnotations(self): + # Obviously this is meaningless and won't generate, but it should pass + # the attribute check's validation. + self._testValid( + "a.mojom", """ + [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"] + module a; + [Stable, Extensible] + enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 }; + [Native] + enum NativeEnum {}; + [Stable,Extensible] + union Thingy { Bar b; [Default]int32 c; Hello hi; }; + + [Stable,RenamedFrom="module.other.Foo", + Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"] + struct Structure { Hello hi; }; + + [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable, + Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"] + interface Foo { + [AllowedContext=Hello.kValue] + Foo@0(int32 a) => (int32 b); + [MinVersion=2,Sync,UnlimitedSize,NoInterrupt] + Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c); + }; + + [RuntimeFeature=test.mojom.FeatureName] + interface FooFeatureControlled {}; + + interface FooMethodFeatureControlled { + [RuntimeFeature=test.mojom.FeatureName] + MethodWithFeature() => (bool c); + }; + """) + + def testWrongModuleStable(self): + contents = """ + // err: module cannot be Stable + [Stable] + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Stable not allowed on module') + + def testWrongEnumDefault(self): + contents = """ + module a; + // err: default should go on EnumValue not Enum. + [Default=kValue] + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute Default not allowed on enum') + + def testWrongStructMinVersion(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + // err: struct cannot have MinVersion. + [MinVersion=2] + struct Structure { Hello hi; }; + + interface Foo { + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute MinVersion not allowed on struct') + + def testWrongMethodRequireContext(self): + contents = """ + module a; + enum Hello { kValue, kValue2, kValue3 }; + enum NativeEnum {}; + struct Structure { Hello hi; }; + + interface Foo { + // err: RequireContext is for interfaces. + [RequireContext=Hello.kValue] + Foo(int32 a) => (int32 b); + Bar(int32 b, Structure? s) => (bool c); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext not allowed on method') + + def testWrongMethodRequireContext(self): + # crbug.com/1230122 + contents = """ + module a; + interface Foo { + // err: sync not Sync. + [sync] + Foo(int32 a) => (int32 b); + }; + """ + self._testThrows('b.mojom', contents, + 'attribute sync not allowed.*Did you mean: Sync') + + def testStableExtensibleEnum(self): + # crbug.com/1193875 + contents = """ + module a; + [Stable] + enum Foo { + kDefaultVal, + kOtherVal = 2, + }; + """ + self._testThrows('a.mojom', contents, + 'Extensible.*?required.*?Stable.*?enum') diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py new file mode 100644 index 00000000..702d41c3 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py @@ -0,0 +1,34 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Ensure no duplicate type definitions before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + def CheckModule(self): + kinds = dict() + for module in self.module.imports: + for kind in module.enums + module.structs + module.unions: + kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}' + if kind_name in kinds: + previous_module = kinds[kind_name] + if previous_module.path != module.path: + raise check.CheckException( + self.module, f"multiple-definition for type {kind_name}" + + f"(defined in both {previous_module} and {module})") + kinds[kind_name] = kind.module + + for kind in self.module.enums + self.module.structs + self.module.unions: + kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}' + if kind_name in kinds: + previous_module = kinds[kind_name] + raise check.CheckException( + self.module, f"multiple-definition for type {kind_name}" + + f"(previous definition in {previous_module})") + return True diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py new file mode 100644 index 00000000..07f51a64 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py @@ -0,0 +1,62 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Validate mojo runtime feature guarded interfaces are nullable.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + # `param` is an Interface of some sort. + def _CheckNonNullableFeatureGuardedInterface(self, kind): + # Only need to validate interface if it has a RuntimeFeature + if not kind.kind.runtime_feature: + return + # Nullable (optional) is ok as the interface expects they might not be sent. + if kind.is_nullable: + return + interface = kind.kind.mojom_name + raise check.CheckException( + self.module, + f"interface {interface} has a RuntimeFeature but is not nullable") + + # `param` can be a lot of things so check if it is a remote/receiver. + # Array/Map must be recursed into. + def _CheckFieldOrParam(self, kind): + if module.IsAnyInterfaceKind(kind): + self._CheckNonNullableFeatureGuardedInterface(kind) + if module.IsArrayKind(kind): + self._CheckFieldOrParam(kind.kind) + if module.IsMapKind(kind): + self._CheckFieldOrParam(kind.key_kind) + self._CheckFieldOrParam(kind.value_kind) + + def _CheckInterfaceFeatures(self, interface): + for method in interface.methods: + for param in method.parameters: + self._CheckFieldOrParam(param.kind) + if method.response_parameters: + for param in method.response_parameters: + self._CheckFieldOrParam(param.kind) + + def _CheckStructFeatures(self, struct): + for field in struct.fields: + self._CheckFieldOrParam(field.kind) + + def _CheckUnionFeatures(self, union): + for field in union.fields: + self._CheckFieldOrParam(field.kind) + + def CheckModule(self): + """Validate that any runtime feature guarded interfaces that might be passed + over mojo are nullable.""" + for interface in self.module.interfaces: + self._CheckInterfaceFeatures(interface) + for struct in self.module.structs: + self._CheckStructFeatures(struct) + for union in self.module.unions: + self._CheckUnionFeatures(union) diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py new file mode 100644 index 00000000..e96152fd --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py @@ -0,0 +1,173 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'features' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def assertValid(self, filename, content): + self.WriteFile(filename, content) + self._ParseAndGenerate([filename]) + + def assertThrows(self, filename, content, regexp): + mojoms = [] + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegexp(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('features') + self.assertTrue(check_modules['features']) + + def testNullableOk(self): + self.assertValid( + "a.mojom", """ + module a; + // Scaffolding. + feature kFeature { + const string name = "Hello"; + const bool enabled_state = false; + }; + [RuntimeFeature=kFeature] + interface Guarded { + }; + + // Unguarded interfaces should be ok everywhere. + interface NotGuarded { }; + + // Optional (nullable) interfaces should be ok everywhere: + struct Bar { + pending_remote<Guarded>? remote; + pending_receiver<Guarded>? receiver; + }; + union Thingy { + pending_remote<Guarded>? remote; + pending_receiver<Guarded>? receiver; + }; + interface Foo { + Foo( + pending_remote<Guarded>? remote, + pending_receiver<Guarded>? receiver, + pending_associated_remote<Guarded>? a_remote, + pending_associated_receiver<Guarded>? a_receiver, + // Unguarded interfaces do not have to be nullable. + pending_remote<NotGuarded> remote, + pending_receiver<NotGuarded> receiver, + pending_associated_remote<NotGuarded> a_remote, + pending_associated_receiver<NotGuarded> a_receiver + ) => ( + pending_remote<Guarded>? remote, + pending_receiver<Guarded>? receiver + ); + Bar(array<pending_remote<Guarded>?> remote) + => (map<string, pending_receiver<Guarded>?> a); + }; + """) + + def testMethodParamsMustBeNullable(self): + prelude = """ + module a; + // Scaffolding. + feature kFeature { + const string name = "Hello"; + const bool enabled_state = false; + }; + [RuntimeFeature=kFeature] + interface Guarded { }; + """ + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(pending_remote<Guarded> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(bool foo) => (pending_receiver<Guarded> a); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(pending_receiver<Guarded> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(pending_associated_remote<Guarded> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(pending_associated_receiver<Guarded> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(array<pending_associated_receiver<Guarded>> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + interface Trial { + Method(map<string, pending_associated_receiver<Guarded>> a) => (); + }; + """, 'interface Guarded has a RuntimeFeature') + + def testStructUnionMembersMustBeNullable(self): + prelude = """ + module a; + // Scaffolding. + feature kFeature { + const string name = "Hello"; + const bool enabled_state = false; + }; + [RuntimeFeature=kFeature] + interface Guarded { }; + """ + self.assertThrows( + 'a.mojom', prelude + """ + struct Trial { + pending_remote<Guarded> a; + }; + """, 'interface Guarded has a RuntimeFeature') + self.assertThrows( + 'a.mojom', prelude + """ + union Trial { + pending_remote<Guarded> a; + }; + """, 'interface Guarded has a RuntimeFeature') diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py new file mode 100644 index 00000000..d570e26c --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py @@ -0,0 +1,102 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Validate RequireContext and AllowedContext annotations before generation.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + def __init__(self, *args, **kwargs): + self.kind_to_interfaces = dict() + super(Check, self).__init__(*args, **kwargs) + + def _IsPassedInterface(self, candidate): + if isinstance( + candidate.kind, + (module.PendingReceiver, module.PendingRemote, + module.PendingAssociatedReceiver, module.PendingAssociatedRemote)): + return True + return False + + def _CheckInterface(self, method, param): + # |param| is a pending_x<Interface> so need .kind.kind to get Interface. + interface = param.kind.kind + if interface.require_context: + if method.allowed_context is None: + raise check.CheckException( + self.module, "method `{}` has parameter `{}` which passes interface" + " `{}` that requires an AllowedContext annotation but none exists.". + format( + method.mojom_name, + param.mojom_name, + interface.mojom_name, + )) + # If a string was provided, or if an enum was not imported, this will + # be a string and we cannot validate that it is in range. + if not isinstance(method.allowed_context, module.EnumValue): + raise check.CheckException( + self.module, + "method `{}` has AllowedContext={} which is not a valid enum value." + .format(method.mojom_name, method.allowed_context)) + # EnumValue must be from the same enum to be compared. + if interface.require_context.enum != method.allowed_context.enum: + raise check.CheckException( + self.module, "method `{}` has parameter `{}` which passes interface" + " `{}` that requires AllowedContext={} but one of kind `{}` was " + "provided.".format( + method.mojom_name, + param.mojom_name, + interface.mojom_name, + interface.require_context.enum, + method.allowed_context.enum, + )) + # RestrictContext enums have most privileged field first (lowest value). + interface_value = interface.require_context.field.numeric_value + method_value = method.allowed_context.field.numeric_value + if interface_value < method_value: + raise check.CheckException( + self.module, "RequireContext={} > AllowedContext={} for method " + "`{}` which passes interface `{}`.".format( + interface.require_context.GetSpec(), + method.allowed_context.GetSpec(), method.mojom_name, + interface.mojom_name)) + return True + + def _GatherReferencedInterfaces(self, field): + key = field.kind.spec + # structs/unions can nest themselves so we need to bookkeep. + if not key in self.kind_to_interfaces: + # Might reference ourselves so have to create the list first. + self.kind_to_interfaces[key] = set() + for param in field.kind.fields: + if self._IsPassedInterface(param): + self.kind_to_interfaces[key].add(param) + elif isinstance(param.kind, (module.Struct, module.Union)): + for iface in self._GatherReferencedInterfaces(param): + self.kind_to_interfaces[key].add(iface) + return self.kind_to_interfaces[key] + + def _CheckParams(self, method, params): + # Note: we have to repeat _CheckParams for each method as each might have + # different AllowedContext= attributes. We cannot memoize this function, + # but can do so for gathering referenced interfaces as their RequireContext + # attributes do not change. + for param in params: + if self._IsPassedInterface(param): + self._CheckInterface(method, param) + elif isinstance(param.kind, (module.Struct, module.Union)): + for interface in self._GatherReferencedInterfaces(param): + self._CheckInterface(method, interface) + + def _CheckMethod(self, method): + if method.parameters: + self._CheckParams(method, method.parameters) + if method.response_parameters: + self._CheckParams(method, method.response_parameters) + + def CheckModule(self): + for interface in self.module.interfaces: + for method in interface.methods: + self._CheckMethod(method) diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py new file mode 100644 index 00000000..a6cd71e2 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py @@ -0,0 +1,254 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + +# Mojoms that we will use in multiple tests. +basic_mojoms = { + 'level.mojom': + """ + module level; + enum Level { + kHighest, + kMiddle, + kLowest, + }; + """, + 'interfaces.mojom': + """ + module interfaces; + import "level.mojom"; + struct Foo {int32 bar;}; + [RequireContext=level.Level.kHighest] + interface High { + DoFoo(Foo foo); + }; + [RequireContext=level.Level.kMiddle] + interface Mid { + DoFoo(Foo foo); + }; + [RequireContext=level.Level.kLowest] + interface Low { + DoFoo(Foo foo); + }; + """ +} + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'restrictions' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + def _WriteBasicMojoms(self): + for filename, contents in basic_mojoms.items(): + self.WriteFile(filename, contents) + return list(basic_mojoms.keys()) + + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('restrictions') + self.assertTrue(check_modules['restrictions']) + + def testValidAnnotations(self): + mojoms = self._WriteBasicMojoms() + + a = 'a.mojom' + self.WriteFile( + a, """ + module a; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + [AllowedContext=level.Level.kHighest] + DoHigh(pending_receiver<interfaces.High> hi); + }; + interface PassesMedium { + [AllowedContext=level.Level.kMiddle] + DoMedium(pending_receiver<interfaces.Mid> hi); + [AllowedContext=level.Level.kMiddle] + DoMediumRem(pending_remote<interfaces.Mid> hi); + [AllowedContext=level.Level.kMiddle] + DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi); + [AllowedContext=level.Level.kMiddle] + DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi); + }; + interface PassesLow { + [AllowedContext=level.Level.kLowest] + DoLow(pending_receiver<interfaces.Low> hi); + }; + + struct One { pending_receiver<interfaces.High> hi; }; + struct Two { One one; }; + interface PassesNestedHigh { + [AllowedContext=level.Level.kHighest] + DoNestedHigh(Two two); + }; + + // Allowed as PassesHigh is not itself restricted. + interface PassesPassesHigh { + DoPass(pending_receiver<PassesHigh> hiho); + }; + """) + mojoms.append(a) + self._ParseAndGenerate(mojoms) + + def _testThrows(self, filename, content, regexp): + mojoms = self._WriteBasicMojoms() + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegexp(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testMissingAnnotation(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + // err: missing annotation. + DoHigh(pending_receiver<interfaces.High> hi); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testAllowTooLow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + + interface PassesHigh { + // err: level is worse than required. + [AllowedContext=level.Level.kMiddle] + DoHigh(pending_receiver<interfaces.High> hi); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle') + + def testWrongEnumInAllow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + enum Blah { + kZero, + }; + interface PassesHigh { + // err: different enums. + [AllowedContext=Blah.kZero] + DoHigh(pending_receiver<interfaces.High> hi); + }; + """ + self._testThrows('b.mojom', contents, 'but one of kind') + + def testNotAnEnumInAllow(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + interface PassesHigh { + // err: not an enum. + [AllowedContext=doopdedoo.mojom.kWhatever] + DoHigh(pending_receiver<interfaces.High> hi); + }; + """ + self._testThrows('b.mojom', contents, 'not a valid enum value') + + def testMissingAllowedForNestedStructs(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver<interfaces.High> hi; }; + struct Two { One one; }; + interface PassesNestedHigh { + // err: missing annotation. + DoNestedHigh(Two two); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testMissingAllowedForNestedUnions(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver<interfaces.High> hi; }; + struct Two { One one; }; + union Three {One one; Two two; }; + interface PassesNestedHigh { + // err: missing annotation. + DoNestedHigh(Three three); + }; + """ + self._testThrows('b.mojom', contents, 'require.*?AllowedContext') + + def testMultipleInterfacesThrows(self): + contents = """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver<interfaces.High> hi; }; + interface PassesMultipleInterfaces { + [AllowedContext=level.Level.kMiddle] + DoMultiple( + pending_remote<interfaces.Mid> mid, + pending_receiver<interfaces.High> hi, + One one + ); + }; + """ + self._testThrows('b.mojom', contents, + 'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle') + + def testMultipleInterfacesAllowed(self): + """Multiple interfaces can be passed, all satisfy the level.""" + mojoms = self._WriteBasicMojoms() + + b = "b.mojom" + self.WriteFile( + b, """ + module b; + import "level.mojom"; + import "interfaces.mojom"; + struct One { pending_receiver<interfaces.High> hi; }; + interface PassesMultipleInterfaces { + [AllowedContext=level.Level.kHighest] + DoMultiple( + pending_receiver<interfaces.High> hi, + pending_remote<interfaces.Mid> mid, + One one + ); + }; + """) + mojoms.append(b) + self._ParseAndGenerate(mojoms) diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py index 48bc66fd..4dd26d4a 100755 --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2019 The Chromium Authors. All rights reserved. +# Copyright 2019 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # @@ -15,6 +15,7 @@ from __future__ import print_function import optparse +import sys def Concatenate(filenames): @@ -47,7 +48,7 @@ def main(): parser.set_usage("""Concatenate several files into one. Equivalent to: cat file1 ... > target.""") (_options, args) = parser.parse_args() - exit(0 if Concatenate(args) else 1) + sys.exit(0 if Concatenate(args) else 1) if __name__ == "__main__": diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py index be8985ce..7d56c9f9 100755 --- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -20,6 +20,7 @@ from __future__ import print_function import optparse import re +import sys _MOJO_INTERNAL_MODULE_NAME = "mojo.internal" @@ -31,10 +32,10 @@ def FilterLine(filename, line, output): return if line.startswith("goog.provide"): - match = re.match("goog.provide\('([^']+)'\);", line) + match = re.match(r"goog.provide\('([^']+)'\);", line) if not match: print("Invalid goog.provide line in %s:\n%s" % (filename, line)) - exit(1) + sys.exit(1) module_name = match.group(1) if module_name == _MOJO_INTERNAL_MODULE_NAME: @@ -67,7 +68,8 @@ def main(): Concatenate several files into one, stripping Closure provide and require directives along the way.""") (_, args) = parser.parse_args() - exit(0 if ConcatenateAndReplaceExports(args) else 1) + sys.exit(0 if ConcatenateAndReplaceExports(args) else 1) + if __name__ == "__main__": main() diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py index 8b78d092..c6daff03 100644 --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py @@ -1,4 +1,4 @@ -# Copyright 2017 The Chromium Authors. All rights reserved. +# Copyright 2017 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a list of all files in a directory. diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py index a0096649..4a53e2bf 100755 --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2016 The Chromium Authors. All rights reserved. +# Copyright 2016 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a JSON typemap from its command-line arguments and dependencies. @@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path): for entry in config['types']: configs[entry['mojom']] = { 'typename': entry['cpp'], + 'forward_declaration': entry.get('forward_declaration', None), 'public_headers': config.get('traits_headers', []), 'traits_headers': config.get('traits_private_headers', []), 'copyable_pass_by_value': entry.get('copyable_pass_by_value', False), + 'default_constructible': entry.get('default_constructible', True), 'force_serialize': entry.get('force_serialize', False), 'hashable': entry.get('hashable', False), 'move_only': entry.get('move_only', False), diff --git a/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py new file mode 100755 index 00000000..cefee7a4 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This utility minifies JS files with terser. +# +# Instance of 'node' has no 'RunNode' member (no-member) +# pylint: disable=no-member + +import argparse +import os +import sys + +_HERE_PATH = os.path.dirname(__file__) +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..')) +_CWD = os.getcwd() +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node')) +import node +import node_modules + + +def MinifyFile(input_file, output_file): + node.RunNode([ + node_modules.PathToTerser(), input_file, '--mangle', '--compress', + '--comments', 'false', '--output', output_file + ]) + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--input', required=True) + parser.add_argument('--output', required=True) + args = parser.parse_args(argv) + + # Delete the output file if it already exists. It may be a sym link to the + # input, because in non-optimized/pre-Terser builds the input file is copied + # to the output location with gn copy(). + out_path = os.path.join(_CWD, args.output) + if (os.path.exists(out_path)): + os.remove(out_path) + + MinifyFile(os.path.join(_CWD, args.input), out_path) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni index fe2a1da3..3f6e54e0 100644 --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni @@ -1,25 +1,28 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/python.gni") import("//third_party/closure_compiler/closure_args.gni") import("//third_party/closure_compiler/compile_js.gni") import("//third_party/protobuf/proto_library.gni") +import("//ui/webui/resources/tools/generate_grd.gni") import("//ui/webui/webui_features.gni") +import("//build/config/cast.gni") + # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're # used to conditionally enable message ID scrambling in a way which is # consistent across toolchains and which is affected by branded vs non-branded # Chrome builds. Ideally we could create some generic knobs here that could be # flipped elsewhere though. import("//build/config/chrome_build.gni") -import("//build/config/chromecast_build.gni") import("//build/config/chromeos/ui_mode.gni") +import("//build/config/features.gni") import("//build/config/nacl/config.gni") import("//build/toolchain/kythe.gni") import("//components/nacl/features.gni") import("//third_party/jinja2/jinja2.gni") +import("//third_party/ply/ply.gni") import("//tools/ipc_fuzzer/ipc_fuzzer.gni") declare_args() { # Indicates whether typemapping should be supported in this build @@ -34,21 +37,30 @@ declare_args() { # Controls message ID scrambling behavior. If |true|, message IDs are # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on - # non-Chrome OS desktop platforms. Set to |false| to disable message ID - # scrambling on all platforms. - enable_mojom_message_id_scrambling = true + # non-Chrome OS desktop platforms. Enabled on official builds by default. + # Set to |true| to enable message ID scrambling on a specific build. + # See also `enable_scrambled_message_ids` below for more details. + enable_mojom_message_id_scrambling = is_official_build + + # Enables generating javascript fuzzing-related code and the bindings for the + # MojoLPM fuzzer targets. Off by default. + enable_mojom_fuzzer = false # Enables Closure compilation of generated JS lite bindings. In environments # where compilation is supported, any mojom target "foo" will also have a # corresponding "foo_js_library_for_compile" target generated. - enable_mojom_closure_compile = enable_js_type_check && optimize_webui - - # Enables generating Typescript bindings and compiling them to JS bindings. - enable_typescript_bindings = false + if (is_chromeos_ash) { + enable_mojom_closure_compile = enable_js_type_check && optimize_webui + } +} - # Enables generating javascript fuzzing-related code and the bindings for the - # MojoLPM fuzzer targets. Off by default. - enable_mojom_fuzzer = false +# Closure libraries are needed for mojom_closure_compile, and when +# js_type_check is enabled on Ash. +if (is_chromeos_ash) { + generate_mojom_closure_libraries = + enable_mojom_closure_compile || enable_js_type_check +} else { + generate_mojom_closure_libraries = false } # NOTE: We would like to avoid scrambling message IDs where it doesn't add @@ -69,9 +81,8 @@ declare_args() { # lacros-chrome switches to target_os="chromeos" enable_scrambled_message_ids = enable_mojom_message_id_scrambling && - (is_mac || is_win || - (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) || - ((enable_nacl || is_nacl || is_nacl_nonsfi) && + (is_mac || is_win || (is_linux && !is_castos) || + ((enable_nacl || is_nacl) && (target_os != "chromeos" && !chromeos_is_browser_only))) _mojom_tools_root = "//mojo/public/tools" @@ -80,7 +91,9 @@ mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py" mojom_parser_sources = [ "$_mojom_library_root/__init__.py", "$_mojom_library_root/error.py", + "$_mojom_library_root/fileutil.py", "$_mojom_library_root/generate/__init__.py", + "$_mojom_library_root/generate/check.py", "$_mojom_library_root/generate/generator.py", "$_mojom_library_root/generate/module.py", "$_mojom_library_root/generate/pack.py", @@ -88,21 +101,32 @@ mojom_parser_sources = [ "$_mojom_library_root/generate/translate.py", "$_mojom_library_root/parse/__init__.py", "$_mojom_library_root/parse/ast.py", + "$_mojom_library_root/parse/conditional_features.py", "$_mojom_library_root/parse/lexer.py", "$_mojom_library_root/parse/parser.py", + "//tools/diagnosis/crbug_1001171.py", ] mojom_generator_root = "$_mojom_tools_root/bindings" mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py" mojom_generator_sources = mojom_parser_sources + [ + "$mojom_generator_root/checks/__init__.py", + "$mojom_generator_root/checks/mojom_attributes_check.py", + "$mojom_generator_root/checks/mojom_definitions_check.py", + "$mojom_generator_root/checks/mojom_interface_feature_check.py", + "$mojom_generator_root/checks/mojom_restrictions_check.py", + "$mojom_generator_root/generators/__init__.py", "$mojom_generator_root/generators/cpp_util.py", "$mojom_generator_root/generators/mojom_cpp_generator.py", "$mojom_generator_root/generators/mojom_java_generator.py", - "$mojom_generator_root/generators/mojom_mojolpm_generator.py", "$mojom_generator_root/generators/mojom_js_generator.py", + "$mojom_generator_root/generators/mojom_mojolpm_generator.py", "$mojom_generator_root/generators/mojom_ts_generator.py", "$mojom_generator_script", + "//build/action_helpers.py", + "//build/gn_helpers.py", + "//build/zip_helpers.py", ] if (enable_scrambled_message_ids) { @@ -243,12 +267,16 @@ if (enable_scrambled_message_ids) { # |cpp_only| is set to true, it overrides this to prevent generation of # Java bindings. # -# enable_fuzzing (optional) +# enable_js_fuzzing (optional) +# Enables generation of javascript fuzzing sources for the target if the +# global build arg |enable_mojom_fuzzer| is also set to |true|. +# Defaults to |true|. If JS fuzzing generation is enabled for a target, +# the target will always generate JS bindings even if |cpp_only| is set to +# |true|. See note above. +# +# enable_mojolpm_fuzzing (optional) # Enables generation of fuzzing sources for the target if the global build -# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If -# fuzzing generation is enabled for a target, the target will always -# generate JS bindings even if |cpp_only| is set to |true|. See note -# above. +# arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. # # support_lazy_serialization (optional) # If set to |true|, generated C++ bindings will effectively prefer to @@ -310,8 +338,15 @@ if (enable_scrambled_message_ids) { # correct dependency order. Note that this only has an effect if # the |enable_mojom_closure_compile| global arg is set to |true| as well. # -# use_typescript_sources (optional) -# Uses the Typescript generator to generate JavaScript bindings. +# generate_webui_js_bindings (optional) +# Generate WebUI bindings in JavaScript rather than TypeScript. Defaults +# to false. ChromeOS only parameter. +# +# generate_legacy_js_bindings (optional) +# Generate js_data_deps target containing legacy JavaScript bindings files +# for Blink tests and other non-WebUI users when generating TypeScript +# bindings for WebUI. Ignored if generate_webui_js_bindings is set to +# true. # # js_generate_struct_deserializers (optional) # Generates JS deerialize methods for structs. @@ -323,17 +358,23 @@ if (enable_scrambled_message_ids) { # webui_module_path (optional) # The path or URL at which modules generated by this target will be # accessible to WebUI pages. This may either be an absolute path or -# a full URL path starting with "chrome://resources/mojo". +# a full URL path starting with "chrome://resources/mojo". If this path +# is not specified, WebUI bindings will not be generated. # # If an absolute path, a WebUI page may only import these modules if -# they are manually packaged and mapped independently by that page's -# WebUIDataSource. The mapped path must match the path given here. +# they are added to that page's data source (usually by adding the +# modules to the mojo_files list for build_webui(), or by listing the +# files as inputs to the page's ts_library() and/or generate_grd() build +# steps. # # If this is is instead a URL string starting with -# "chrome://resources/mojo", the generated resources must be added to -# content_resources.grd and registered with -# content::SharedResourcesDataSource with a corresponding path, at which -# point they will be made available to all WebUI pages at the given URL. +# "chrome://resources/mojo", the resulting bindings files should +# be added to one of the lists in ui/webui/resources/mojo/BUILD.gn, +# at which point they will be made available to all WebUI pages at the +# given URL. +# +# Note: WebUI module bindings are generated in TypeScript by default, +# unless |generate_webui_js_bindings| is specified as true. # # The following parameters are used to support the component build. They are # needed so that bindings which are linked with a component can use the same @@ -402,16 +443,41 @@ if (enable_scrambled_message_ids) { # should be mapped in generated bindings. This is a string like # "::base::Value" or "std::vector<::base::Value>". # -# move_only (optional) -# A boolean value (default false) which indicates whether the C++ -# type is move-only. If true, generated bindings will pass the type -# by value and use std::move() at call sites. -# # copyable_pass_by_value (optional) # A boolean value (default false) which effectively indicates # whether the C++ type is very cheap to copy. If so, generated # bindings will pass by value but not use std::move() at call sites. # +# default_constructible (optional) +# A boolean value (default true) which indicates whether the C++ +# type is default constructible. If a C++ type is not default +# constructible (e.g. the implementor of the type prefers not to +# publicly expose a default constructor that creates an object in an +# invalid state), Mojo will instead construct C++ type with an +# argument of the type `mojo::DefaultConstruct::Tag` (essentially a +# passkey-like type specifically for this use case). +# +# force_serialize (optional) +# A boolean value (default false) which disables lazy serialization +# of the typemapped type if lazy serialization is enabled for the +# mojom target applying this typemap. +# +# forward_declaration (optional) +# A forward declaration of the C++ type, which bindings that don't +# need the full type definition can use to reduce the size of +# the generated code. This is a string like +# "namespace base { class Value; }". +# +# hashable (optional) +# A boolean value (default false) indicating whether the C++ type is +# hashable. Set to true if true AND needed (i.e. you need to use the +# type as the key of a mojom map). +# +# move_only (optional) +# A boolean value (default false) which indicates whether the C++ +# type is move-only. If true, generated bindings will pass the type +# by value and use std::move() at call sites. +# # nullable_is_same_type (optional) # A boolean value (default false) which indicates that the C++ type # has some baked-in semantic notion of a "null" state. If true, the @@ -421,16 +487,6 @@ if (enable_scrambled_message_ids) { # type with absl::optional, and null values are simply # absl::nullopt. # -# hashable (optional) -# A boolean value (default false) indicating whether the C++ type is -# hashable. Set to true if true AND needed (i.e. you need to use the -# type as the key of a mojom map). -# -# force_serialize (optional) -# A boolean value (default false) which disables lazy serialization -# of the typemapped type if lazy serialization is enabled for the -# mojom target applying this typemap. -# # Additional typemap scope parameters: # # traits_headers (optional) @@ -621,20 +677,26 @@ template("mojom") { build_metadata_filename = "$target_gen_dir/$target_name.build_metadata" build_metadata = { } - build_metadata.sources = rebase_path(sources_list) + build_metadata.sources = rebase_path(sources_list, target_gen_dir) build_metadata.deps = [] foreach(dep, all_deps) { dep_target_gen_dir = get_label_info(dep, "target_gen_dir") dep_name = get_label_info(dep, "name") build_metadata.deps += - [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ] + [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata", + target_gen_dir) ] } write_file(build_metadata_filename, build_metadata, "json") - generate_fuzzing = - (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) && + generate_js_fuzzing = + (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) && enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly) + generate_mojolpm_fuzzing = + (!defined(invoker.enable_mojolpm_fuzzing) || + invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer && + (!defined(invoker.testonly) || !invoker.testonly) + parser_target_name = "${target_name}__parser" parser_deps = [] foreach(dep, all_deps) { @@ -665,30 +727,34 @@ template("mojom") { "is_chromeos", "is_chromeos_ash", ] + } else if (is_chromeos_lacros) { + enabled_features += [ + "is_chromeos", + "is_chromeos_lacros", + ] } else if (is_fuchsia) { enabled_features += [ "is_fuchsia" ] } else if (is_ios) { enabled_features += [ "is_ios" ] - } else if (is_linux || is_chromeos_lacros) { + } else if (is_linux) { enabled_features += [ "is_linux" ] - if (is_chromeos_lacros) { - enabled_features += [ - "is_chromeos", - "is_chromeos_lacros", - ] - } } else if (is_mac) { enabled_features += [ "is_mac" ] } else if (is_win) { enabled_features += [ "is_win" ] } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(parser_target_name) { + if (is_apple) { + enabled_features += [ "is_apple" ] + } + + action(parser_target_name) { + allow_remote = true + custom_processor = "mojom_parser" script = mojom_parser_script - inputs = mojom_parser_sources + [ build_metadata_filename ] + inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ] sources = sources_list - deps = parser_deps + public_deps = parser_deps outputs = [] foreach(base_path, output_file_base_paths) { filename = get_path_info(base_path, "file") @@ -698,31 +764,35 @@ template("mojom") { filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path(source) ] + filelist += [ rebase_path(source, root_build_dir) ] } - response_file_contents = filelist + + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args = [ # Resolve relative input mojom paths against both the root src dir and # the root gen dir. "--input-root", - rebase_path("//."), + rebase_path("//.", root_build_dir), "--input-root", - rebase_path(root_gen_dir), + rebase_path(root_gen_dir, root_build_dir), "--output-root", - rebase_path(root_gen_dir), + rebase_path(root_gen_dir, root_build_dir), - "--mojom-file-list={{response_file_name}}", + "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir), "--check-imports", - rebase_path(build_metadata_filename), + rebase_path(build_metadata_filename, root_build_dir), ] if (defined(invoker.input_root_override)) { args += [ "--input-root", - rebase_path(invoker.input_root_override), + rebase_path(invoker.input_root_override, root_build_dir), ] } @@ -738,6 +808,13 @@ template("mojom") { "--add-module-metadata", "webui_module_path=${invoker.webui_module_path}", ] + if (defined(invoker.generate_webui_js_bindings) && + invoker.generate_webui_js_bindings) { + args += [ + "--add-module-metadata", + "generate_webui_js=True", + ] + } } } } @@ -819,11 +896,12 @@ template("mojom") { } } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_cpp_message_ids_target_name) { + action(generator_cpp_message_ids_target_name) { + allow_remote = true script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -835,16 +913,22 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ] } - response_file_contents = filelist + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "--generate_message_ids", "-g", @@ -860,12 +944,13 @@ template("mojom") { generator_shared_target_name = "${target_name}_shared__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_shared_target_name) { + action(generator_shared_target_name) { + allow_remote = true visibility = [ ":*" ] script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = sources_list + + [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -878,10 +963,16 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + # Need the mojom-module as an input to this action. + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] + outputs += [ + "$root_gen_dir/$base_path-features.h", "$root_gen_dir/$base_path-params-data.h", "$root_gen_dir/$base_path-shared-internal.h", "$root_gen_dir/$base_path-shared.cc", @@ -889,10 +980,13 @@ template("mojom") { ] } - response_file_contents = filelist + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "-g", "c++", @@ -923,12 +1017,14 @@ template("mojom") { if (defined(invoker.testonly)) { testonly = invoker.testonly } + configs += [ "//build/config/compiler:wexit_time_destructors" ] deps = [] public_deps = [] if (output_file_base_paths != []) { sources = [] foreach(base_path, output_file_base_paths) { sources += [ + "$root_gen_dir/$base_path-features.h", "$root_gen_dir/$base_path-params-data.h", "$root_gen_dir/$base_path-shared-internal.h", "$root_gen_dir/$base_path-shared.cc", @@ -972,7 +1068,7 @@ template("mojom") { } } - if (generate_fuzzing) { + if (generate_mojolpm_fuzzing) { # This block generates the proto files used for the MojoLPM fuzzer, # and the corresponding proto targets that will be linked in the fuzzer # targets. These are independent of the typemappings, and can be done @@ -981,11 +1077,15 @@ template("mojom") { generator_mojolpm_proto_target_name = "${target_name}_mojolpm_proto_generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_mojolpm_proto_target_name) { + action(generator_mojolpm_proto_target_name) { + allow_remote = true script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = invoker.sources + sources = + invoker.sources + [ + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip", + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip", + ] deps = [ ":$parser_target_name", "//mojo/public/tools/bindings:precompile_templates", @@ -994,15 +1094,37 @@ template("mojom") { outputs = [] args = common_generator_args filelist = [] - foreach(source, invoker.sources) { - filelist += [ rebase_path("$source", root_build_dir) ] + + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ]) + + foreach(source, non_gen_sources) { + filelist += [ rebase_path(source, root_build_dir) ] + inputs += [ "$target_gen_dir/$source-module" ] outputs += [ "$target_gen_dir/$source.mojolpm.proto" ] } - response_file_contents = filelist + foreach(source, gen_sources) { + filelist += [ rebase_path(source, root_build_dir) ] + + # For generated files, we assume they're in the target_gen_dir or a + # sub-folder of it. Rebase the path so we can get the relative location. + source_file = rebase_path(source, target_gen_dir) + inputs += [ "$target_gen_dir/$source_file-module" ] + outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ] + } + + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path(rsp_file, root_build_dir), "--generate_non_variant_code", "-g", "mojolpm", @@ -1014,9 +1136,20 @@ template("mojom") { proto_library(mojolpm_proto_target_name) { testonly = true generate_python = false + + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ]) sources = process_file_template( - invoker.sources, + non_gen_sources, [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ]) + sources += process_file_template( + gen_sources, + [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ]) + import_dirs = [ "//" ] proto_in_dir = "${root_gen_dir}" proto_out_dir = "." @@ -1055,7 +1188,7 @@ template("mojom") { component_macro_suffix = "" } if ((!defined(invoker.disable_variants) || !invoker.disable_variants) && - !is_ios) { + use_blink) { blink_variant = { variant = "blink" component_macro_suffix = "_BLINK" @@ -1149,39 +1282,6 @@ template("mojom") { "${bindings_configuration.component_macro_suffix}_IMPL" ] } - export_args = [] - export_args_overridden = false - if (defined(bindings_configuration.for_blink) && - bindings_configuration.for_blink) { - if (defined(invoker.export_class_attribute_blink)) { - export_args_overridden = true - export_args += [ - "--export_attribute", - invoker.export_class_attribute_blink, - "--export_header", - invoker.export_header_blink, - ] - } - } else if (defined(invoker.export_class_attribute)) { - export_args_overridden = true - export_args += [ - "--export_attribute", - invoker.export_class_attribute, - "--export_header", - invoker.export_header, - ] - } - - if (!export_args_overridden && defined(invoker.component_macro_prefix)) { - export_args += [ - "--export_attribute", - "COMPONENT_EXPORT(${invoker.component_macro_prefix}" + - "${bindings_configuration.component_macro_suffix})", - "--export_header", - "base/component_export.h", - ] - } - generate_java = false if (!cpp_only && defined(invoker.generate_java)) { generate_java = invoker.generate_java @@ -1190,6 +1290,38 @@ template("mojom") { type_mappings_path = "$target_gen_dir/${target_name}${variant_suffix}__type_mappings" if (sources_list != []) { + export_args = [] + export_args_overridden = false + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.export_class_attribute_blink)) { + export_args_overridden = true + export_args += [ + "--export_attribute", + invoker.export_class_attribute_blink, + "--export_header", + invoker.export_header_blink, + ] + } + } else if (defined(invoker.export_class_attribute)) { + export_args_overridden = true + export_args += [ + "--export_attribute", + invoker.export_class_attribute, + "--export_header", + invoker.export_header, + ] + } + if (!export_args_overridden && defined(invoker.component_macro_prefix)) { + export_args += [ + "--export_attribute", + "COMPONENT_EXPORT(${invoker.component_macro_prefix}" + + "${bindings_configuration.component_macro_suffix})", + "--export_header", + "base/component_export.h", + ] + } + generator_cpp_output_suffixes = [] variant_dash_suffix = "" if (defined(variant)) { @@ -1198,7 +1330,6 @@ template("mojom") { generator_cpp_output_suffixes += [ "${variant_dash_suffix}-forward.h", "${variant_dash_suffix}-import-headers.h", - "${variant_dash_suffix}-test-utils.cc", "${variant_dash_suffix}-test-utils.h", "${variant_dash_suffix}.cc", "${variant_dash_suffix}.h", @@ -1207,16 +1338,28 @@ template("mojom") { generator_target_name = "${target_name}${variant_suffix}__generator" # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_target_name) { + action(generator_target_name) { + allow_remote = true visibility = [ ":*" ] script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources - sources = sources_list + sources = + sources_list + [ + "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip", + type_mappings_path, + ] + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { + sources += [ + "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip", + ] + } deps = [ ":$parser_target_name", ":$type_mappings_target_name", "//mojo/public/tools/bindings:precompile_templates", ] + if (defined(invoker.parser_deps)) { deps += invoker.parser_deps } @@ -1224,18 +1367,22 @@ template("mojom") { args = common_generator_args + export_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { + filename = get_path_info(base_path, "file") + dirname = get_path_info(base_path, "dir") + inputs += [ "$root_gen_dir/$dirname/${filename}-module" ] + outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h", "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h", - "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h", "$root_gen_dir/${base_path}${variant_dash_suffix}.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}.h", ] - if (generate_fuzzing && !defined(bindings_configuration.variant)) { + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { outputs += [ "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc", "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h", @@ -1243,14 +1390,17 @@ template("mojom") { } } - response_file_contents = filelist - + # Workaround for https://github.com/ninja-build/ninja/issues/1966. + rsp_file = "$target_gen_dir/${target_name}.rsp" + write_file(rsp_file, filelist) + inputs += [ rsp_file ] args += [ - "--filelist={{response_file_name}}", + "--filelist=" + rebase_path("$rsp_file", root_build_dir), "-g", ] - if (generate_fuzzing && !defined(bindings_configuration.variant)) { + if (generate_mojolpm_fuzzing && + !defined(bindings_configuration.variant)) { args += [ "c++,mojolpm" ] } else { args += [ "c++" ] @@ -1294,6 +1444,8 @@ template("mojom") { "--extra_cpp_template_paths", rebase_path(extra_cpp_template, root_build_dir), ] + inputs += [ extra_cpp_template ] + assert( get_path_info(extra_cpp_template, "extension") == "tmpl", "--extra_cpp_template_paths only accepts template files ending in extension .tmpl") @@ -1306,62 +1458,6 @@ template("mojom") { } } - if (generate_fuzzing && !defined(variant)) { - # This block contains the C++ targets for the MojoLPM fuzzer, we need to - # do this here so that we can use the typemap configuration for the - # empty-variant Mojo target. - - mojolpm_target_name = "${target_name}_mojolpm" - mojolpm_generator_target_name = "${target_name}__generator" - source_set(mojolpm_target_name) { - # There are still a few missing header dependencies between mojo targets - # with typemaps and the dependencies of their typemap headers. It would - # be good to enable include checking for these in the future though. - check_includes = false - testonly = true - if (defined(invoker.sources)) { - sources = process_file_template( - invoker.sources, - [ - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc", - "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h", - ]) - deps = [] - } else { - sources = [] - deps = [] - } - - public_deps = [ - ":$generator_shared_target_name", - - # NB: hardcoded dependency on the no-variant variant generator, since - # mojolpm only uses the no-variant type. - ":$mojolpm_generator_target_name", - ":$mojolpm_proto_target_name", - "//base", - "//mojo/public/tools/fuzzers:mojolpm", - ] - - foreach(d, all_deps) { - # Resolve the name, so that a target //mojo/something becomes - # //mojo/something:something and we can append variant_suffix to - # get the cpp dependency name. - full_name = get_label_info("$d", "label_no_toolchain") - public_deps += [ "${full_name}_mojolpm" ] - } - - foreach(config, cpp_typemap_configs) { - if (defined(config.traits_deps)) { - deps += config.traits_deps - } - if (defined(config.traits_public_deps)) { - public_deps += config.traits_public_deps - } - } - } - } - # Write the typemapping configuration for this target out to a file to be # validated by a Python script. This helps catch mistakes that can't # be caught by logic in GN. @@ -1389,20 +1485,20 @@ template("mojom") { write_file(_typemap_config_filename, _rebased_typemap_configs, "json") _mojom_target_name = target_name - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(_typemap_validator_target_name) { + action(_typemap_validator_target_name) { + allow_remote = true script = "$mojom_generator_root/validate_typemap_config.py" inputs = [ _typemap_config_filename ] outputs = [ _typemap_stamp_filename ] args = [ get_label_info(_mojom_target_name, "label_no_toolchain"), - rebase_path(_typemap_config_filename), - rebase_path(_typemap_stamp_filename), + rebase_path(_typemap_config_filename, root_build_dir), + rebase_path(_typemap_stamp_filename, root_build_dir), ] } - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(type_mappings_target_name) { + action(type_mappings_target_name) { + allow_remote = true inputs = mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ] outputs = [ type_mappings_path ] @@ -1413,6 +1509,7 @@ template("mojom") { rebase_path(type_mappings_path, root_build_dir), ] + sources = [] foreach(d, all_deps) { name = get_label_info(d, "label_no_toolchain") toolchain = get_label_info(d, "toolchain") @@ -1422,12 +1519,11 @@ template("mojom") { dependency_output_dir = get_label_info(dependency_output, "target_gen_dir") dependency_name = get_label_info(dependency_output, "name") - dependency_path = - rebase_path("$dependency_output_dir/${dependency_name}", - root_build_dir) + dependency_path = "$dependency_output_dir/${dependency_name}" + sources += [ dependency_path ] args += [ "--dependency", - dependency_path, + rebase_path(dependency_path, root_build_dir), ] } @@ -1485,11 +1581,15 @@ template("mojom") { if (defined(output_name_override)) { output_name = output_name_override } - visibility = output_visibility + [ ":$output_target_name" ] + visibility = output_visibility + [ + ":$output_target_name", + ":${target_name}_mojolpm", + ] if (defined(invoker.testonly)) { testonly = invoker.testonly } defines = export_defines + configs += [ "//build/config/compiler:wexit_time_destructors" ] configs += extra_configs if (output_file_base_paths != []) { sources = [] @@ -1578,13 +1678,81 @@ template("mojom") { } } + if (generate_mojolpm_fuzzing && !defined(variant)) { + # This block contains the C++ targets for the MojoLPM fuzzer, we need to + # do this here so that we can use the typemap configuration for the + # empty-variant Mojo target. + + mojolpm_target_name = "${target_name}_mojolpm" + mojolpm_generator_target_name = "${target_name}__generator" + source_set(mojolpm_target_name) { + # There are still a few missing header dependencies between mojo targets + # with typemaps and the dependencies of their typemap headers. It would + # be good to enable include checking for these in the future though. + check_includes = false + testonly = true + if (defined(invoker.sources)) { + # Split the input into generated and non-generated source files. They + # need to be processed separately. + gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*" + non_gen_sources = + filter_exclude(invoker.sources, [ gen_dir_path_wildcard ]) + gen_sources = + filter_include(invoker.sources, [ gen_dir_path_wildcard ]) + sources = process_file_template( + non_gen_sources, + [ + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc", + "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h", + ]) + sources += process_file_template( + gen_sources, + [ + "{{source_dir}}/{{source_file_part}}-mojolpm.cc", + "{{source_dir}}/{{source_file_part}}-mojolpm.h", + ]) + deps = [ ":$output_target_name" ] + } else { + sources = [] + deps = [] + } + + public_deps = [ + ":$generator_shared_target_name", + + # NB: hardcoded dependency on the no-variant variant generator, since + # mojolpm only uses the no-variant type. + ":$mojolpm_generator_target_name", + ":$mojolpm_proto_target_name", + "//base", + "//mojo/public/tools/fuzzers:mojolpm", + ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix to + # get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_mojolpm" ] + } + + foreach(config, cpp_typemap_configs) { + if (defined(config.traits_deps)) { + deps += config.traits_deps + } + if (defined(config.traits_public_deps)) { + public_deps += config.traits_public_deps + } + } + } + } + if (generate_java && is_android) { import("//build/config/android/rules.gni") java_generator_target_name = target_name + "_java__generator" if (sources_list != []) { - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(java_generator_target_name) { + action(java_generator_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1597,7 +1765,7 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { outputs += [ "$root_gen_dir/$base_path.srcjar" ] @@ -1624,8 +1792,7 @@ template("mojom") { java_srcjar_target_name = target_name + "_java_sources" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(java_srcjar_target_name) { + action(java_srcjar_target_name) { script = "//build/android/gyp/zip.py" inputs = [] if (output_file_base_paths != []) { @@ -1651,7 +1818,6 @@ template("mojom") { android_library(java_target_name) { forward_variables_from(invoker, [ "enable_bytecode_checks" ]) deps = [ - "//base:base_java", "//mojo/public/java:bindings_java", "//mojo/public/java:system_java", "//third_party/androidx:androidx_annotation_annotation_java", @@ -1673,21 +1839,36 @@ template("mojom") { } } - use_typescript_for_target = - enable_typescript_bindings && defined(invoker.use_typescript_sources) && - invoker.use_typescript_sources + if (defined(invoker.generate_webui_js_bindings)) { + assert(is_chromeos_ash, + "generate_webui_js_bindings can only be used on ChromeOS Ash") + assert(invoker.generate_webui_js_bindings, + "generate_webui_js_bindings should be set to true or removed") + } + + use_typescript_for_target = defined(invoker.webui_module_path) && + !defined(invoker.generate_webui_js_bindings) - if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) { - not_needed(invoker, [ "use_typescript_sources" ]) + generate_legacy_js = !use_typescript_for_target || + (defined(invoker.generate_legacy_js_bindings) && + invoker.generate_legacy_js_bindings) + + if (!use_typescript_for_target && + defined(invoker.generate_legacy_js_bindings)) { + not_needed(invoker, [ "generate_legacy_js_bindings" ]) } - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) && - !use_typescript_for_target) { + # Targets needed by both TS and JS bindings targets. These are needed + # unconditionally for JS bindings targets, and are needed for TS bindings + # targets when generate_legacy_js_bindings is true. This option is provided + # since the legacy bindings are needed by Blink tests and non-Chromium users, + # which are not expected to migrate to modules or TypeScript. + if (generate_legacy_js && (generate_js_fuzzing || + !defined(invoker.cpp_only) || !invoker.cpp_only)) { if (sources_list != []) { generator_js_target_name = "${target_name}_js__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_js_target_name) { + action(generator_js_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1702,19 +1883,18 @@ template("mojom") { args = common_generator_args filelist = [] foreach(source, sources_list) { - filelist += [ rebase_path("$source", root_build_dir) ] + filelist += [ rebase_path(source, root_build_dir) ] } foreach(base_path, output_file_base_paths) { outputs += [ "$root_gen_dir/$base_path.js", - "$root_gen_dir/$base_path.externs.js", "$root_gen_dir/$base_path.m.js", "$root_gen_dir/$base_path-lite.js", - "$root_gen_dir/$base_path.html", "$root_gen_dir/$base_path-lite-for-compile.js", ] - if (defined(invoker.webui_module_path)) { + if (defined(invoker.webui_module_path) && + !use_typescript_for_target) { outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ] } } @@ -1725,7 +1905,6 @@ template("mojom") { "--filelist={{response_file_name}}", "-g", "javascript", - "--js_bindings_mode=new", ] if (defined(invoker.js_generate_struct_deserializers) && @@ -1739,7 +1918,7 @@ template("mojom") { args += message_scrambling_args } - if (generate_fuzzing) { + if (generate_js_fuzzing) { args += [ "--generate_fuzzing" ] } } @@ -1783,31 +1962,13 @@ template("mojom") { data_deps += [ "${full_name}_js_data_deps" ] } } + } - js_library_target_name = "${target_name}_js_library" - if (sources_list != []) { - js_library(js_library_target_name) { - extra_public_deps = [ ":$generator_js_target_name" ] - sources = [] - foreach(base_path, output_file_base_paths) { - sources += [ "$root_gen_dir/${base_path}-lite.js" ] - } - externs_list = [ - "${externs_path}/mojo_core.js", - "${externs_path}/pending.js", - ] - - deps = [] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - deps += [ "${full_name}_js_library" ] - } - } - } else { - group(js_library_target_name) { - } - } - + # js_library() closure compiler targets, primarily used on ChromeOS. Only + # generate these targets if the mojom target is not C++ only and is not using + # TypeScript. + if (generate_mojom_closure_libraries && + (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) { js_library_for_compile_target_name = "${target_name}_js_library_for_compile" if (sources_list != []) { js_library(js_library_for_compile_target_name) { @@ -1834,35 +1995,9 @@ template("mojom") { } } - js_modules_target_name = "${target_name}_js_modules" - if (sources_list != []) { - js_library(js_modules_target_name) { - extra_public_deps = [ ":$generator_js_target_name" ] - sources = [] - foreach(base_path, output_file_base_paths) { - sources += [ "$root_gen_dir/${base_path}.m.js" ] - } - externs_list = [ - "${externs_path}/mojo_core.js", - "${externs_path}/pending.js", - ] - if (defined(invoker.disallow_native_types) && - invoker.disallow_native_types) { - deps = [] - } else { - deps = [ "//mojo/public/js:bindings_uncompiled" ] - } - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - deps += [ "${full_name}_js_modules" ] - } - } - } else { - group(js_modules_target_name) { - } - } - - if (defined(invoker.webui_module_path)) { + # WebUI specific closure targets, not needed by targets that are generating + # TypeScript WebUI bindings or by legacy-only targets. + if (defined(invoker.webui_module_path) && !use_typescript_for_target) { webui_js_target_name = "${target_name}_webui_js" if (sources_list != []) { js_library(webui_js_target_name) { @@ -1890,46 +2025,38 @@ template("mojom") { group(webui_js_target_name) { } } - } - } - if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) && - use_typescript_for_target) { - generator_js_target_names = [] - source_filelist = [] - foreach(source, sources_list) { - source_filelist += [ rebase_path("$source", root_build_dir) ] - } - dependency_types = [ - { - name = "regular" - ts_extension = ".ts" - js_extension = ".js" - }, - { - name = "es_modules" - ts_extension = ".m.ts" - js_extension = ".m.js" - }, - ] + webui_grdp_target_name = "${target_name}_webui_grdp" + out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp" + grd_prefix = "${target_name}_webui" + generate_grd(webui_grdp_target_name) { + grd_prefix = grd_prefix + out_grd = out_grd - foreach(dependency_type, dependency_types) { - ts_outputs = [] - js_outputs = [] + deps = [ ":$webui_js_target_name" ] - foreach(base_path, output_file_base_paths) { - ts_outputs += - [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ] - js_outputs += - [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ] + input_files = [] + foreach(base_path, output_file_base_paths) { + input_files += [ "${base_path}-webui.js" ] + } + + input_files_base_dir = + rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir") + } + } + } + if ((generate_js_fuzzing || !defined(invoker.cpp_only) || + !invoker.cpp_only) && use_typescript_for_target) { + if (sources_list != []) { + source_filelist = [] + foreach(source, sources_list) { + source_filelist += [ rebase_path(source, root_build_dir) ] } # Generate Typescript bindings. - generator_ts_target_name = - "${target_name}_${dependency_type.name}__ts__generator" + generator_ts_target_name = "${target_name}_ts__generator" - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_ts_target_name) { + action(generator_ts_target_name) { script = mojom_generator_script inputs = mojom_generator_sources + jinja2_sources sources = sources_list @@ -1938,7 +2065,10 @@ template("mojom") { "//mojo/public/tools/bindings:precompile_templates", ] - outputs = ts_outputs + outputs = [] + foreach(base_path, output_file_base_paths) { + outputs += [ "$root_gen_dir/$base_path-webui.ts" ] + } args = common_generator_args response_file_contents = source_filelist @@ -1948,97 +2078,20 @@ template("mojom") { "typescript", ] - if (dependency_type.name == "es_modules") { - args += [ "--ts_use_es_modules" ] - } - - # TODO(crbug.com/1007587): Support scramble_message_ids. - # TODO(crbug.com/1007591): Support generate_fuzzing. - } - - # Create tsconfig.json for the generated Typescript. - tsconfig_filename = - "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json" - tsconfig = { - } - tsconfig.compilerOptions = { - composite = true - target = "es6" - module = "es6" - lib = [ - "es6", - "esnext.bigint", - ] - strict = true - } - tsconfig.files = [] - foreach(base_path, output_file_base_paths) { - tsconfig.files += [ rebase_path( - "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}", - target_gen_dir, - root_gen_dir) ] - } - tsconfig.references = [] - - # Get tsconfigs for deps. - foreach(d, all_deps) { - dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir")) - dep_name = get_label_info(d, "name") - reference = { - } - reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json" - tsconfig.references += [ reference ] - } - write_file(tsconfig_filename, tsconfig, "json") - - # Compile previously generated Typescript to Javascript. - generator_js_target_name = - "${target_name}_${dependency_type.name}__js__generator" - generator_js_target_names += [ generator_js_target_name ] - - # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds. - python2_action(generator_js_target_name) { - script = "$mojom_generator_root/compile_typescript.py" - sources = ts_outputs - outputs = js_outputs - public_deps = [ ":$generator_ts_target_name" ] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - public_deps += - [ "${full_name}_${dependency_type.name}__js__generator" ] + if (!defined(invoker.scramble_message_ids) || + invoker.scramble_message_ids) { + inputs += message_scrambling_inputs + args += message_scrambling_args } - absolute_tsconfig_path = - rebase_path(tsconfig_filename, "", target_gen_dir) - args = [ "--tsconfig_path=$absolute_tsconfig_path" ] - } - } - - js_target_name = target_name + "_js" - group(js_target_name) { - public_deps = [] - if (sources_list != []) { - foreach(generator_js_target_name, generator_js_target_names) { - public_deps += [ ":$generator_js_target_name" ] + if (defined(invoker.js_generate_struct_deserializers) && + invoker.js_generate_struct_deserializers) { + args += [ "--js_generate_struct_deserializers" ] } - } - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - public_deps += [ "${full_name}_js" ] - } - } - - group(js_data_deps_target_name) { - data = js_outputs - deps = [] - foreach(generator_js_target_name, generator_js_target_names) { - deps += [ ":$generator_js_target_name" ] - } - data_deps = [] - foreach(d, all_deps) { - full_name = get_label_info(d, "label_no_toolchain") - data_deps += [ "${full_name}_js_data_deps" ] + # TODO(crbug.com/1007587): Support scramble_message_ids if above is + # insufficient. + # TODO(crbug.com/1007591): Support generate_fuzzing. } } } diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py index da9efc71..8c641c2a 100755 --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = { "typescript": "mojom_ts_generator", } +_BUILTIN_CHECKS = { + "attributes": "mojom_attributes_check", + "definitions": "mojom_definitions_check", + "features": "mojom_interface_feature_check", + "restrictions": "mojom_restrictions_check", +} + def LoadGenerators(generators_string): if not generators_string: - return [] # No generators. + return {} # No generators. generators = {} for generator_name in [s.strip() for s in generators_string.split(",")]: @@ -74,6 +81,21 @@ def LoadGenerators(generators_string): return generators +def LoadChecks(checks_string): + if not checks_string: + return {} # No checks. + + checks = {} + for check_name in [s.strip() for s in checks_string.split(",")]: + check = check_name.lower() + if check not in _BUILTIN_CHECKS: + print("Unknown check name %s" % check_name) + sys.exit(1) + check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check]) + checks[check] = check_module + return checks + + def MakeImportStackMessage(imported_filename_stack): """Make a (human-readable) message listing a chain of imports. (Returned string begins with a newline (if nonempty) and does not end with one.)""" @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack): zip(imported_filename_stack[1:], imported_filename_stack)])) -class RelativePath(object): +class RelativePath: """Represents a path relative to the source tree or generated output dir.""" def __init__(self, path, source_root, output_dir): @@ -142,7 +164,7 @@ def ReadFileContents(filename): return f.read() -class MojomProcessor(object): +class MojomProcessor: """Takes parsed mojom modules and generates language bindings from them. Attributes: @@ -169,8 +191,8 @@ class MojomProcessor(object): if 'c++' in self._typemap: self._typemap['mojolpm'] = self._typemap['c++'] - def _GenerateModule(self, args, remaining_args, generator_modules, - rel_filename, imported_filename_stack): + def _GenerateModule(self, args, remaining_args, check_modules, + generator_modules, rel_filename, imported_filename_stack): # Return the already-generated module. if rel_filename.path in self._processed_files: return self._processed_files[rel_filename.path] @@ -190,12 +212,16 @@ class MojomProcessor(object): ScrambleMethodOrdinals(module.interfaces, salt) if self._should_generate(rel_filename.path): + # Run checks on module first. + for check_module in check_modules.values(): + checker = check_module.Check(module) + checker.CheckModule() + # Then run generation. for language, generator_module in generator_modules.items(): generator = generator_module.Generator( module, args.output_dir, typemap=self._typemap.get(language, {}), variant=args.variant, bytecode_path=args.bytecode_path, for_blink=args.for_blink, - js_bindings_mode=args.js_bindings_mode, js_generate_struct_deserializers=\ args.js_generate_struct_deserializers, export_attribute=args.export_attribute, @@ -234,6 +260,7 @@ def _Generate(args, remaining_args): args.import_directories[idx] = RelativePath(tokens[0], args.depth, args.output_dir) generator_modules = LoadGenerators(args.generators_string) + check_modules = LoadChecks(args.checks_string) fileutil.EnsureDirectoryExists(args.output_dir) @@ -246,7 +273,7 @@ def _Generate(args, remaining_args): for filename in args.filename: processor._GenerateModule( - args, remaining_args, generator_modules, + args, remaining_args, check_modules, generator_modules, RelativePath(filename, args.depth, args.output_dir), []) return 0 @@ -286,6 +313,12 @@ def main(): metavar="GENERATORS", default="c++,javascript,java,mojolpm", help="comma-separated list of generators") + generate_parser.add_argument("-c", + "--checks", + dest="checks_string", + metavar="CHECKS", + default=",".join(_BUILTIN_CHECKS.keys()), + help="comma-separated list of checks") generate_parser.add_argument( "--gen_dir", dest="gen_directories", action="append", metavar="directory", default=[], help="add a directory to be searched for the syntax trees.") @@ -309,11 +342,6 @@ def main(): help="Use WTF types as generated types for mojo " "string/array/map.") generate_parser.add_argument( - "--js_bindings_mode", choices=["new", "old"], default="old", - help="This option only affects the JavaScript bindings. The value could " - "be \"new\" to generate new-style lite JS bindings in addition to the " - "old, or \"old\" to only generate old bindings.") - generate_parser.add_argument( "--js_generate_struct_deserializers", action="store_true", help="Generate javascript deserialize methods for structs in " "mojom-lite.js file") @@ -387,4 +415,10 @@ def main(): if __name__ == "__main__": with crbug_1001171.DumpStateOnLookupError(): - sys.exit(main()) + ret = main() + # Exit without running GC, which can save multiple seconds due to the large + # number of object created. But flush is necessary as os._exit doesn't do + # that. + sys.stdout.flush() + sys.stderr.flush() + os._exit(ret) diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py index bddbe3f4..761922b6 100644 --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage from mojom_bindings_generator import ScrambleMethodOrdinals -class FakeIface(object): +class FakeIface: def __init__(self): self.mojom_name = None self.methods = None -class FakeMethod(object): +class FakeMethod: def __init__(self, explicit_ordinal=None): self.explicit_ordinal = explicit_ordinal self.ordinal = explicit_ordinal diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py index f1783d59..6bb7a209 100755 --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py +++ b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename): ]) _SUPPORTED_TYPE_KEYS = set([ 'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable', - 'move_only', 'nullable_is_same_type' + 'move_only', 'nullable_is_same_type', 'forward_declaration', + 'default_constructible' ]) with open(config_filename, 'r') as f: for config in json.load(f): diff --git a/utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn new file mode 100644 index 00000000..eafb95a1 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn @@ -0,0 +1,18 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +group("tests") { + data = [ + "check_stable_mojom_compatibility_unittest.py", + "check_stable_mojom_compatibility.py", + "const_unittest.py", + "enum_unittest.py", + "feature_unittest.py", + "mojom_parser_test_case.py", + "mojom_parser_unittest.py", + "mojom_parser.py", + "stable_attribute_unittest.py", + "version_compatibility_unittest.py", + ] +} diff --git a/utils/ipc/mojo/public/tools/mojom/README.md b/utils/codegen/ipc/mojo/public/tools/mojom/README.md index e5d17ab0..e5d17ab0 100644 --- a/utils/ipc/mojo/public/tools/mojom/README.md +++ b/utils/codegen/ipc/mojo/public/tools/mojom/README.md diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py index 08bd672f..35cd1cfd 100755 --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Verifies backward-compatibility of mojom type changes. @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making breaking changes to stable mojoms.""" import argparse -import errno import io import json import os import os.path -import shutil -import six import sys -import tempfile from mojom.generate import module from mojom.generate import translate from mojom.parse import parser +# pylint: disable=raise-missing-from + class ParseError(Exception): pass @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta): transitive closure of a mojom's input dependencies all at once. """ + translate.is_running_backwards_compatibility_check_hack = True + # First build a map of all files covered by the delta affected_files = set() old_files = {} @@ -73,11 +73,35 @@ def _ValidateDelta(root, delta): try: ast = parser.Parse(contents, mojom) except Exception as e: - six.reraise( - ParseError, - 'encountered exception {0} while parsing {1}'.format(e, mojom), - sys.exc_info()[2]) + raise ParseError('encountered exception {0} while parsing {1}'.format( + e, mojom)) + + # Files which are generated at compile time can't be checked by this script + # (at the moment) since they may not exist in the output directory. + generated_files_to_skip = { + ('third_party/blink/public/mojom/runtime_feature_state/' + 'runtime_feature.mojom'), + ('third_party/blink/public/mojom/origin_trial_feature/' + 'origin_trial_feature.mojom'), + } + + ast.import_list.items = [ + x for x in ast.import_list.items + if x.import_filename not in generated_files_to_skip + ] + for imp in ast.import_list: + if (not file_overrides.get(imp.import_filename) + and not os.path.exists(os.path.join(root, imp.import_filename))): + # Speculatively construct a path prefix to locate the import_filename + mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep) + test_prefix = '' + for path_component in mojom_path: + test_prefix = os.path.join(test_prefix, path_component) + test_import_filename = os.path.join(test_prefix, imp.import_filename) + if os.path.exists(os.path.join(root, test_import_filename)): + imp.import_filename = test_import_filename + break parseMojom(imp.import_filename, file_overrides, override_modules) # Now that the transitive set of dependencies has been imported and parsed @@ -89,10 +113,10 @@ def _ValidateDelta(root, delta): modules[mojom] = translate.OrderedModule(ast, mojom, all_modules) old_modules = {} - for mojom in old_files.keys(): + for mojom in old_files: parseMojom(mojom, old_files, old_modules) new_modules = {} - for mojom in new_files.keys(): + for mojom in new_files: parseMojom(mojom, new_files, new_modules) # At this point we have a complete set of translated Modules from both the @@ -132,12 +156,21 @@ def _ValidateDelta(root, delta): 'can be deleted by a subsequent change.' % qualified_name) checker = module.BackwardCompatibilityChecker() - if not checker.IsBackwardCompatible(new_types[new_name], kind): - raise Exception('Stable type %s appears to have changed in a way which ' - 'breaks backward-compatibility. Please fix!\n\nIf you ' - 'believe this assessment to be incorrect, please file a ' - 'Chromium bug against the "Internals>Mojo>Bindings" ' - 'component.' % qualified_name) + try: + if not checker.IsBackwardCompatible(new_types[new_name], kind): + raise Exception( + 'Stable type %s appears to have changed in a way which ' + 'breaks backward-compatibility. Please fix!\n\nIf you ' + 'believe this assessment to be incorrect, please file a ' + 'Chromium bug against the "Internals>Mojo>Bindings" ' + 'component.' % qualified_name) + except Exception as e: + raise Exception( + 'Stable type %s appears to have changed in a way which ' + 'breaks backward-compatibility: \n\n%s.\nPlease fix!\n\nIf you ' + 'believe this assessment to be incorrect, please file a ' + 'Chromium bug against the "Internals>Mojo>Bindings" ' + 'component.' % (qualified_name, e)) def Run(command_line, delta=None): diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py index 9f51ea77..06769c95 100755 --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility from mojom.generate import module -class Change(object): +class Change: """Helper to clearly define a mojom file delta to be analyzed.""" def __init__(self, filename, old=None, new=None): @@ -28,7 +28,7 @@ class Change(object): class UnchangedFile(Change): def __init__(self, filename, contents): - super(UnchangedFile, self).__init__(filename, old=contents, new=contents) + super().__init__(filename, old=contents, new=contents) class CheckStableMojomCompatibilityTest(unittest.TestCase): @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase): [Stable] struct T { foo.S s; int32 x; }; """) ]) + + def testWithPartialImport(self): + """The compatibility checking tool correctly parses imports with partial + paths.""" + self.assertBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('foo/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('foo/bar.mojom', + old="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertNotBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + self.assertNotBackwardCompatible([ + UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'), + Change('bar/bar.mojom', + old="""\ + module bar; + import "foo.mojom"; + [Stable] struct T { foo.S s; }; + """, + new="""\ + module bar; + import "foo/foo.mojom"; + [Stable] struct T { foo.S s; }; + """) + ]) + + def testNewEnumDefault(self): + # Should be backwards compatible since it does not affect the wire format. + # This specific case also checks that the backwards compatibility checker + # does not throw an error due to the older version of the enum not + # specifying [Default]. + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Extensible] enum E { One };', + new='[Extensible] enum E { [Default] One };') + ]) + self.assertBackwardCompatible([ + Change('foo/foo.mojom', + old='[Extensible] enum E { [Default] One, Two, };', + new='[Extensible] enum E { One, [Default] Two, };') + ]) diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/const_unittest.py index cb42dfac..e8ed36a7 100644 --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/const_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/enum_unittest.py index d9005078..9269cde5 100644 --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/enum_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase): self.assertEqual('F', b.enums[0].mojom_name) self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name) self.assertEqual(37, b.enums[0].fields[0].numeric_value) + + def testEnumAttributesAreEnums(self): + """Verifies that enum values in attributes are really enum types.""" + a_mojom = 'a.mojom' + self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };') + b_mojom = 'b.mojom' + self.WriteFile( + b_mojom, 'module b;' + 'import "a.mojom";' + '[MooCow=a.E.kFoo]' + 'interface Foo { Foo(); };') + self.ParseMojoms([a_mojom, b_mojom]) + b = self.LoadModule(b_mojom) + self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo') + + def testConstantAttributes(self): + """Verifies that constants as attributes are translated to the constant.""" + a_mojom = 'a.mojom' + self.WriteFile( + a_mojom, 'module a;' + 'enum E { kFoo, kBar };' + 'const E kB = E.kFoo;' + '[Attr=kB] interface Hello { Foo(); };') + self.ParseMojoms([a_mojom]) + a = self.LoadModule(a_mojom) + self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB') + self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name, + 'kFoo') diff --git a/utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py new file mode 100644 index 00000000..5f014e1c --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py @@ -0,0 +1,84 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + + +class FeatureTest(MojomParserTestCase): + """Tests feature parsing behavior.""" + def testFeatureOff(self): + """Verifies basic parsing of feature types.""" + types = self.ExtractTypes(""" + // e.g. BASE_DECLARE_FEATURE(kFeature); + [AttributeOne=ValueOne] + feature kFeature { + // BASE_FEATURE(kFeature,"MyFeature", + // base::FEATURE_DISABLED_BY_DEFAULT); + const string name = "MyFeature"; + const bool default_state = false; + }; + """) + self.assertEqual('name', types['kFeature'].constants[0].mojom_name) + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value) + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name) + self.assertEqual('false', types['kFeature'].constants[1].value) + + def testFeatureOn(self): + """Verifies basic parsing of feature types.""" + types = self.ExtractTypes(""" + // e.g. BASE_DECLARE_FEATURE(kFeature); + feature kFeature { + // BASE_FEATURE(kFeature,"MyFeature", + // base::FEATURE_ENABLED_BY_DEFAULT); + const string name = "MyFeature"; + const bool default_state = true; + }; + """) + self.assertEqual('name', types['kFeature'].constants[0].mojom_name) + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value) + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name) + self.assertEqual('true', types['kFeature'].constants[1].value) + + def testFeatureWeakKeyword(self): + """Verifies that `feature` is a weak keyword.""" + types = self.ExtractTypes(""" + // e.g. BASE_DECLARE_FEATURE(kFeature); + [AttributeOne=ValueOne] + feature kFeature { + // BASE_FEATURE(kFeature,"MyFeature", + // base::FEATURE_DISABLED_BY_DEFAULT); + const string name = "MyFeature"; + const bool default_state = false; + }; + struct MyStruct { + bool feature = true; + }; + interface InterfaceName { + Method(string feature) => (int32 feature); + }; + """) + self.assertEqual('name', types['kFeature'].constants[0].mojom_name) + self.assertEqual('"MyFeature"', types['kFeature'].constants[0].value) + self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name) + self.assertEqual('false', types['kFeature'].constants[1].value) + + def testFeatureAttributesAreFeatures(self): + """Verifies that feature values in attributes are really feature types.""" + a_mojom = 'a.mojom' + self.WriteFile( + a_mojom, 'module a;' + 'feature F { const string name = "f";' + 'const bool default_state = false; };') + b_mojom = 'b.mojom' + self.WriteFile( + b_mojom, 'module b;' + 'import "a.mojom";' + 'feature G' + '{const string name = "g"; const bool default_state = false;};' + '[Attri=a.F] interface Foo { Foo(); };' + '[Boink=G] interface Bar {};') + self.ParseMojoms([a_mojom, b_mojom]) + b = self.LoadModule(b_mojom) + self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F') + self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G') diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/BUILD.gn index 51facc0c..a0edf0eb 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,6 +8,7 @@ group("mojom") { "error.py", "fileutil.py", "generate/__init__.py", + "generate/check.py", "generate/generator.py", "generate/module.py", "generate/pack.py", diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/__init__.py index e69de29b..e69de29b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/__init__.py diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/error.py index 8a1e03da..dd53b835 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/error.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil.py index bf626f54..124f12c1 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil.py @@ -1,9 +1,8 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. +# Copyright 2015 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import errno -import imp import os.path import sys diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py index ff5753a2..c93d2289 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py @@ -1,20 +1,17 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. +# Copyright 2015 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp import os.path import shutil -import sys import tempfile import unittest from mojom import fileutil - class FileUtilTest(unittest.TestCase): def testEnsureDirectoryExists(self): - """Test that EnsureDirectoryExists fuctions correctly.""" + """Test that EnsureDirectoryExists functions correctly.""" temp_dir = tempfile.mkdtemp() try: diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py index e69de29b..e69de29b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py diff --git a/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py new file mode 100644 index 00000000..1efe2022 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py @@ -0,0 +1,26 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Code shared by the various pre-generation mojom checkers.""" + + +class CheckException(Exception): + def __init__(self, module, message): + self.module = module + self.message = message + super().__init__(self.message) + + def __str__(self): + return "Failed mojo pre-generation check for {}:\n{}".format( + self.module.path, self.message) + + +class Check: + def __init__(self, module): + self.module = module + + def CheckModule(self): + """ Subclass should return True if its Checks pass, and throw an + exception otherwise. CheckModule will be called immediately before + mojom.generate.Generator.GenerateFiles()""" + raise NotImplementedError("Subclasses must override/implement this method") diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator.py index 4a1c73fc..96fe3a2d 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Code shared by the various language-specific code generators.""" @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier): return _ToSnakeCase(identifier, upper=False) -class Stylizer(object): +class Stylizer: """Stylizers specify naming rules to map mojom names to names in generated code. For example, if you would like method_name in mojom to be mapped to MethodName in the generated code, you need to define a subclass of Stylizer @@ -130,6 +130,9 @@ class Stylizer(object): def StylizeEnum(self, mojom_name): return mojom_name + def StylizeFeature(self, mojom_name): + return mojom_name + def StylizeModule(self, mojom_namespace): return mojom_namespace @@ -233,7 +236,7 @@ def AddComputedData(module): _AddInterfaceComputedData(interface) -class Generator(object): +class Generator: # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all # files to stdout. def __init__(self, @@ -243,7 +246,6 @@ class Generator(object): variant=None, bytecode_path=None, for_blink=False, - js_bindings_mode="new", js_generate_struct_deserializers=False, export_attribute=None, export_header=None, @@ -262,7 +264,6 @@ class Generator(object): self.variant = variant self.bytecode_path = bytecode_path self.for_blink = for_blink - self.js_bindings_mode = js_bindings_mode self.js_generate_struct_deserializers = js_generate_struct_deserializers self.export_attribute = export_attribute self.export_header = export_header diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py index 32c884a8..7143e07c 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py @@ -1,13 +1,12 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp +import importlib.util import os.path import sys import unittest - def _GetDirAbove(dirname): """Returns the directory "above" this file containing |dirname| (which must also be "above" this file).""" @@ -20,12 +19,11 @@ def _GetDirAbove(dirname): try: - imp.find_module("mojom") + importlib.util.find_spec("mojom") except ImportError: sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) from mojom.generate import generator - class StringManipulationTest(unittest.TestCase): """generator contains some string utilities, this tests only those.""" @@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase): self.assertEquals("SNAKE_D3D11_CASE", generator.ToUpperSnakeCase("snakeD3d11Case")) - if __name__ == "__main__": unittest.main() diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module.py index 9bdb28e0..ca71059d 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,15 +12,14 @@ # method = interface.AddMethod('Tat', 0) # method.AddParameter('baz', 0, mojom.INT32) -import sys -if sys.version_info.major == 2: - import cPickle as pickle -else: - import pickle +import pickle +from collections import OrderedDict from uuid import UUID +# pylint: disable=raise-missing-from -class BackwardCompatibilityChecker(object): + +class BackwardCompatibilityChecker: """Used for memoization while recursively checking two type definitions for backward-compatibility.""" @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True): return obj.Repr(as_ref=as_ref) # Since we cannot implement Repr for existing container types, we # handle them here. - elif isinstance(obj, list): + if isinstance(obj, list): if not obj: return '[]' - else: - return ('[\n%s\n]' % (',\n'.join( - ' %s' % Repr(elem, as_ref).replace('\n', '\n ') - for elem in obj))) - elif isinstance(obj, dict): + return ('[\n%s\n]' % + (',\n'.join(' %s' % Repr(elem, as_ref).replace('\n', '\n ') + for elem in obj))) + if isinstance(obj, dict): if not obj: return '{}' - else: - return ('{\n%s\n}' % (',\n'.join( - ' %s: %s' % (Repr(key, as_ref).replace('\n', '\n '), - Repr(val, as_ref).replace('\n', '\n ')) - for key, val in obj.items()))) - else: - return repr(obj) + return ('{\n%s\n}' % (',\n'.join(' %s: %s' % + (Repr(key, as_ref).replace('\n', '\n '), + Repr(val, as_ref).replace('\n', '\n ')) + for key, val in obj.items()))) + return repr(obj) def GenericRepr(obj, names): @@ -104,7 +100,7 @@ def GenericRepr(obj, names): ReprIndent(name, as_ref) for (name, as_ref) in names.items())) -class Kind(object): +class Kind: """Kind represents a type (e.g. int8, string). Attributes: @@ -112,16 +108,43 @@ class Kind(object): module: {Module} The defining module. Set to None for built-in types. parent_kind: The enclosing type. For example, an enum defined inside an interface has that interface as its parent. May be None. + is_nullable: True if the type is nullable. """ - def __init__(self, spec=None, module=None): + def __init__(self, spec=None, is_nullable=False, module=None): self.spec = spec self.module = module self.parent_kind = None + self.is_nullable = is_nullable + self.shared_definition = {} + + @classmethod + def AddSharedProperty(cls, name): + """Adds a property |name| to |cls|, which accesses the corresponding item in + |shared_definition|. + + The reason of adding such indirection is to enable sharing definition + between a reference kind and its nullable variation. For example: + a = Struct('test_struct_1') + b = a.MakeNullableKind() + a.name = 'test_struct_2' + print(b.name) # Outputs 'test_struct_2'. + """ + def Get(self): + try: + return self.shared_definition[name] + except KeyError: # Must raise AttributeError if property doesn't exist. + raise AttributeError + + def Set(self, value): + self.shared_definition[name] = value + + setattr(cls, name, property(Get, Set)) def Repr(self, as_ref=True): # pylint: disable=unused-argument - return '<%s spec=%r>' % (self.__class__.__name__, self.spec) + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, + self.is_nullable) def __repr__(self): # Gives us a decent __repr__ for all kinds. @@ -130,7 +153,8 @@ class Kind(object): def __eq__(self, rhs): # pylint: disable=unidiomatic-typecheck return (type(self) == type(rhs) - and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind)) + and (self.spec, self.parent_kind, self.is_nullable) + == (rhs.spec, rhs.parent_kind, rhs.is_nullable)) def __hash__(self): # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind @@ -138,32 +162,113 @@ class Kind(object): # some primitive Kinds as dict keys. The default hash (object identity) # breaks these dicts when a pickled Module instance is unpickled and used # during a subsequent run of the parser. - return hash((self.spec, self.parent_kind)) + return hash((self.spec, self.parent_kind, self.is_nullable)) # pylint: disable=unused-argument def IsBackwardCompatible(self, rhs, checker): return self == rhs +class ValueKind(Kind): + """ValueKind represents values that aren't reference kinds. + + The primary difference is the wire representation for nullable value kinds + still reserves space for the value type itself, even if that value itself + is logically null. + """ + def __init__(self, spec=None, is_nullable=False, module=None): + assert spec is None or is_nullable == spec.startswith('?') + Kind.__init__(self, spec, is_nullable, module) + + def MakeNullableKind(self): + assert not self.is_nullable + + if self == BOOL: + return NULLABLE_BOOL + if self == INT8: + return NULLABLE_INT8 + if self == INT16: + return NULLABLE_INT16 + if self == INT32: + return NULLABLE_INT32 + if self == INT64: + return NULLABLE_INT64 + if self == UINT8: + return NULLABLE_UINT8 + if self == UINT16: + return NULLABLE_UINT16 + if self == UINT32: + return NULLABLE_UINT32 + if self == UINT64: + return NULLABLE_UINT64 + if self == FLOAT: + return NULLABLE_FLOAT + if self == DOUBLE: + return NULLABLE_DOUBLE + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = '?' + self.spec + nullable_kind.is_nullable = True + nullable_kind.parent_kind = self.parent_kind + nullable_kind.module = self.module + + return nullable_kind + + def MakeUnnullableKind(self): + assert self.is_nullable + + if self == NULLABLE_BOOL: + return BOOL + if self == NULLABLE_INT8: + return INT8 + if self == NULLABLE_INT16: + return INT16 + if self == NULLABLE_INT32: + return INT32 + if self == NULLABLE_INT64: + return INT64 + if self == NULLABLE_UINT8: + return UINT8 + if self == NULLABLE_UINT16: + return UINT16 + if self == NULLABLE_UINT32: + return UINT32 + if self == NULLABLE_UINT64: + return UINT64 + if self == NULLABLE_FLOAT: + return FLOAT + if self == NULLABLE_DOUBLE: + return DOUBLE + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = self.spec[1:] + nullable_kind.is_nullable = False + nullable_kind.parent_kind = self.parent_kind + nullable_kind.module = self.module + + return nullable_kind + + def __eq__(self, rhs): + return (isinstance(rhs, ValueKind) and super().__eq__(rhs)) + + def __hash__(self): # pylint: disable=useless-super-delegation + return super().__hash__() + + class ReferenceKind(Kind): """ReferenceKind represents pointer and handle types. A type is nullable if null (for pointer types) or invalid handle (for handle types) is a legal value for the type. - - Attributes: - is_nullable: True if the type is nullable. """ def __init__(self, spec=None, is_nullable=False, module=None): assert spec is None or is_nullable == spec.startswith('?') - Kind.__init__(self, spec, module) - self.is_nullable = is_nullable - self.shared_definition = {} - - def Repr(self, as_ref=True): - return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, - self.is_nullable) + Kind.__init__(self, spec, is_nullable, module) def MakeNullableKind(self): assert not self.is_nullable @@ -193,55 +298,65 @@ class ReferenceKind(Kind): return nullable_kind - @classmethod - def AddSharedProperty(cls, name): - """Adds a property |name| to |cls|, which accesses the corresponding item in - |shared_definition|. - - The reason of adding such indirection is to enable sharing definition - between a reference kind and its nullable variation. For example: - a = Struct('test_struct_1') - b = a.MakeNullableKind() - a.name = 'test_struct_2' - print(b.name) # Outputs 'test_struct_2'. - """ - - def Get(self): - try: - return self.shared_definition[name] - except KeyError: # Must raise AttributeError if property doesn't exist. - raise AttributeError - - def Set(self, value): - self.shared_definition[name] = value + def MakeUnnullableKind(self): + assert self.is_nullable + + if self == NULLABLE_STRING: + return STRING + if self == NULLABLE_HANDLE: + return HANDLE + if self == NULLABLE_DCPIPE: + return DCPIPE + if self == NULLABLE_DPPIPE: + return DPPIPE + if self == NULLABLE_MSGPIPE: + return MSGPIPE + if self == NULLABLE_SHAREDBUFFER: + return SHAREDBUFFER + if self == NULLABLE_PLATFORMHANDLE: + return PLATFORMHANDLE + + unnullable_kind = type(self)() + unnullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + assert self.spec[0] == '?' + unnullable_kind.spec = self.spec[1:] + unnullable_kind.is_nullable = False + unnullable_kind.parent_kind = self.parent_kind + unnullable_kind.module = self.module - setattr(cls, name, property(Get, Set)) + return unnullable_kind def __eq__(self, rhs): - return (isinstance(rhs, ReferenceKind) - and super(ReferenceKind, self).__eq__(rhs) - and self.is_nullable == rhs.is_nullable) + return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs)) - def __hash__(self): - return hash((super(ReferenceKind, self).__hash__(), self.is_nullable)) - - def IsBackwardCompatible(self, rhs, checker): - return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker) - and self.is_nullable == rhs.is_nullable) + def __hash__(self): # pylint: disable=useless-super-delegation + return super().__hash__() # Initialize the set of primitive types. These can be accessed by clients. -BOOL = Kind('b') -INT8 = Kind('i8') -INT16 = Kind('i16') -INT32 = Kind('i32') -INT64 = Kind('i64') -UINT8 = Kind('u8') -UINT16 = Kind('u16') -UINT32 = Kind('u32') -UINT64 = Kind('u64') -FLOAT = Kind('f') -DOUBLE = Kind('d') +BOOL = ValueKind('b') +INT8 = ValueKind('i8') +INT16 = ValueKind('i16') +INT32 = ValueKind('i32') +INT64 = ValueKind('i64') +UINT8 = ValueKind('u8') +UINT16 = ValueKind('u16') +UINT32 = ValueKind('u32') +UINT64 = ValueKind('u64') +FLOAT = ValueKind('f') +DOUBLE = ValueKind('d') +NULLABLE_BOOL = ValueKind('?b', True) +NULLABLE_INT8 = ValueKind('?i8', True) +NULLABLE_INT16 = ValueKind('?i16', True) +NULLABLE_INT32 = ValueKind('?i32', True) +NULLABLE_INT64 = ValueKind('?i64', True) +NULLABLE_UINT8 = ValueKind('?u8', True) +NULLABLE_UINT16 = ValueKind('?u16', True) +NULLABLE_UINT32 = ValueKind('?u32', True) +NULLABLE_UINT64 = ValueKind('?u64', True) +NULLABLE_FLOAT = ValueKind('?f', True) +NULLABLE_DOUBLE = ValueKind('?d', True) STRING = ReferenceKind('s') HANDLE = ReferenceKind('h') DCPIPE = ReferenceKind('h:d:c') @@ -270,6 +385,17 @@ PRIMITIVES = ( UINT64, FLOAT, DOUBLE, + NULLABLE_BOOL, + NULLABLE_INT8, + NULLABLE_INT16, + NULLABLE_INT32, + NULLABLE_INT64, + NULLABLE_UINT8, + NULLABLE_UINT16, + NULLABLE_UINT32, + NULLABLE_UINT64, + NULLABLE_FLOAT, + NULLABLE_DOUBLE, STRING, HANDLE, DCPIPE, @@ -291,12 +417,17 @@ ATTRIBUTE_DEFAULT = 'Default' ATTRIBUTE_EXTENSIBLE = 'Extensible' ATTRIBUTE_NO_INTERRUPT = 'NoInterrupt' ATTRIBUTE_STABLE = 'Stable' +ATTRIBUTE_SUPPORTS_URGENT = 'SupportsUrgent' ATTRIBUTE_SYNC = 'Sync' ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize' ATTRIBUTE_UUID = 'Uuid' +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox' +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext' +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext' +ATTRIBUTE_RUNTIME_FEATURE = 'RuntimeFeature' -class NamedValue(object): +class NamedValue: def __init__(self, module, parent_kind, mojom_name): self.module = module self.parent_kind = parent_kind @@ -316,7 +447,7 @@ class NamedValue(object): return hash((self.parent_kind, self.mojom_name)) -class BuiltinValue(object): +class BuiltinValue: def __init__(self, value): self.value = value @@ -350,7 +481,7 @@ class EnumValue(NamedValue): return self.field.name -class Constant(object): +class Constant: def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None): self.mojom_name = mojom_name self.name = None @@ -368,7 +499,7 @@ class Constant(object): rhs.parent_kind)) -class Field(object): +class Field: def __init__(self, mojom_name=None, kind=None, @@ -414,7 +545,18 @@ class StructField(Field): class UnionField(Field): - pass + def __init__(self, + mojom_name=None, + kind=None, + ordinal=None, + default=None, + attributes=None): + Field.__init__(self, mojom_name, kind, ordinal, default, attributes) + + @property + def is_default(self): + return self.attributes.get(ATTRIBUTE_DEFAULT, False) \ + if self.attributes else False def _IsFieldBackwardCompatible(new_field, old_field, checker): @@ -424,6 +566,38 @@ def _IsFieldBackwardCompatible(new_field, old_field, checker): return checker.IsBackwardCompatible(new_field.kind, old_field.kind) +class Feature(ReferenceKind): + """A runtime enabled feature defined from mojom. + + Attributes: + mojom_name: {str} The name of the feature type as defined in mojom. + name: {str} The stylized name. (Note: not the "name" used by FeatureList.) + constants: {List[Constant]} The constants defined in the feature scope. + attributes: {dict} Additional information about the feature. + """ + + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('constants') + Kind.AddSharedProperty('attributes') + + def __init__(self, mojom_name=None, module=None, attributes=None): + if mojom_name is not None: + spec = 'x:' + mojom_name + else: + spec = None + ReferenceKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.constants = [] + self.attributes = attributes + + def Stylize(self, stylizer): + self.name = stylizer.StylizeFeature(self.mojom_name) + for constant in self.constants: + constant.Stylize(stylizer) + + class Struct(ReferenceKind): """A struct with typed fields. @@ -441,14 +615,14 @@ class Struct(ReferenceKind): if it's a native struct. """ - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('native_only') - ReferenceKind.AddSharedProperty('custom_serializer') - ReferenceKind.AddSharedProperty('fields') - ReferenceKind.AddSharedProperty('enums') - ReferenceKind.AddSharedProperty('constants') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('native_only') + Kind.AddSharedProperty('custom_serializer') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('enums') + Kind.AddSharedProperty('constants') + Kind.AddSharedProperty('attributes') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -470,12 +644,11 @@ class Struct(ReferenceKind): return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__, self.mojom_name, Repr(self.module, as_ref=True)) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'fields': False, - 'module': True - }) + return GenericRepr(self, { + 'mojom_name': False, + 'fields': False, + 'module': True + }) def AddField(self, mojom_name, @@ -496,13 +669,13 @@ class Struct(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) - def IsBackwardCompatible(self, older_struct, checker): - """This struct is backward-compatible with older_struct if and only if all - of the following conditions hold: + def IsBackwardCompatible(self, rhs, checker): + """This struct is backward-compatible with rhs (older_struct) if and only if + all of the following conditions hold: - Any newly added field is tagged with a [MinVersion] attribute specifying a version number greater than all previously used [MinVersion] attributes within the struct. - - All fields present in older_struct remain present in the new struct, + - All fields present in rhs remain present in the new struct, with the same ordinal position, same optional or non-optional status, same (or backward-compatible) type and where applicable, the same [MinVersion] attribute value. @@ -521,7 +694,7 @@ class Struct(ReferenceKind): return fields_by_ordinal new_fields = buildOrdinalFieldMap(self) - old_fields = buildOrdinalFieldMap(older_struct) + old_fields = buildOrdinalFieldMap(rhs) if len(new_fields) < len(old_fields): # At least one field was removed, which is not OK. return False @@ -574,11 +747,18 @@ class Struct(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.native_only, self.fields, self.constants, + self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Struct) and - (self.mojom_name, self.native_only, self.fields, self.constants, - self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields, - rhs.constants, rhs.attributes)) + return isinstance(rhs, Struct) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) @@ -595,10 +775,11 @@ class Union(ReferenceKind): which Java class name to use to represent it in the generated bindings. """ - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('fields') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('attributes') + Kind.AddSharedProperty('default_field') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -610,14 +791,14 @@ class Union(ReferenceKind): self.name = None self.fields = [] self.attributes = attributes + self.default_field = None def Repr(self, as_ref=True): if as_ref: return '<%s spec=%r is_nullable=%r fields=%s>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr( self.fields)) - else: - return GenericRepr(self, {'fields': True, 'is_nullable': False}) + return GenericRepr(self, {'fields': True, 'is_nullable': False}) def AddField(self, mojom_name, kind, ordinal=None, attributes=None): field = UnionField(mojom_name, kind, ordinal, None, attributes) @@ -629,13 +810,13 @@ class Union(ReferenceKind): for field in self.fields: field.Stylize(stylizer) - def IsBackwardCompatible(self, older_union, checker): - """This union is backward-compatible with older_union if and only if all - of the following conditions hold: + def IsBackwardCompatible(self, rhs, checker): + """This union is backward-compatible with rhs (older_union) if and only if + all of the following conditions hold: - Any newly added field is tagged with a [MinVersion] attribute specifying a version number greater than all previously used [MinVersion] attributes within the union. - - All fields present in older_union remain present in the new union, + - All fields present in rhs remain present in the new union, with the same ordinal value, same optional or non-optional status, same (or backward-compatible) type, and where applicable, the same [MinVersion] attribute value. @@ -651,7 +832,7 @@ class Union(ReferenceKind): return fields_by_ordinal new_fields = buildOrdinalFieldMap(self) - old_fields = buildOrdinalFieldMap(older_union) + old_fields = buildOrdinalFieldMap(rhs) if len(new_fields) < len(old_fields): # At least one field was removed, which is not OK. return False @@ -678,6 +859,11 @@ class Union(ReferenceKind): return True @property + def extensible(self): + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ + if self.attributes else False + + @property def stable(self): return self.attributes.get(ATTRIBUTE_STABLE, False) \ if self.attributes else False @@ -690,10 +876,17 @@ class Union(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.fields, self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Union) and - (self.mojom_name, self.fields, - self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes)) + return isinstance(rhs, Union) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) @@ -707,8 +900,8 @@ class Array(ReferenceKind): length: The number of elements. None if unknown. """ - ReferenceKind.AddSharedProperty('kind') - ReferenceKind.AddSharedProperty('length') + Kind.AddSharedProperty('kind') + Kind.AddSharedProperty('length') def __init__(self, kind=None, length=None): if kind is not None: @@ -728,12 +921,11 @@ class Array(ReferenceKind): return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr( self.kind), self.length) - else: - return GenericRepr(self, { - 'kind': True, - 'length': False, - 'is_nullable': False - }) + return GenericRepr(self, { + 'kind': True, + 'length': False, + 'is_nullable': False + }) def __eq__(self, rhs): return (isinstance(rhs, Array) @@ -754,8 +946,8 @@ class Map(ReferenceKind): key_kind: {Kind} The type of the keys. May be None. value_kind: {Kind} The type of the elements. May be None. """ - ReferenceKind.AddSharedProperty('key_kind') - ReferenceKind.AddSharedProperty('value_kind') + Kind.AddSharedProperty('key_kind') + Kind.AddSharedProperty('value_kind') def __init__(self, key_kind=None, value_kind=None): if (key_kind is not None and value_kind is not None): @@ -780,8 +972,7 @@ class Map(ReferenceKind): return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % ( self.__class__.__name__, self.spec, self.is_nullable, Repr(self.key_kind), Repr(self.value_kind)) - else: - return GenericRepr(self, {'key_kind': True, 'value_kind': True}) + return GenericRepr(self, {'key_kind': True, 'value_kind': True}) def __eq__(self, rhs): return (isinstance(rhs, Map) and @@ -797,7 +988,7 @@ class Map(ReferenceKind): class PendingRemote(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -822,7 +1013,7 @@ class PendingRemote(ReferenceKind): class PendingReceiver(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -847,7 +1038,7 @@ class PendingReceiver(ReferenceKind): class PendingAssociatedRemote(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -873,7 +1064,7 @@ class PendingAssociatedRemote(ReferenceKind): class PendingAssociatedReceiver(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -899,7 +1090,7 @@ class PendingAssociatedReceiver(ReferenceKind): class InterfaceRequest(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -923,7 +1114,7 @@ class InterfaceRequest(ReferenceKind): class AssociatedInterfaceRequest(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -949,7 +1140,7 @@ class AssociatedInterfaceRequest(ReferenceKind): self.kind, rhs.kind) -class Parameter(object): +class Parameter: def __init__(self, mojom_name=None, kind=None, @@ -983,7 +1174,7 @@ class Parameter(object): rhs.default, rhs.attributes)) -class Method(object): +class Method: def __init__(self, interface, mojom_name, ordinal=None, attributes=None): self.interface = interface self.mojom_name = mojom_name @@ -999,12 +1190,11 @@ class Method(object): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'parameters': True, - 'response_parameters': True - }) + return GenericRepr(self, { + 'mojom_name': False, + 'parameters': True, + 'response_parameters': True + }) def AddParameter(self, mojom_name, @@ -1061,21 +1251,49 @@ class Method(object): return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \ if self.attributes else False + @property + def allowed_context(self): + return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \ + if self.attributes else None + + @property + def supports_urgent(self): + return self.attributes.get(ATTRIBUTE_SUPPORTS_URGENT) \ + if self.attributes else None + + @property + def runtime_feature(self): + if not self.attributes: + return None + runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None) + if runtime_feature is None: + return None + if not isinstance(runtime_feature, Feature): + raise Exception("RuntimeFeature attribute on %s must be a feature." % + self.name) + return runtime_feature + + def _tuple(self): + return (self.mojom_name, self.ordinal, self.parameters, + self.response_parameters, self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Method) and - (self.mojom_name, self.ordinal, self.parameters, - self.response_parameters, - self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters, - rhs.response_parameters, rhs.attributes)) + return isinstance(rhs, Method) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() class Interface(ReferenceKind): - ReferenceKind.AddSharedProperty('mojom_name') - ReferenceKind.AddSharedProperty('name') - ReferenceKind.AddSharedProperty('methods') - ReferenceKind.AddSharedProperty('enums') - ReferenceKind.AddSharedProperty('constants') - ReferenceKind.AddSharedProperty('attributes') + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('methods') + Kind.AddSharedProperty('enums') + Kind.AddSharedProperty('constants') + Kind.AddSharedProperty('attributes') def __init__(self, mojom_name=None, module=None, attributes=None): if mojom_name is not None: @@ -1093,12 +1311,11 @@ class Interface(ReferenceKind): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, { - 'mojom_name': False, - 'attributes': False, - 'methods': False - }) + return GenericRepr(self, { + 'mojom_name': False, + 'attributes': False, + 'methods': False + }) def AddMethod(self, mojom_name, ordinal=None, attributes=None): method = Method(self, mojom_name, ordinal, attributes) @@ -1114,10 +1331,10 @@ class Interface(ReferenceKind): for constant in self.constants: constant.Stylize(stylizer) - def IsBackwardCompatible(self, older_interface, checker): - """This interface is backward-compatible with older_interface if and only - if all of the following conditions hold: - - All defined methods in older_interface (when identified by ordinal) have + def IsBackwardCompatible(self, rhs, checker): + """This interface is backward-compatible with rhs (older_interface) if and + only if all of the following conditions hold: + - All defined methods in rhs (when identified by ordinal) have backward-compatible definitions in this interface. For each method this means: - The parameter list is backward-compatible, according to backward- @@ -1131,7 +1348,7 @@ class Interface(ReferenceKind): rules for structs. - All newly introduced methods in this interface have a [MinVersion] attribute specifying a version greater than any method in - older_interface. + rhs. """ def buildOrdinalMethodMap(interface): @@ -1144,7 +1361,7 @@ class Interface(ReferenceKind): return methods_by_ordinal new_methods = buildOrdinalMethodMap(self) - old_methods = buildOrdinalMethodMap(older_interface) + old_methods = buildOrdinalMethodMap(rhs) max_old_min_version = 0 for ordinal, old_method in old_methods.items(): new_method = new_methods.get(ordinal) @@ -1187,6 +1404,39 @@ class Interface(ReferenceKind): return True @property + def service_sandbox(self): + if not self.attributes: + return None + service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None) + if service_sandbox is None: + return None + # Constants are only allowed to refer to an enum here, so replace. + if isinstance(service_sandbox, Constant): + service_sandbox = service_sandbox.value + if not isinstance(service_sandbox, EnumValue): + raise Exception("ServiceSandbox attribute on %s must be an enum value." % + self.module.name) + return service_sandbox + + @property + def runtime_feature(self): + if not self.attributes: + return None + runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None) + if runtime_feature is None: + return None + if not isinstance(runtime_feature, Feature): + raise Exception("RuntimeFeature attribute on %s must be a feature." % + self.name) + return runtime_feature + + @property + def require_context(self): + if not self.attributes: + return None + return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None) + + @property def stable(self): return self.attributes.get(ATTRIBUTE_STABLE, False) \ if self.attributes else False @@ -1199,11 +1449,18 @@ class Interface(ReferenceKind): prefix = self.module.GetNamespacePrefix() return '%s%s' % (prefix, self.mojom_name) + def _tuple(self): + return (self.mojom_name, self.methods, self.enums, self.constants, + self.attributes) + def __eq__(self, rhs): - return (isinstance(rhs, Interface) - and (self.mojom_name, self.methods, self.enums, self.constants, - self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums, - rhs.constants, rhs.attributes)) + return isinstance(rhs, Interface) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() @property def uuid(self): @@ -1224,7 +1481,7 @@ class Interface(ReferenceKind): class AssociatedInterface(ReferenceKind): - ReferenceKind.AddSharedProperty('kind') + Kind.AddSharedProperty('kind') def __init__(self, kind=None): if kind is not None: @@ -1249,7 +1506,7 @@ class AssociatedInterface(ReferenceKind): self.kind, rhs.kind) -class EnumField(object): +class EnumField: def __init__(self, mojom_name=None, value=None, @@ -1281,16 +1538,25 @@ class EnumField(object): rhs.attributes, rhs.numeric_value)) -class Enum(Kind): +class Enum(ValueKind): + Kind.AddSharedProperty('mojom_name') + Kind.AddSharedProperty('name') + Kind.AddSharedProperty('native_only') + Kind.AddSharedProperty('fields') + Kind.AddSharedProperty('attributes') + Kind.AddSharedProperty('min_value') + Kind.AddSharedProperty('max_value') + Kind.AddSharedProperty('default_field') + def __init__(self, mojom_name=None, module=None, attributes=None): - self.mojom_name = mojom_name - self.name = None - self.native_only = False if mojom_name is not None: spec = 'x:' + mojom_name else: spec = None - Kind.__init__(self, spec, module) + ValueKind.__init__(self, spec, False, module) + self.mojom_name = mojom_name + self.name = None + self.native_only = False self.fields = [] self.attributes = attributes self.min_value = None @@ -1300,8 +1566,7 @@ class Enum(Kind): def Repr(self, as_ref=True): if as_ref: return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name) - else: - return GenericRepr(self, {'mojom_name': False, 'fields': False}) + return GenericRepr(self, {'mojom_name': False, 'fields': False}) def Stylize(self, stylizer): self.name = stylizer.StylizeEnum(self.mojom_name) @@ -1327,14 +1592,14 @@ class Enum(Kind): return '%s%s' % (prefix, self.mojom_name) # pylint: disable=unused-argument - def IsBackwardCompatible(self, older_enum, checker): - """This enum is backward-compatible with older_enum if and only if one of - the following conditions holds: + def IsBackwardCompatible(self, rhs, checker): + """This enum is backward-compatible with rhs (older_enum) if and only if one + of the following conditions holds: - Neither enum is [Extensible] and both have the exact same set of valid numeric values. Field names and aliases for the same numeric value do not affect compatibility. - - older_enum is [Extensible], and for every version defined by - older_enum, this enum has the exact same set of valid numeric values. + - rhs is [Extensible], and for every version defined by + rhs, this enum has the exact same set of valid numeric values. """ def buildVersionFieldMap(enum): @@ -1345,32 +1610,49 @@ class Enum(Kind): fields_by_min_version[field.min_version].add(field.numeric_value) return fields_by_min_version - old_fields = buildVersionFieldMap(older_enum) + old_fields = buildVersionFieldMap(rhs) new_fields = buildVersionFieldMap(self) - if new_fields.keys() != old_fields.keys() and not older_enum.extensible: - return False + if new_fields.keys() != old_fields.keys() and not rhs.extensible: + raise Exception("Non-extensible enum cannot be modified") for min_version, valid_values in old_fields.items(): - if (min_version not in new_fields - or new_fields[min_version] != valid_values): - return False + if min_version not in new_fields: + raise Exception('New values added to an extensible enum ' + 'do not specify MinVersion: %s' % new_fields) + + if (new_fields[min_version] != valid_values): + if (len(new_fields[min_version]) < len(valid_values)): + raise Exception('Removing values for an existing MinVersion %s ' + 'is not allowed' % min_version) + raise Exception( + 'New values don\'t match old values' + 'for an existing MinVersion %s,' + ' please specify MinVersion equal to "Next version" ' + 'in the enum description' + ' for the following values:\n%s' % + (min_version, new_fields[min_version].difference(valid_values))) return True + def _tuple(self): + return (self.mojom_name, self.native_only, self.fields, self.attributes, + self.min_value, self.max_value, self.default_field) + def __eq__(self, rhs): - return (isinstance(rhs, Enum) and - (self.mojom_name, self.native_only, self.fields, self.attributes, - self.min_value, self.max_value, - self.default_field) == (rhs.mojom_name, rhs.native_only, - rhs.fields, rhs.attributes, rhs.min_value, - rhs.max_value, rhs.default_field)) + return isinstance(rhs, Enum) and self._tuple() == rhs._tuple() + + def __lt__(self, rhs): + if not isinstance(self, type(rhs)): + return str(type(self)) < str(type(rhs)) + + return self._tuple() < rhs._tuple() def __hash__(self): return id(self) -class Module(object): +class Module: def __init__(self, path=None, mojom_namespace=None, attributes=None): self.path = path self.mojom_namespace = mojom_namespace @@ -1379,24 +1661,26 @@ class Module(object): self.unions = [] self.interfaces = [] self.enums = [] + self.features = [] self.constants = [] - self.kinds = {} + self.kinds = OrderedDict() self.attributes = attributes self.imports = [] - self.imported_kinds = {} - self.metadata = {} + self.imported_kinds = OrderedDict() + self.metadata = OrderedDict() def __repr__(self): # Gives us a decent __repr__ for modules. return self.Repr() def __eq__(self, rhs): - return (isinstance(rhs, Module) and - (self.path, self.attributes, self.mojom_namespace, self.imports, - self.constants, self.enums, self.structs, self.unions, - self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace, - rhs.imports, rhs.constants, rhs.enums, - rhs.structs, rhs.unions, rhs.interfaces)) + return (isinstance(rhs, Module) + and (self.path, self.attributes, self.mojom_namespace, self.imports, + self.constants, self.enums, self.structs, self.unions, + self.interfaces, self.features) + == (rhs.path, rhs.attributes, rhs.mojom_namespace, rhs.imports, + rhs.constants, rhs.enums, rhs.structs, rhs.unions, + rhs.interfaces, rhs.features)) def __hash__(self): return id(self) @@ -1405,16 +1689,16 @@ class Module(object): if as_ref: return '<%s path=%r mojom_namespace=%r>' % ( self.__class__.__name__, self.path, self.mojom_namespace) - else: - return GenericRepr( - self, { - 'path': False, - 'mojom_namespace': False, - 'attributes': False, - 'structs': False, - 'interfaces': False, - 'unions': False - }) + return GenericRepr( + self, { + 'path': False, + 'mojom_namespace': False, + 'attributes': False, + 'structs': False, + 'interfaces': False, + 'unions': False, + 'features': False, + }) def GetNamespacePrefix(self): return '%s.' % self.mojom_namespace if self.mojom_namespace else '' @@ -1434,6 +1718,11 @@ class Module(object): self.unions.append(union) return union + def AddFeature(self, mojom_name, attributes=None): + feature = Feature(mojom_name, self, attributes) + self.features.append(feature) + return feature + def Stylize(self, stylizer): self.namespace = stylizer.StylizeModule(self.mojom_namespace) for struct in self.structs: @@ -1446,12 +1735,14 @@ class Module(object): enum.Stylize(stylizer) for constant in self.constants: constant.Stylize(stylizer) + for feature in self.features: + feature.Stylize(stylizer) for imported_module in self.imports: imported_module.Stylize(stylizer) def Dump(self, f): - pickle.dump(self, f, 2) + pickle.dump(self, f) @classmethod def Load(cls, f): @@ -1461,15 +1752,15 @@ class Module(object): def IsBoolKind(kind): - return kind.spec == BOOL.spec + return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec def IsFloatKind(kind): - return kind.spec == FLOAT.spec + return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec def IsDoubleKind(kind): - return kind.spec == DOUBLE.spec + return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec def IsIntegralKind(kind): @@ -1477,7 +1768,14 @@ def IsIntegralKind(kind): or kind.spec == INT16.spec or kind.spec == INT32.spec or kind.spec == INT64.spec or kind.spec == UINT8.spec or kind.spec == UINT16.spec or kind.spec == UINT32.spec - or kind.spec == UINT64.spec) + or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec + or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec + or kind.spec == NULLABLE_INT32.spec + or kind.spec == NULLABLE_INT64.spec + or kind.spec == NULLABLE_UINT8.spec + or kind.spec == NULLABLE_UINT16.spec + or kind.spec == NULLABLE_UINT32.spec + or kind.spec == NULLABLE_UINT64.spec) def IsStringKind(kind): @@ -1522,6 +1820,10 @@ def IsArrayKind(kind): return isinstance(kind, Array) +def IsFeatureKind(kind): + return isinstance(kind, Feature) + + def IsInterfaceKind(kind): return isinstance(kind, Interface) @@ -1558,12 +1860,16 @@ def IsEnumKind(kind): return isinstance(kind, Enum) +def IsValueKind(kind): + return isinstance(kind, ValueKind) + + def IsReferenceKind(kind): return isinstance(kind, ReferenceKind) def IsNullableKind(kind): - return IsReferenceKind(kind) and kind.is_nullable + return kind.is_nullable def IsMapKind(kind): @@ -1664,11 +1970,8 @@ def MethodPassesInterfaces(method): return _AnyMethodParameterRecursive(method, IsInterfaceKind) -def HasSyncMethods(interface): - for method in interface.methods: - if method.sync: - return True - return False +def GetSyncMethodOrdinals(interface): + return [method.ordinal for method in interface.methods if method.sync] def HasUninterruptableMethods(interface): @@ -1700,18 +2003,17 @@ def ContainsHandlesOrInterfaces(kind): checked.add(kind.spec) if IsStructKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsUnionKind(kind): + if IsUnionKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsAnyHandleKind(kind): + if IsAnyHandleKind(kind): return True - elif IsAnyInterfaceKind(kind): + if IsAnyInterfaceKind(kind): return True - elif IsArrayKind(kind): + if IsArrayKind(kind): return Check(kind.kind) - elif IsMapKind(kind): + if IsMapKind(kind): return Check(kind.key_kind) or Check(kind.value_kind) - else: - return False + return False return Check(kind) @@ -1738,21 +2040,20 @@ def ContainsNativeTypes(kind): checked.add(kind.spec) if IsEnumKind(kind): return kind.native_only - elif IsStructKind(kind): + if IsStructKind(kind): if kind.native_only: return True if any(enum.native_only for enum in kind.enums): return True return any(Check(field.kind) for field in kind.fields) - elif IsUnionKind(kind): + if IsUnionKind(kind): return any(Check(field.kind) for field in kind.fields) - elif IsInterfaceKind(kind): + if IsInterfaceKind(kind): return any(enum.native_only for enum in kind.enums) - elif IsArrayKind(kind): + if IsArrayKind(kind): return Check(kind.kind) - elif IsMapKind(kind): + if IsMapKind(kind): return Check(kind.key_kind) or Check(kind.value_kind) - else: - return False + return False return Check(kind) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py index e8fd4936..2a4e852c 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack.py index 88b77c98..61240426 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack.py @@ -1,7 +1,8 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import copy from mojom.generate import module as mojom # This module provides a mechanism for determining the packed order and offsets @@ -15,7 +16,7 @@ from mojom.generate import module as mojom HEADER_SIZE = 8 -class PackedField(object): +class PackedField: kind_to_size = { mojom.BOOL: 1, mojom.INT8: 1, @@ -75,18 +76,55 @@ class PackedField(object): return 8 return cls.GetSizeForKind(kind) - def __init__(self, field, index, ordinal): + def __init__(self, + field, + index, + ordinal, + original_field=None, + sub_ordinal=None, + linked_value_packed_field=None): """ Args: field: the original field. index: the position of the original field in the struct. ordinal: the ordinal of the field for serialization. + original_field: See below. + sub_ordinal: See below. + linked_value_packed_field: See below. + + original_field, sub_ordinal, and linked_value_packed_field are used to + support nullable ValueKind fields. For legacy reasons, nullable ValueKind + fields actually generate two PackedFields. This allows: + + - backwards compatibility prior to Mojo support for nullable ValueKinds. + - correct packing of fields for the aforementioned backwards compatibility. + + When translating Fields to PackedFields, the original field is turned into + two PackedFields: the first PackedField always has type mojom.BOOL, while + the second PackedField has the non-nullable version of the field's kind. + + When constructing these PackedFields, original_field references the field + as defined in the mojom; the name as defined in the mojom will be used for + all layers above the wire/data layer. + + sub_ordinal is used to sort the two PackedFields correctly with respect to + each other: the first mojom.BOOL field always has sub_ordinal 0, while the + second field always has sub_ordinal 1. + + Finally, linked_value_packed_field is used by the serialization and + deserialization helpers, which generally just iterate over a PackedStruct's + PackedField's in ordinal order. This allows the helpers to easily reference + any related PackedFields rather than having to lookup related PackedFields + by index while iterating. """ self.field = field self.index = index self.ordinal = ordinal - self.size = self.GetSizeForKind(field.kind) - self.alignment = self.GetAlignmentForKind(field.kind) + self.original_field = original_field + self.sub_ordinal = sub_ordinal + self.linked_value_packed_field = linked_value_packed_field + self.size = self.GetSizeForKind(self.field.kind) + self.alignment = self.GetAlignmentForKind(self.field.kind) self.offset = None self.bit = None self.min_version = None @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field): return offset + pad -class PackedStruct(object): +def IsNullableValueKindPackedField(field): + """Returns true if `field` is derived from a nullable ValueKind field. + + Nullable ValueKind fields often require special handling in the bindings due + to the way the implementation is constrained for wire compatibility. + """ + assert isinstance(field, PackedField) + return field.sub_ordinal is not None + + +def IsPrimaryNullableValueKindPackedField(field): + """Returns true if `field` is derived from a nullable ValueKind mojom field + and is the "primary" field. + + The primary field is a bool PackedField that controls if the field should be + considered as present or not; it will have a reference to the PackedField that + holds the actual value representation if considered present. + + Bindings code that translates between the wire protocol and the higher layers + can use this to simplify mapping multiple PackedFields to the single field + that is logically exposed to bindings consumers. + """ + assert isinstance(field, PackedField) + return field.linked_value_packed_field is not None + + +class PackedStruct: def __init__(self, struct): self.struct = struct # |packed_fields| contains all the fields, in increasing offset order. @@ -139,9 +203,41 @@ class PackedStruct(object): for index, field in enumerate(struct.fields): if field.ordinal is not None: ordinal = field.ordinal - src_fields.append(PackedField(field, index, ordinal)) + # Nullable value types are a bit weird: they generate two PackedFields + # despite being a single ValueKind. This is for wire compatibility to + # ease the transition from legacy mojom syntax where nullable value types + # were not supported. + if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable: + # The suffixes intentionally use Unicode codepoints which are considered + # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in + # actual user code. + has_value_field = copy.copy(field) + has_value_field.name = f'{field.mojom_name}_$flag' + has_value_field.kind = mojom.BOOL + + value_field = copy.copy(field) + value_field.name = f'{field.mojom_name}_$value' + value_field.kind = field.kind.MakeUnnullableKind() + + value_packed_field = PackedField(value_field, + index, + ordinal, + original_field=field, + sub_ordinal=1, + linked_value_packed_field=None) + has_value_packed_field = PackedField( + has_value_field, + index, + ordinal, + original_field=field, + sub_ordinal=0, + linked_value_packed_field=value_packed_field) + src_fields.append(has_value_packed_field) + src_fields.append(value_packed_field) + else: + src_fields.append(PackedField(field, index, ordinal)) ordinal += 1 - src_fields.sort(key=lambda field: field.ordinal) + src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal)) # Set |min_version| for each field. next_min_version = 0 @@ -156,10 +252,11 @@ class PackedStruct(object): if (packed_field.min_version != 0 and mojom.IsReferenceKind(packed_field.field.kind) and not packed_field.field.kind.is_nullable): - raise Exception("Non-nullable fields are only allowed in version 0 of " - "a struct. %s.%s is defined with [MinVersion=%d]." % - (self.struct.name, packed_field.field.name, - packed_field.min_version)) + raise Exception( + "Non-nullable reference fields are only allowed in version 0 of a " + "struct. %s.%s is defined with [MinVersion=%d]." % + (self.struct.name, packed_field.field.name, + packed_field.min_version)) src_field = src_fields[0] src_field.offset = 0 @@ -186,7 +283,7 @@ class PackedStruct(object): dst_fields.append(src_field) -class ByteInfo(object): +class ByteInfo: def __init__(self): self.is_padding = False self.packed_fields = [] @@ -214,10 +311,11 @@ def GetByteLayout(packed_struct): return byte_info -class VersionInfo(object): - def __init__(self, version, num_fields, num_bytes): +class VersionInfo: + def __init__(self, version, num_fields, num_packed_fields, num_bytes): self.version = version self.num_fields = num_fields + self.num_packed_fields = num_packed_fields self.num_bytes = num_bytes @@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct): versions = [] last_version = 0 last_num_fields = 0 + last_num_packed_fields = 0 last_payload_size = 0 for packed_field in packed_struct.packed_fields_in_ordinal_order: if packed_field.min_version != last_version: versions.append( - VersionInfo(last_version, last_num_fields, + VersionInfo(last_version, last_num_fields, last_num_packed_fields, last_payload_size + HEADER_SIZE)) last_version = packed_field.min_version - last_num_fields += 1 + # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to + # avoid double-counting, only increment if the field is: + # - not used for representing a nullable value kind field, or + # - the primary field representing the nullable value kind field. + last_num_fields += 1 if ( + not IsNullableValueKindPackedField(packed_field) + or IsPrimaryNullableValueKindPackedField(packed_field)) else 0 + + last_num_packed_fields += 1 + # The fields are iterated in ordinal order here. However, the size of a # version is determined by the last field of that version in pack order, # instead of ordinal order. Therefore, we need to calculate the max value. - last_payload_size = max( - GetPayloadSizeUpToField(packed_field), last_payload_size) + last_payload_size = max(GetPayloadSizeUpToField(packed_field), + last_payload_size) - assert len(versions) == 0 or last_num_fields != versions[-1].num_fields + assert len( + versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields versions.append( - VersionInfo(last_version, last_num_fields, + VersionInfo(last_version, last_num_fields, last_num_packed_fields, last_payload_size + HEADER_SIZE)) return versions diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py index 98c705ad..7d8e4e01 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -205,6 +205,34 @@ class PackTest(unittest.TestCase): self.assertEqual(4, versions[2].num_fields) self.assertEqual(32, versions[2].num_bytes) + def testGetVersionInfoPackedStruct(self): + """Tests that pack.GetVersionInfo() correctly sets version, num_fields, + and num_packed_fields for a packed struct. + """ + struct = mojom.Struct('test') + struct.AddField('field_0', mojom.BOOL, ordinal=0) + struct.AddField('field_1', + mojom.NULLABLE_BOOL, + ordinal=1, + attributes={'MinVersion': 1}) + struct.AddField('field_2', + mojom.NULLABLE_BOOL, + ordinal=2, + attributes={'MinVersion': 2}) + ps = pack.PackedStruct(struct) + versions = pack.GetVersionInfo(ps) + + self.assertEqual(3, len(versions)) + self.assertEqual(0, versions[0].version) + self.assertEqual(1, versions[1].version) + self.assertEqual(2, versions[2].version) + self.assertEqual(1, versions[0].num_fields) + self.assertEqual(2, versions[1].num_fields) + self.assertEqual(3, versions[2].num_fields) + self.assertEqual(1, versions[0].num_packed_fields) + self.assertEqual(3, versions[1].num_packed_fields) + self.assertEqual(5, versions[2].num_packed_fields) + def testInterfaceAlignment(self): """Tests that interfaces are aligned on 4-byte boundaries, although the size of an interface is 8 bytes. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py index 0da90058..807e2a4f 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate.py index 7580b780..83bb297f 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate.py @@ -1,4 +1,4 @@ -# Copyright 2013 The Chromium Authors. All rights reserved. +# Copyright 2013 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Convert parse tree to AST. @@ -12,17 +12,294 @@ already been parsed and converted to ASTs before. import itertools import os import re -import sys +from collections import OrderedDict from mojom.generate import generator from mojom.generate import module as mojom from mojom.parse import ast -def _IsStrOrUnicode(x): - if sys.version_info[0] < 3: - return isinstance(x, (unicode, str)) - return isinstance(x, str) +is_running_backwards_compatibility_check_hack = False + +### DO NOT ADD ENTRIES TO THIS LIST. ### +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = ( + 'x:arc.keymaster.mojom.Algorithm', + 'x:arc.keymaster.mojom.Digest', + 'x:arc.keymaster.mojom.SignatureResult', + 'x:arc.mojom.AccessibilityActionType', + 'x:arc.mojom.AccessibilityBooleanProperty', + 'x:arc.mojom.AccessibilityEventIntListProperty', + 'x:arc.mojom.AccessibilityEventIntProperty', + 'x:arc.mojom.AccessibilityEventStringProperty', + 'x:arc.mojom.AccessibilityEventType', + 'x:arc.mojom.AccessibilityFilterType', + 'x:arc.mojom.AccessibilityIntListProperty', + 'x:arc.mojom.AccessibilityIntProperty', + 'x:arc.mojom.AccessibilityLiveRegionType', + 'x:arc.mojom.AccessibilityNotificationStateType', + 'x:arc.mojom.AccessibilityRangeType', + 'x:arc.mojom.AccessibilitySelectionMode', + 'x:arc.mojom.AccessibilityStringListProperty', + 'x:arc.mojom.AccessibilityStringProperty', + 'x:arc.mojom.AccessibilityWindowBooleanProperty', + 'x:arc.mojom.AccessibilityWindowIntListProperty', + 'x:arc.mojom.AccessibilityWindowIntProperty', + 'x:arc.mojom.AccessibilityWindowStringProperty', + 'x:arc.mojom.AccessibilityWindowType', + 'x:arc.mojom.AccountCheckStatus', + 'x:arc.mojom.AccountUpdateType', + 'x:arc.mojom.ActionType', + 'x:arc.mojom.Algorithm', + 'x:arc.mojom.AndroidIdSource', + 'x:arc.mojom.AnrSource', + 'x:arc.mojom.AnrType', + 'x:arc.mojom.AppDiscoveryRequestState', + 'x:arc.mojom.AppKillType', + 'x:arc.mojom.AppPermission', + 'x:arc.mojom.AppPermissionGroup', + 'x:arc.mojom.AppReinstallState', + 'x:arc.mojom.AppShortcutItemType', + 'x:arc.mojom.ArcAuthCodeStatus', + 'x:arc.mojom.ArcClipboardDragDropEvent', + 'x:arc.mojom.ArcCorePriAbiMigEvent', + 'x:arc.mojom.ArcDnsQuery', + 'x:arc.mojom.ArcImageCopyPasteCompatAction', + 'x:arc.mojom.ArcNetworkError', + 'x:arc.mojom.ArcNetworkEvent', + 'x:arc.mojom.ArcNotificationEvent', + 'x:arc.mojom.ArcNotificationExpandState', + 'x:arc.mojom.ArcNotificationPriority', + 'x:arc.mojom.ArcNotificationRemoteInputState', + 'x:arc.mojom.ArcNotificationShownContents', + 'x:arc.mojom.ArcNotificationStyle', + 'x:arc.mojom.ArcNotificationType', + 'x:arc.mojom.ArcPipEvent', + 'x:arc.mojom.ArcResizeLockState', + 'x:arc.mojom.ArcSignInSuccess', + 'x:arc.mojom.ArcTimerResult', + 'x:arc.mojom.AudioSwitch', + 'x:arc.mojom.BluetoothAclState', + 'x:arc.mojom.BluetoothAdapterState', + 'x:arc.mojom.BluetoothAdvertisingDataType', + 'x:arc.mojom.BluetoothBondState', + 'x:arc.mojom.BluetoothDeviceType', + 'x:arc.mojom.BluetoothDiscoveryState', + 'x:arc.mojom.BluetoothGattDBAttributeType', + 'x:arc.mojom.BluetoothGattStatus', + 'x:arc.mojom.BluetoothPropertyType', + 'x:arc.mojom.BluetoothScanMode', + 'x:arc.mojom.BluetoothSdpAttributeType', + 'x:arc.mojom.BluetoothSocketType', + 'x:arc.mojom.BluetoothStatus', + 'x:arc.mojom.BootType', + 'x:arc.mojom.CaptionTextShadowType', + 'x:arc.mojom.ChangeType', + 'x:arc.mojom.ChromeAccountType', + 'x:arc.mojom.ChromeApp', + 'x:arc.mojom.ChromePage', + 'x:arc.mojom.ClockId', + 'x:arc.mojom.CloudProvisionFlowError', + 'x:arc.mojom.CommandResultType', + 'x:arc.mojom.CompanionLibApiId', + 'x:arc.mojom.ConnectionStateType', + 'x:arc.mojom.ContentChangeType', + 'x:arc.mojom.CpuRestrictionState', + 'x:arc.mojom.CursorCoordinateSpace', + 'x:arc.mojom.DataRestoreStatus', + 'x:arc.mojom.DecoderStatus', + 'x:arc.mojom.DeviceType', + 'x:arc.mojom.Digest', + 'x:arc.mojom.DisplayWakeLockType', + 'x:arc.mojom.EapMethod', + 'x:arc.mojom.EapPhase2Method', + 'x:arc.mojom.FileSelectorEventType', + 'x:arc.mojom.GMSCheckInError', + 'x:arc.mojom.GMSSignInError', + 'x:arc.mojom.GeneralSignInError', + 'x:arc.mojom.GetNetworksRequestType', + 'x:arc.mojom.HalPixelFormat', + 'x:arc.mojom.IPAddressType', + 'x:arc.mojom.InstallErrorReason', + 'x:arc.mojom.KeyFormat', + 'x:arc.mojom.KeyManagement', + 'x:arc.mojom.KeyPurpose', + 'x:arc.mojom.KeymasterError', + 'x:arc.mojom.MainAccountHashMigrationStatus', + 'x:arc.mojom.MainAccountResolutionStatus', + 'x:arc.mojom.ManagementChangeStatus', + 'x:arc.mojom.ManagementState', + 'x:arc.mojom.MessageCenterVisibility', + 'x:arc.mojom.MetricsType', + 'x:arc.mojom.MountEvent', + 'x:arc.mojom.NativeBridgeType', + 'x:arc.mojom.NetworkResult', + 'x:arc.mojom.NetworkType', + 'x:arc.mojom.OemCryptoAlgorithm', + 'x:arc.mojom.OemCryptoCipherMode', + 'x:arc.mojom.OemCryptoHdcpCapability', + 'x:arc.mojom.OemCryptoLicenseType', + 'x:arc.mojom.OemCryptoPrivateKey', + 'x:arc.mojom.OemCryptoProvisioningMethod', + 'x:arc.mojom.OemCryptoResult', + 'x:arc.mojom.OemCryptoRsaPaddingScheme', + 'x:arc.mojom.OemCryptoUsageEntryStatus', + 'x:arc.mojom.Padding', + 'x:arc.mojom.PaiFlowState', + 'x:arc.mojom.PatternType', + 'x:arc.mojom.PressureLevel', + 'x:arc.mojom.PrintColorMode', + 'x:arc.mojom.PrintContentType', + 'x:arc.mojom.PrintDuplexMode', + 'x:arc.mojom.PrinterStatus', + 'x:arc.mojom.ProcessState', + 'x:arc.mojom.PurchaseState', + 'x:arc.mojom.ReauthReason', + 'x:arc.mojom.ScaleFactor', + 'x:arc.mojom.SecurityType', + 'x:arc.mojom.SegmentStyle', + 'x:arc.mojom.SelectFilesActionType', + 'x:arc.mojom.SetNativeChromeVoxResponse', + 'x:arc.mojom.ShowPackageInfoPage', + 'x:arc.mojom.SpanType', + 'x:arc.mojom.SupportedLinkChangeSource', + 'x:arc.mojom.TetheringClientState', + 'x:arc.mojom.TextInputType', + 'x:arc.mojom.TtsEventType', + 'x:arc.mojom.VideoCodecProfile', + 'x:arc.mojom.VideoDecodeAccelerator.Result', + 'x:arc.mojom.VideoEncodeAccelerator.Error', + 'x:arc.mojom.VideoFrameStorageType', + 'x:arc.mojom.VideoPixelFormat', + 'x:arc.mojom.WakefulnessMode', + 'x:arc.mojom.WebApkInstallResult', + 'x:ash.ime.mojom.InputFieldType', + 'x:ash.ime.mojom.PersonalizationMode', + 'x:ash.language.mojom.FeatureId', + 'x:blink.mojom.ScrollRestorationType', + 'x:chromeos.cdm.mojom.CdmKeyStatus', + 'x:chromeos.cdm.mojom.CdmMessageType', + 'x:chromeos.cdm.mojom.CdmSessionType', + 'x:chromeos.cdm.mojom.DecryptStatus', + 'x:chromeos.cdm.mojom.EmeInitDataType', + 'x:chromeos.cdm.mojom.EncryptionScheme', + 'x:chromeos.cdm.mojom.HdcpVersion', + 'x:chromeos.cdm.mojom.OutputProtection.LinkType', + 'x:chromeos.cdm.mojom.OutputProtection.ProtectionType', + 'x:chromeos.cdm.mojom.PromiseException', + 'x:chromeos.cfm.mojom.EnqueuePriority', + 'x:chromeos.cfm.mojom.LoggerErrorCode', + 'x:chromeos.cfm.mojom.LoggerState', + 'x:chromeos.cros_healthd.mojom.CryptoAlgorithm', + 'x:chromeos.cros_healthd.mojom.EncryptionState', + 'x:chromeos.machine_learning.mojom.AnnotationUsecase', + 'x:chromeos.machine_learning.mojom.BuiltinModelId', + 'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult', + 'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus', + 'x:chromeos.machine_learning.mojom.EndpointReason', + 'x:chromeos.machine_learning.mojom.EndpointerType', + 'x:chromeos.machine_learning.mojom.ExecuteResult', + 'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status', + 'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status', + 'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult', + 'x:chromeos.machine_learning.mojom.LoadModelResult', + 'x:chromeos.machine_learning.mojom.Rotation', + 'x:chromeos.network_config.mojom.ConnectionStateType', + 'x:chromeos.network_config.mojom.DeviceStateType', + 'x:chromeos.network_config.mojom.IPConfigType', + 'x:chromeos.network_config.mojom.NetworkType', + 'x:chromeos.network_config.mojom.OncSource', + 'x:chromeos.network_config.mojom.PolicySource', + 'x:chromeos.network_config.mojom.PortalState', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod', + 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus', + 'x:cros.mojom.CameraClientType', + 'x:cros.mojom.CameraMetadataSectionStart', + 'x:cros.mojom.CameraMetadataTag', + 'x:cros.mojom.HalPixelFormat', + 'x:crosapi.mojom.AllowedPaths', + 'x:crosapi.mojom.BrowserAppInstanceType', + 'x:crosapi.mojom.CreationResult', + 'x:crosapi.mojom.DeviceAccessResultCode', + 'x:crosapi.mojom.DeviceMode', + 'x:crosapi.mojom.DlpRestrictionLevel', + 'x:crosapi.mojom.ExoImeSupport', + 'x:crosapi.mojom.FullscreenVisibility', + 'x:crosapi.mojom.GoogleServiceAuthError.State', + 'x:crosapi.mojom.IsInstallableResult', + 'x:crosapi.mojom.KeyTag', + 'x:crosapi.mojom.KeystoreSigningAlgorithmName', + 'x:crosapi.mojom.KeystoreType', + 'x:crosapi.mojom.LacrosFeedbackSource', + 'x:crosapi.mojom.MemoryPressureLevel', + 'x:crosapi.mojom.MetricsReportingManaged', + 'x:crosapi.mojom.NotificationType', + 'x:crosapi.mojom.OndeviceHandwritingSupport', + 'x:crosapi.mojom.OpenResult', + 'x:crosapi.mojom.PolicyDomain', + 'x:crosapi.mojom.RegistrationCodeType', + 'x:crosapi.mojom.ScaleFactor', + 'x:crosapi.mojom.SearchResult.OptionalBool', + 'x:crosapi.mojom.SelectFileDialogType', + 'x:crosapi.mojom.SelectFileResult', + 'x:crosapi.mojom.SharesheetResult', + 'x:crosapi.mojom.TouchEventType', + 'x:crosapi.mojom.VideoRotation', + 'x:crosapi.mojom.WallpaperLayout', + 'x:crosapi.mojom.WebAppInstallResultCode', + 'x:crosapi.mojom.WebAppUninstallResultCode', + 'x:device.mojom.HidBusType', + 'x:device.mojom.WakeLockReason', + 'x:device.mojom.WakeLockType', + 'x:drivefs.mojom.DialogReason.Type', + 'x:drivefs.mojom.DriveError.Type', + 'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus', + 'x:drivefs.mojom.FileMetadata.CanPinStatus', + 'x:drivefs.mojom.FileMetadata.Type', + 'x:drivefs.mojom.ItemEventReason', + 'x:drivefs.mojom.MirrorPathStatus', + 'x:drivefs.mojom.MirrorSyncStatus', + 'x:drivefs.mojom.QueryParameters.SortField', + 'x:fuzz.mojom.FuzzEnum', + 'x:media.mojom.FillLightMode', + 'x:media.mojom.MeteringMode', + 'x:media.mojom.PowerLineFrequency', + 'x:media.mojom.RedEyeReduction', + 'x:media.mojom.ResolutionChangePolicy', + 'x:media.mojom.VideoCaptureApi', + 'x:media.mojom.VideoCaptureBufferType', + 'x:media.mojom.VideoCaptureError', + 'x:media.mojom.VideoCaptureFrameDropReason', + 'x:media.mojom.VideoCapturePixelFormat', + 'x:media.mojom.VideoCaptureTransportType', + 'x:media.mojom.VideoFacingMode', + 'x:media_session.mojom.AudioFocusType', + 'x:media_session.mojom.CameraState', + 'x:media_session.mojom.EnforcementMode', + 'x:media_session.mojom.MediaAudioVideoState', + 'x:media_session.mojom.MediaImageBitmapColorType', + 'x:media_session.mojom.MediaPictureInPictureState', + 'x:media_session.mojom.MediaPlaybackState', + 'x:media_session.mojom.MediaSession.SuspendType', + 'x:media_session.mojom.MediaSessionAction', + 'x:media_session.mojom.MediaSessionImageType', + 'x:media_session.mojom.MediaSessionInfo.SessionState', + 'x:media_session.mojom.MicrophoneState', + 'x:ml.model_loader.mojom.ComputeResult', + 'x:ml.model_loader.mojom.CreateModelLoaderResult', + 'x:ml.model_loader.mojom.LoadModelResult', + 'x:mojo.test.AnExtensibleEnum', + 'x:mojo.test.EnumB', + 'x:mojo.test.ExtensibleEmptyEnum', + 'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault', + 'x:network.mojom.WebSandboxFlags', + 'x:payments.mojom.BillingResponseCode', + 'x:payments.mojom.CreateDigitalGoodsResponseCode', + 'x:payments.mojom.ItemType', + 'x:printing.mojom.PrinterType', + 'x:ui.mojom.KeyboardCode', +) +### DO NOT ADD ENTRIES TO THIS LIST. ### def _DuplicateName(values): @@ -98,12 +375,6 @@ def _MapKind(kind): } if kind.endswith('?'): base_kind = _MapKind(kind[0:-1]) - # NOTE: This doesn't rule out enum types. Those will be detected later, when - # cross-reference is established. - reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv', - 'rma', 'rca') - if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds: - raise Exception('A type (spec "%s") cannot be made nullable' % base_kind) return '?' + base_kind if kind.endswith('}'): lbracket = kind.rfind('{') @@ -113,8 +384,6 @@ def _MapKind(kind): lbracket = kind.rfind('[') typename = kind[0:lbracket] return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename) - if kind.endswith('&'): - return 'r:' + _MapKind(kind[0:-1]) if kind.startswith('asso<'): assert kind.endswith('>') return 'asso:' + _MapKind(kind[5:-1]) @@ -135,13 +404,45 @@ def _MapKind(kind): return 'x:' + kind -def _AttributeListToDict(attribute_list): +def _MapAttributeValue(module, kind, value): + # True/False/None + if value is None: + return value + if not isinstance(value, str): + return value + # Is the attribute value the name of a feature? + try: + # Features cannot be nested in other types, so lookup in the global scope. + trial = _LookupKind(module.kinds, 'x:' + value, + _GetScopeForKind(module, kind)) + if isinstance(trial, mojom.Feature): + return trial + except ValueError: + pass + # Is the attribute value a constant or enum value? + try: + trial = _LookupValue(module, None, None, ('IDENTIFIER', value)) + if isinstance(trial, mojom.ConstantValue): + return trial.constant + if isinstance(trial, mojom.EnumValue): + return trial + except ValueError: + pass + # If not a referenceable mojo type - return as a string. + return value + + +def _AttributeListToDict(module, kind, attribute_list): if attribute_list is None: return None assert isinstance(attribute_list, ast.AttributeList) - # TODO(vtl): Check for duplicate keys here. - return dict( - [(attribute.key, attribute.value) for attribute in attribute_list]) + attributes = dict() + for attribute in attribute_list: + if attribute.key in attributes: + raise Exception("Duplicate key (%s) in attribute list" % attribute.key) + attributes[attribute.key] = _MapAttributeValue(module, kind, + attribute.value) + return attributes builtin_values = frozenset([ @@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope): return kind if spec.startswith('?'): - kind = _Kind(kinds, spec[1:], scope).MakeNullableKind() + kind = _Kind(kinds, spec[1:], scope) + kind = kind.MakeNullableKind() elif spec.startswith('a:'): kind = mojom.Array(_Kind(kinds, spec[2:], scope)) elif spec.startswith('asso:'): @@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope): def _Import(module, import_module): # Copy the struct kinds from our imports into the current module. - importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) + importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface, + mojom.Feature) for kind in import_module.kinds.values(): if (isinstance(kind, importable_kinds) and kind.module.path == import_module.path): @@ -316,6 +619,32 @@ def _Import(module, import_module): return import_module +def _Feature(module, parsed_feature): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_feature: {ast.Feature} Parsed feature. + + Returns: + {mojom.Feature} AST feature. + """ + feature = mojom.Feature(module=module) + feature.mojom_name = parsed_feature.mojom_name + feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name + module.kinds[feature.spec] = feature + feature.constants = [] + _ProcessElements( + parsed_feature.mojom_name, parsed_feature.body, { + ast.Const: + lambda const: feature.constants.append( + _Constant(module, const, feature)), + }) + + feature.attributes = _AttributeListToDict(module, feature, + parsed_feature.attribute_list) + return feature + + def _Struct(module, parsed_struct): """ Args: @@ -345,7 +674,8 @@ def _Struct(module, parsed_struct): struct.fields_data.append, }) - struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) + struct.attributes = _AttributeListToDict(module, struct, + parsed_struct.attribute_list) # Enforce that a [Native] attribute is set to make native-only struct # declarations more explicit. @@ -377,7 +707,8 @@ def _Union(module, parsed_union): union.fields_data = [] _ProcessElements(parsed_union.mojom_name, parsed_union.body, {ast.UnionField: union.fields_data.append}) - union.attributes = _AttributeListToDict(parsed_union.attribute_list) + union.attributes = _AttributeListToDict(module, union, + parsed_union.attribute_list) return union @@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct): field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.default = _LookupValue(module, struct, field.kind, parsed_field.default_value) - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, field, + parsed_field.attribute_list) return field @@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union): """ field = mojom.UnionField() field.mojom_name = parsed_field.mojom_name + # Disallow unions from being self-recursive. + parsed_typename = parsed_field.typename + if parsed_typename.endswith('?'): + parsed_typename = parsed_typename[:-1] + assert parsed_typename != union.mojom_name field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename), (module.mojom_namespace, union.mojom_name)) field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None field.default = None - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, field, + parsed_field.attribute_list) + if field.is_default and not mojom.IsNullableKind(field.kind) and \ + not mojom.IsIntegralKind(field.kind): + raise Exception( + '[Default] field for union %s must be nullable or integral type.' % + union.mojom_name) return field @@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface): parameter.ordinal = (parsed_param.ordinal.value if parsed_param.ordinal else None) parameter.default = None # TODO(tibell): We never have these. Remove field? - parameter.attributes = _AttributeListToDict(parsed_param.attribute_list) + parameter.attributes = _AttributeListToDict(module, parameter, + parsed_param.attribute_list) return parameter @@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface): method.response_parameters = list( map(lambda parameter: _Parameter(module, parameter, interface), parsed_method.response_parameter_list)) - method.attributes = _AttributeListToDict(parsed_method.attribute_list) + method.attributes = _AttributeListToDict(module, method, + parsed_method.attribute_list) # Enforce that only methods with response can have a [Sync] attribute. if method.sync and method.response_parameters is None: @@ -492,7 +837,8 @@ def _Interface(module, parsed_iface): interface.mojom_name = parsed_iface.mojom_name interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name module.kinds[interface.spec] = interface - interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) + interface.attributes = _AttributeListToDict(module, interface, + parsed_iface.attribute_list) interface.enums = [] interface.constants = [] interface.methods_data = [] @@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field): field = mojom.EnumField() field.mojom_name = parsed_field.mojom_name field.value = _LookupValue(module, enum, None, parsed_field.value) - field.attributes = _AttributeListToDict(parsed_field.attribute_list) + field.attributes = _AttributeListToDict(module, field, + parsed_field.attribute_list) value = mojom.EnumValue(module, enum, field) module.values[value.GetSpec()] = value return field @@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum): prev_value += 1 # Integral value (e.g: BEGIN = -0x1). - elif _IsStrOrUnicode(field.value): + elif isinstance(field.value, str): prev_value = int(field.value, 0) # Reference to a previous enum value (e.g: INIT = BEGIN). @@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum): else: raise Exception('Unresolved enum value for %s' % field.value.GetSpec()) - #resolved_enum_values[field.mojom_name] = prev_value + if prev_value in (-128, -127): + raise Exception(f'{field.mojom_name} in {enum.spec} has the value ' + f'{prev_value}, which is reserved for WTF::HashTrait\'s ' + 'default enum specialization and may not be used.') field.numeric_value = prev_value if min_value is None or prev_value < min_value: min_value = prev_value @@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind): mojom_name = parent_kind.mojom_name + '.' + mojom_name enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name) enum.parent_kind = parent_kind - enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) + enum.attributes = _AttributeListToDict(module, enum, + parsed_enum.attribute_list) if not enum.native_only: enum.fields = list( @@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind): for field in enum.fields: if field.default: if not enum.extensible: - raise Exception('Non-extensible enums may not specify a default') - if enum.default_field is not None: raise Exception( - 'Only one enumerator value may be specified as the default') + f'Non-extensible enum {enum.spec} may not specify a default') + if enum.default_field is not None: + raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}') enum.default_field = field + # While running the backwards compatibility check, ignore errors because the + # old version of the enum might not specify [Default]. + if (enum.extensible and enum.default_field is None + and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT + and not is_running_backwards_compatibility_check_hack): + raise Exception( + f'Extensible enum {enum.spec} must specify a [Default] enumerator') module.kinds[enum.spec] = enum @@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds): for referenced_kind in extract_referenced_user_kinds(param.kind): sanitized_kind = sanitize_kind(referenced_kind) referenced_user_kinds[sanitized_kind.spec] = sanitized_kind + # Consts can reference imported enums. + for const in module.constants: + if not const.kind in mojom.PRIMITIVES: + sanitized_kind = sanitize_kind(const.kind) + referenced_user_kinds[sanitized_kind.spec] = sanitized_kind return referenced_user_kinds @@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind): assertDependencyIsStable(response_param.kind) +def _AssertStructIsValid(kind): + expected_ordinals = set(range(0, len(kind.fields))) + ordinals = set(map(lambda field: field.ordinal, kind.fields)) + if ordinals != expected_ordinals: + raise Exception( + 'Structs must use contiguous ordinals starting from 0. ' + + '{} is missing the following ordinals: {}.'.format( + kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals)))) + + def _Module(tree, path, imports): """ Args: @@ -778,6 +1151,8 @@ def _Module(tree, path, imports): module.structs = [] module.unions = [] module.interfaces = [] + module.features = [] + _ProcessElements( filename, tree.definition_list, { ast.Const: @@ -791,6 +1166,8 @@ def _Module(tree, path, imports): ast.Interface: lambda interface: module.interfaces.append( _Interface(module, interface)), + ast.Feature: + lambda feature: module.features.append(_Feature(module, feature)), }) # Second pass expands fields and methods. This allows fields and parameters @@ -806,12 +1183,24 @@ def _Module(tree, path, imports): for enum in struct.enums: all_defined_kinds[enum.spec] = enum + for feature in module.features: + all_defined_kinds[feature.spec] = feature + for union in module.unions: union.fields = list( map(lambda field: _UnionField(module, field, union), union.fields_data)) _AssignDefaultOrdinals(union.fields) + for field in union.fields: + if field.is_default: + if union.default_field is not None: + raise Exception('Multiple [Default] fields in union %s.' % + union.mojom_name) + union.default_field = field del union.fields_data all_defined_kinds[union.spec] = union + if union.extensible and union.default_field is None: + raise Exception('Extensible union %s must specify a [Default] field' % + union.mojom_name) for interface in module.interfaces: interface.methods = list( @@ -829,8 +1218,8 @@ def _Module(tree, path, imports): all_defined_kinds.values()) imported_kind_specs = set(all_referenced_kinds.keys()).difference( set(all_defined_kinds.keys())) - module.imported_kinds = dict( - (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs) + module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec]) + for spec in sorted(imported_kind_specs)) generator.AddComputedData(module) for iface in module.interfaces: @@ -847,6 +1236,9 @@ def _Module(tree, path, imports): if kind.stable: _AssertTypeIsStable(kind) + for kind in module.structs: + _AssertStructIsValid(kind) + return module diff --git a/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py new file mode 100644 index 00000000..b4fea924 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py @@ -0,0 +1,141 @@ +# Copyright 2014 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +from mojom.generate import module as mojom +from mojom.generate import translate +from mojom.parse import ast + +class TranslateTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testSimpleArray(self): + """Tests a simple int32[].""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("int32[]"), "a:i32") + + def testAssociativeArray(self): + """Tests a simple uint8{string}.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8{string}"), "m[s][u8]") + + def testLeftToRightAssociativeArray(self): + """Makes sure that parsing is done from right to left on the internal kinds + in the presence of an associative array.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8[]{string}"), "m[s][a:u8]") + + def testTranslateSimpleUnions(self): + """Makes sure that a simple union is translated correctly.""" + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union( + "SomeUnion", None, + ast.UnionBody([ + ast.UnionField("a", None, None, "int32"), + ast.UnionField("b", None, None, "string") + ])) + ]) + + translation = translate.OrderedModule(tree, "mojom_tree", []) + self.assertEqual(1, len(translation.unions)) + + union = translation.unions[0] + self.assertTrue(isinstance(union, mojom.Union)) + self.assertEqual("SomeUnion", union.mojom_name) + self.assertEqual(2, len(union.fields)) + self.assertEqual("a", union.fields[0].mojom_name) + self.assertEqual(mojom.INT32.spec, union.fields[0].kind.spec) + self.assertEqual("b", union.fields[1].mojom_name) + self.assertEqual(mojom.STRING.spec, union.fields[1].kind.spec) + + def testMapKindRaisesWithDuplicate(self): + """Verifies _MapTreeForType() raises when passed two values with the same + name.""" + methods = [ + ast.Method('dup', None, None, ast.ParameterList(), None), + ast.Method('dup', None, None, ast.ParameterList(), None) + ] + with self.assertRaises(Exception): + translate._ElemsOfType(methods, ast.Method, 'scope') + + def testAssociatedKinds(self): + """Tests type spec translation of associated interfaces and requests.""" + # pylint: disable=W0212 + self.assertEquals( + translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface") + self.assertEquals(translate._MapKind("rca<SomeInterface>?"), + "?rca:x:SomeInterface") + + def testSelfRecursiveUnions(self): + """Verifies _UnionField() raises when a union is self-recursive.""" + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union("SomeUnion", None, + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) + + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union( + "SomeUnion", None, + ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) + + def testDuplicateAttributesException(self): + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Union( + "FakeUnion", + ast.AttributeList([ + ast.Attribute("key1", "value"), + ast.Attribute("key1", "value") + ]), + ast.UnionBody([ + ast.UnionField("a", None, None, "int32"), + ast.UnionField("b", None, None, "string") + ])) + ]) + with self.assertRaises(Exception): + translate.OrderedModule(tree, "mojom_tree", []) + + def testEnumWithReservedValues(self): + """Verifies that assigning reserved values to enumerators fails.""" + # -128 is reserved for the empty representation in WTF::HashTraits. + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Enum( + "MyEnum", None, + ast.EnumValueList([ + ast.EnumValue('kReserved', None, '-128'), + ])) + ]) + with self.assertRaises(Exception) as context: + translate.OrderedModule(tree, "mojom_tree", []) + self.assertIn("reserved for WTF::HashTrait", str(context.exception)) + + # -127 is reserved for the deleted representation in WTF::HashTraits. + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Enum( + "MyEnum", None, + ast.EnumValueList([ + ast.EnumValue('kReserved', None, '-127'), + ])) + ]) + with self.assertRaises(Exception) as context: + translate.OrderedModule(tree, "mojom_tree", []) + self.assertIn("reserved for WTF::HashTrait", str(context.exception)) + + # Implicitly assigning a reserved value should also fail. + tree = ast.Mojom(None, ast.ImportList(), [ + ast.Enum( + "MyEnum", None, + ast.EnumValueList([ + ast.EnumValue('kNotReserved', None, '-129'), + ast.EnumValue('kImplicitlyReserved', None, None), + ])) + ]) + with self.assertRaises(Exception) as context: + translate.OrderedModule(tree, "mojom_tree", []) + self.assertIn("reserved for WTF::HashTrait", str(context.exception)) diff --git a/src/libcamera/formats.c b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py index e69de29b..e69de29b 100644 --- a/src/libcamera/formats.c +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast.py index 1f0db200..aae9cdb6 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast.py @@ -1,4 +1,4 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Node classes for the AST for a Mojo IDL file.""" @@ -8,17 +8,14 @@ # and lineno). You may also define __repr__() to help with analyzing test # failures, especially for more complex types. +import os.path -import sys +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member) +# pylint: disable=no-member -def _IsStrOrUnicode(x): - if sys.version_info[0] < 3: - return isinstance(x, (unicode, str)) - return isinstance(x, str) - -class NodeBase(object): +class NodeBase: """Base class for nodes in the AST.""" def __init__(self, filename=None, lineno=None): @@ -43,7 +40,7 @@ class NodeListBase(NodeBase): classes, in a tuple) of the members of the list.)""" def __init__(self, item_or_items=None, **kwargs): - super(NodeListBase, self).__init__(**kwargs) + super().__init__(**kwargs) self.items = [] if item_or_items is None: pass @@ -62,7 +59,7 @@ class NodeListBase(NodeBase): return self.items.__iter__() def __eq__(self, other): - return super(NodeListBase, self).__eq__(other) and \ + return super().__eq__(other) and \ self.items == other.items # Implement this so that on failure, we get slightly more sensible output. @@ -96,7 +93,7 @@ class Definition(NodeBase): include parameter definitions.) This class is meant to be subclassed.""" def __init__(self, mojom_name, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) NodeBase.__init__(self, **kwargs) self.mojom_name = mojom_name @@ -108,13 +105,13 @@ class Attribute(NodeBase): """Represents an attribute.""" def __init__(self, key, value, **kwargs): - assert _IsStrOrUnicode(key) - super(Attribute, self).__init__(**kwargs) + assert isinstance(key, str) + super().__init__(**kwargs) self.key = key self.value = value def __eq__(self, other): - return super(Attribute, self).__eq__(other) and \ + return super().__eq__(other) and \ self.key == other.key and \ self.value == other.value @@ -131,17 +128,17 @@ class Const(Definition): def __init__(self, mojom_name, attribute_list, typename, value, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) # The typename is currently passed through as a string. - assert _IsStrOrUnicode(typename) + assert isinstance(typename, str) # The value is either a literal (currently passed through as a string) or a # "wrapped identifier". - assert _IsStrOrUnicode or isinstance(value, tuple) - super(Const, self).__init__(mojom_name, **kwargs) + assert isinstance(value, (tuple, str)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.typename = typename self.value = value def __eq__(self, other): - return super(Const, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.typename == other.typename and \ self.value == other.value @@ -153,12 +150,12 @@ class Enum(Definition): def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert enum_value_list is None or isinstance(enum_value_list, EnumValueList) - super(Enum, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.enum_value_list = enum_value_list def __eq__(self, other): - return super(Enum, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.enum_value_list == other.enum_value_list @@ -170,13 +167,13 @@ class EnumValue(Definition): # The optional value is either an int (which is current a string) or a # "wrapped identifier". assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple) - super(EnumValue, self).__init__(mojom_name, **kwargs) + assert value is None or isinstance(value, (tuple, str)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.value = value def __eq__(self, other): - return super(EnumValue, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.value == other.value @@ -188,18 +185,47 @@ class EnumValueList(NodeListBase): _list_item_type = EnumValue +class Feature(Definition): + """Represents a runtime feature definition.""" + def __init__(self, mojom_name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, FeatureBody) or body is None + super().__init__(mojom_name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super().__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + def __repr__(self): + return "Feature(mojom_name = %s, attribute_list = %s, body = %s)" % ( + self.mojom_name, self.attribute_list, self.body) + + +# This needs to be declared after `FeatureConst` and `FeatureField`. +class FeatureBody(NodeListBase): + """Represents the body of (i.e., list of definitions inside) a feature.""" + + # Features are compile time helpers so all fields are initializers/consts + # for the underlying platform feature type. + _list_item_type = (Const) + + class Import(NodeBase): """Represents an import statement.""" def __init__(self, attribute_list, import_filename, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) - assert _IsStrOrUnicode(import_filename) - super(Import, self).__init__(**kwargs) + assert isinstance(import_filename, str) + super().__init__(**kwargs) self.attribute_list = attribute_list - self.import_filename = import_filename + # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3. + self.import_filename = os.path.normpath(import_filename).replace('\\', '/') def __eq__(self, other): - return super(Import, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.import_filename == other.import_filename @@ -216,12 +242,12 @@ class Interface(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, InterfaceBody) - super(Interface, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Interface, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body @@ -236,14 +262,14 @@ class Method(Definition): assert isinstance(parameter_list, ParameterList) assert response_parameter_list is None or \ isinstance(response_parameter_list, ParameterList) - super(Method, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.parameter_list = parameter_list self.response_parameter_list = response_parameter_list def __eq__(self, other): - return super(Method, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.parameter_list == other.parameter_list and \ @@ -264,12 +290,12 @@ class Module(NodeBase): # |mojom_namespace| is either none or a "wrapped identifier". assert mojom_namespace is None or isinstance(mojom_namespace, tuple) assert attribute_list is None or isinstance(attribute_list, AttributeList) - super(Module, self).__init__(**kwargs) + super().__init__(**kwargs) self.mojom_namespace = mojom_namespace self.attribute_list = attribute_list def __eq__(self, other): - return super(Module, self).__eq__(other) and \ + return super().__eq__(other) and \ self.mojom_namespace == other.mojom_namespace and \ self.attribute_list == other.attribute_list @@ -281,13 +307,13 @@ class Mojom(NodeBase): assert module is None or isinstance(module, Module) assert isinstance(import_list, ImportList) assert isinstance(definition_list, list) - super(Mojom, self).__init__(**kwargs) + super().__init__(**kwargs) self.module = module self.import_list = import_list self.definition_list = definition_list def __eq__(self, other): - return super(Mojom, self).__eq__(other) and \ + return super().__eq__(other) and \ self.module == other.module and \ self.import_list == other.import_list and \ self.definition_list == other.definition_list @@ -302,11 +328,11 @@ class Ordinal(NodeBase): def __init__(self, value, **kwargs): assert isinstance(value, int) - super(Ordinal, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value def __eq__(self, other): - return super(Ordinal, self).__eq__(other) and \ + return super().__eq__(other) and \ self.value == other.value @@ -314,18 +340,18 @@ class Parameter(NodeBase): """Represents a method request or response parameter.""" def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) - super(Parameter, self).__init__(**kwargs) + assert isinstance(typename, str) + super().__init__(**kwargs) self.mojom_name = mojom_name self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename def __eq__(self, other): - return super(Parameter, self).__eq__(other) and \ + return super().__eq__(other) and \ self.mojom_name == other.mojom_name and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ @@ -344,42 +370,51 @@ class Struct(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, StructBody) or body is None - super(Struct, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Struct, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body + def __repr__(self): + return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % ( + self.mojom_name, self.attribute_list, self.body) + class StructField(Definition): """Represents a struct field definition.""" def __init__(self, mojom_name, attribute_list, ordinal, typename, default_value, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) + assert isinstance(typename, str) # The optional default value is currently either a value as a string or a # "wrapped identifier". - assert default_value is None or _IsStrOrUnicode(default_value) or \ - isinstance(default_value, tuple) - super(StructField, self).__init__(mojom_name, **kwargs) + assert default_value is None or isinstance(default_value, (str, tuple)) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename self.default_value = default_value def __eq__(self, other): - return super(StructField, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.typename == other.typename and \ self.default_value == other.default_value + def __repr__(self): + return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, " + "typename = %s, default_value = %s") % ( + self.mojom_name, self.attribute_list, self.ordinal, + self.typename, self.default_value) + # This needs to be declared after |StructField|. class StructBody(NodeListBase): @@ -394,29 +429,29 @@ class Union(Definition): def __init__(self, mojom_name, attribute_list, body, **kwargs): assert attribute_list is None or isinstance(attribute_list, AttributeList) assert isinstance(body, UnionBody) - super(Union, self).__init__(mojom_name, **kwargs) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.body = body def __eq__(self, other): - return super(Union, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.body == other.body class UnionField(Definition): def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): - assert _IsStrOrUnicode(mojom_name) + assert isinstance(mojom_name, str) assert attribute_list is None or isinstance(attribute_list, AttributeList) assert ordinal is None or isinstance(ordinal, Ordinal) - assert _IsStrOrUnicode(typename) - super(UnionField, self).__init__(mojom_name, **kwargs) + assert isinstance(typename, str) + super().__init__(mojom_name, **kwargs) self.attribute_list = attribute_list self.ordinal = ordinal self.typename = typename def __eq__(self, other): - return super(UnionField, self).__eq__(other) and \ + return super().__eq__(other) and \ self.attribute_list == other.attribute_list and \ self.ordinal == other.ordinal and \ self.typename == other.typename diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py index 62798631..b289f7b1 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py @@ -1,32 +1,26 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp -import os.path -import sys import unittest from mojom.parse import ast - class _TestNode(ast.NodeBase): """Node type for tests.""" def __init__(self, value, **kwargs): - super(_TestNode, self).__init__(**kwargs) + super().__init__(**kwargs) self.value = value def __eq__(self, other): - return super(_TestNode, self).__eq__(other) and self.value == other.value - + return super().__eq__(other) and self.value == other.value class _TestNodeList(ast.NodeListBase): """Node list type for tests.""" _list_item_type = _TestNode - class ASTTest(unittest.TestCase): """Tests various AST classes.""" diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py index 3cb73c5d..9687edbf 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py @@ -1,4 +1,4 @@ -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Helpers for processing conditionally enabled features in a mojom.""" @@ -17,8 +17,10 @@ class EnableIfError(Error): def _IsEnabled(definition, enabled_features): """Returns true if a definition is enabled. - A definition is enabled if it has no EnableIf attribute, or if the value of - the EnableIf attribute is in enabled_features. + A definition is enabled if it has no EnableIf/EnableIfNot attribute. + It is retained if it has an EnableIf attribute and the attribute is in + enabled_features. It is retained if it has an EnableIfNot attribute and the + attribute is not in enabled features. """ if not hasattr(definition, "attribute_list"): return True @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features): already_defined = False for a in definition.attribute_list: - if a.key == 'EnableIf': + if a.key == 'EnableIf' or a.key == 'EnableIfNot': if already_defined: raise EnableIfError( definition.filename, - "EnableIf attribute may only be defined once per field.", + "EnableIf/EnableIfNot attribute may only be set once per field.", definition.lineno) already_defined = True for attribute in definition.attribute_list: if attribute.key == 'EnableIf' and attribute.value not in enabled_features: return False + if attribute.key == 'EnableIfNot' and attribute.value in enabled_features: + return False return True @@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features): """Filters definitions with a body.""" if isinstance(definition, ast.Enum): _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features) - elif isinstance(definition, ast.Interface): - _FilterDisabledFromNodeList(definition.body, enabled_features) elif isinstance(definition, ast.Method): _FilterDisabledFromNodeList(definition.parameter_list, enabled_features) _FilterDisabledFromNodeList(definition.response_parameter_list, enabled_features) - elif isinstance(definition, ast.Struct): - _FilterDisabledFromNodeList(definition.body, enabled_features) - elif isinstance(definition, ast.Union): + elif isinstance(definition, + (ast.Interface, ast.Struct, ast.Union, ast.Feature)): _FilterDisabledFromNodeList(definition.body, enabled_features) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py index aa609be7..cca1764b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py @@ -1,13 +1,12 @@ -# Copyright 2018 The Chromium Authors. All rights reserved. +# Copyright 2018 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp +import importlib.util import os import sys import unittest - def _GetDirAbove(dirname): """Returns the directory "above" this file containing |dirname| (which must also be "above" this file).""" @@ -18,9 +17,8 @@ def _GetDirAbove(dirname): if tail == dirname: return path - try: - imp.find_module('mojom') + importlib.util.find_spec("mojom") except ImportError: sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib')) import mojom.parse.ast as ast @@ -29,7 +27,6 @@ import mojom.parse.parser as parser ENABLED_FEATURES = frozenset({'red', 'green', 'blue'}) - class ConditionalFeaturesTest(unittest.TestCase): """Tests |mojom.parse.conditional_features|.""" @@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(const_source, expected_source) + def testFilterIfNotConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue] + const int kMyConst3 = 3; + [EnableIfNot=blue] + const int kMyConst4 = 4; + [EnableIfNot=purple] + const int kMyConst5 = 5; + """ + expected_source = """ + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIf=blue] + const int kMyConst3 = 3; + [EnableIfNot=purple] + const int kMyConst5 = 5; + """ + self.parseAndAssertEqual(const_source, expected_source) + + def testFilterIfNotMultipleConst(self): + """Test that Consts are correctly filtered.""" + const_source = """ + [EnableIfNot=blue] + const int kMyConst1 = 1; + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIfNot=orange] + const int kMyConst3 = 3; + """ + expected_source = """ + [EnableIfNot=orange] + const double kMyConst2 = 2; + [EnableIfNot=orange] + const int kMyConst3 = 3; + """ + self.parseAndAssertEqual(const_source, expected_source) + def testFilterEnum(self): """Test that EnumValues are correctly filtered from an Enum.""" enum_source = """ @@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(import_source, expected_source) + def testFilterIfNotImport(self): + """Test that imports are correctly filtered from a Mojom.""" + import_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + [EnableIfNot=green] + import "baz.mojom"; + """ + expected_source = """ + [EnableIf=blue] + import "foo.mojom"; + [EnableIfNot=purple] + import "bar.mojom"; + """ + self.parseAndAssertEqual(import_source, expected_source) + def testFilterInterface(self): """Test that definitions are correctly filtered from an Interface.""" interface_source = """ @@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(struct_source, expected_source) + def testFilterIfNotStruct(self): + """Test that definitions are correctly filtered from a Struct.""" + struct_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + [EnableIfNot=red] + VALUE2, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + [EnableIfNot=red] + double f; + }; + """ + expected_source = """ + struct MyStruct { + [EnableIf=blue] + enum MyEnum { + VALUE1, + }; + [EnableIfNot=yellow] + const double kMyConst = 1.23; + [EnableIf=green] + int32 a; + double b; + [EnableIfNot=purple] + int32 c; + [EnableIf=blue] + double d; + int32 e; + }; + """ + self.parseAndAssertEqual(struct_source, expected_source) + def testFilterUnion(self): """Test that UnionFields are correctly filtered from a Union.""" union_source = """ @@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase): """ self.parseAndAssertEqual(mojom_source, expected_source) + def testFeaturesWithEnableIf(self): + mojom_source = """ + feature Foo { + const string name = "FooFeature"; + [EnableIf=red] + const bool default_state = false; + [EnableIf=yellow] + const bool default_state = true; + }; + """ + expected_source = """ + feature Foo { + const string name = "FooFeature"; + [EnableIf=red] + const bool default_state = false; + }; + """ + self.parseAndAssertEqual(mojom_source, expected_source) + def testMultipleEnableIfs(self): source = """ enum Foo { @@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase): conditional_features.RemoveDisabledDefinitions, definition, ENABLED_FEATURES) + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIf=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) + + def testMultipleEnableIfs(self): + source = """ + enum Foo { + [EnableIfNot=red,EnableIfNot=yellow] + kBarValue = 5, + }; + """ + definition = parser.Parse(source, "my_file.mojom") + self.assertRaises(conditional_features.EnableIfError, + conditional_features.RemoveDisabledDefinitions, + definition, ENABLED_FEATURES) if __name__ == '__main__': unittest.main() diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py index 3e084bbf..00136a8b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py @@ -1,8 +1,7 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp import os.path import sys @@ -22,7 +21,7 @@ class LexError(Error): # We have methods which look like they could be functions: # pylint: disable=R0201 -class Lexer(object): +class Lexer: def __init__(self, filename): self.filename = filename @@ -56,6 +55,7 @@ class Lexer(object): 'PENDING_RECEIVER', 'PENDING_ASSOCIATED_REMOTE', 'PENDING_ASSOCIATED_RECEIVER', + 'FEATURE', ) keyword_map = {} @@ -81,7 +81,6 @@ class Lexer(object): # Operators 'MINUS', 'PLUS', - 'AMP', 'QSTN', # Assignment @@ -168,7 +167,6 @@ class Lexer(object): # Operators t_MINUS = r'-' t_PLUS = r'\+' - t_AMP = r'&' t_QSTN = r'\?' # = diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py index eadc6587..bc9f8354 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py @@ -1,13 +1,12 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp +import importlib.util import os.path import sys import unittest - def _GetDirAbove(dirname): """Returns the directory "above" this file containing |dirname| (which must also be "above" this file).""" @@ -18,17 +17,15 @@ def _GetDirAbove(dirname): if tail == dirname: return path - sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party")) from ply import lex try: - imp.find_module("mojom") + importlib.util.find_spec("mojom") except ImportError: sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) import mojom.parse.lexer - # This (monkey-patching LexToken to make comparison value-based) is evil, but # we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing # for object identity.) @@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase): self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+")) self.assertEquals( self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-")) - self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&")) self.assertEquals( self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?")) self.assertEquals( diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser.py index b3b803d6..1dffd98b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser.py @@ -1,8 +1,11 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Generates a syntax tree from a Mojo IDL file.""" +# Breaking parser stanzas is unhelpful so allow longer lines. +# pylint: disable=line-too-long + import os.path import sys @@ -33,7 +36,7 @@ class ParseError(Error): # We have methods which look like they could be functions: # pylint: disable=R0201 -class Parser(object): +class Parser: def __init__(self, lexer, source, filename): self.tokens = lexer.tokens self.source = source @@ -111,7 +114,8 @@ class Parser(object): | union | interface | enum - | const""" + | const + | feature""" p[0] = p[1] def p_attribute_section_1(self, p): @@ -140,12 +144,19 @@ class Parser(object): p[0].Append(p[3]) def p_attribute_1(self, p): - """attribute : NAME EQUALS evaled_literal - | NAME EQUALS NAME""" - p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) + """attribute : name_wrapped EQUALS identifier_wrapped""" + p[0] = ast.Attribute(p[1], + p[3][1], + filename=self.filename, + lineno=p.lineno(1)) def p_attribute_2(self, p): - """attribute : NAME""" + """attribute : name_wrapped EQUALS evaled_literal + | name_wrapped EQUALS name_wrapped""" + p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) + + def p_attribute_3(self, p): + """attribute : name_wrapped""" p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1)) def p_evaled_literal(self, p): @@ -161,11 +172,11 @@ class Parser(object): p[0] = eval(p[1]) def p_struct_1(self, p): - """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI""" + """struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI""" p[0] = ast.Struct(p[3], p[1], p[5]) def p_struct_2(self, p): - """struct : attribute_section STRUCT NAME SEMI""" + """struct : attribute_section STRUCT name_wrapped SEMI""" p[0] = ast.Struct(p[3], p[1], None) def p_struct_body_1(self, p): @@ -180,11 +191,24 @@ class Parser(object): p[0].Append(p[2]) def p_struct_field(self, p): - """struct_field : attribute_section typename NAME ordinal default SEMI""" + """struct_field : attribute_section typename name_wrapped ordinal default SEMI""" p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5]) + def p_feature(self, p): + """feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI""" + p[0] = ast.Feature(p[3], p[1], p[5]) + + def p_feature_body_1(self, p): + """feature_body : """ + p[0] = ast.FeatureBody() + + def p_feature_body_2(self, p): + """feature_body : feature_body const""" + p[0] = p[1] + p[0].Append(p[2]) + def p_union(self, p): - """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI""" + """union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI""" p[0] = ast.Union(p[3], p[1], p[5]) def p_union_body_1(self, p): @@ -197,7 +221,7 @@ class Parser(object): p[1].Append(p[2]) def p_union_field(self, p): - """union_field : attribute_section typename NAME ordinal SEMI""" + """union_field : attribute_section typename name_wrapped ordinal SEMI""" p[0] = ast.UnionField(p[3], p[1], p[4], p[2]) def p_default_1(self, p): @@ -209,8 +233,7 @@ class Parser(object): p[0] = p[2] def p_interface(self, p): - """interface : attribute_section INTERFACE NAME LBRACE interface_body \ - RBRACE SEMI""" + """interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI""" p[0] = ast.Interface(p[3], p[1], p[5]) def p_interface_body_1(self, p): @@ -233,8 +256,7 @@ class Parser(object): p[0] = p[3] def p_method(self, p): - """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \ - response SEMI""" + """method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI""" p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7]) def p_parameter_list_1(self, p): @@ -255,7 +277,7 @@ class Parser(object): p[0].Append(p[3]) def p_parameter(self, p): - """parameter : attribute_section typename NAME ordinal""" + """parameter : attribute_section typename name_wrapped ordinal""" p[0] = ast.Parameter( p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3)) @@ -271,8 +293,7 @@ class Parser(object): """nonnullable_typename : basictypename | array | fixed_array - | associative_array - | interfacerequest""" + | associative_array""" p[0] = p[1] def p_basictypename(self, p): @@ -297,18 +318,16 @@ class Parser(object): p[0] = "rcv<%s>" % p[3] def p_associatedremotetype(self, p): - """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \ - RANGLE""" + """associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE""" p[0] = "rma<%s>" % p[3] def p_associatedreceivertype(self, p): - """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \ - RANGLE""" + """associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE""" p[0] = "rca<%s>" % p[3] def p_handletype(self, p): """handletype : HANDLE - | HANDLE LANGLE NAME RANGLE""" + | HANDLE LANGLE name_wrapped RANGLE""" if len(p) == 2: p[0] = p[1] else: @@ -342,14 +361,6 @@ class Parser(object): """associative_array : MAP LANGLE identifier COMMA typename RANGLE""" p[0] = p[5] + "{" + p[3] + "}" - def p_interfacerequest(self, p): - """interfacerequest : identifier AMP - | ASSOCIATED identifier AMP""" - if len(p) == 3: - p[0] = p[1] + "&" - else: - p[0] = "asso<" + p[2] + "&>" - def p_ordinal_1(self, p): """ordinal : """ p[0] = None @@ -366,15 +377,14 @@ class Parser(object): p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1)) def p_enum_1(self, p): - """enum : attribute_section ENUM NAME LBRACE enum_value_list \ - RBRACE SEMI - | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \ - COMMA RBRACE SEMI""" + """enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI + | attribute_section ENUM name_wrapped LBRACE \ + nonempty_enum_value_list COMMA RBRACE SEMI""" p[0] = ast.Enum( p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2)) def p_enum_2(self, p): - """enum : attribute_section ENUM NAME SEMI""" + """enum : attribute_section ENUM name_wrapped SEMI""" p[0] = ast.Enum( p[3], p[1], None, filename=self.filename, lineno=p.lineno(2)) @@ -396,9 +406,9 @@ class Parser(object): p[0].Append(p[3]) def p_enum_value(self, p): - """enum_value : attribute_section NAME - | attribute_section NAME EQUALS int - | attribute_section NAME EQUALS identifier_wrapped""" + """enum_value : attribute_section name_wrapped + | attribute_section name_wrapped EQUALS int + | attribute_section name_wrapped EQUALS identifier_wrapped""" p[0] = ast.EnumValue( p[2], p[1], @@ -407,7 +417,7 @@ class Parser(object): lineno=p.lineno(2)) def p_const(self, p): - """const : attribute_section CONST typename NAME EQUALS constant SEMI""" + """const : attribute_section CONST typename name_wrapped EQUALS constant SEMI""" p[0] = ast.Const(p[4], p[1], p[3], p[6]) def p_constant(self, p): @@ -422,10 +432,16 @@ class Parser(object): # TODO(vtl): Make this produce a "wrapped" identifier (probably as an # |ast.Identifier|, to be added) and get rid of identifier_wrapped. def p_identifier(self, p): - """identifier : NAME - | NAME DOT identifier""" + """identifier : name_wrapped + | name_wrapped DOT identifier""" p[0] = ''.join(p[1:]) + # Allow 'feature' to be a name literal not just a keyword. + def p_name_wrapped(self, p): + """name_wrapped : NAME + | FEATURE""" + p[0] = p[1] + def p_literal(self, p): """literal : int | float @@ -458,6 +474,12 @@ class Parser(object): # TODO(vtl): Can we figure out what's missing? raise ParseError(self.filename, "Unexpected end of file") + if e.value == 'feature': + raise ParseError(self.filename, + "`feature` is reserved for a future mojom keyword", + lineno=e.lineno, + snippet=self._GetSnippet(e.lineno)) + raise ParseError( self.filename, "Unexpected %r:" % e.value, diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py index 6d6b7153..0a26307b 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py @@ -1,17 +1,13 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. +# Copyright 2014 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import imp -import os.path -import sys import unittest from mojom.parse import ast from mojom.parse import lexer from mojom.parse import parser - class ParserTest(unittest.TestCase): """Tests |parser.Parse()|.""" @@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase): handle<data_pipe_producer>? k; handle<message_pipe>? l; handle<shared_buffer>? m; - some_interface&? n; + pending_receiver<some_interface>? n; handle<platform>? o; }; """ @@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase): ast.StructField('l', None, None, 'handle<message_pipe>?', None), ast.StructField('m', None, None, 'handle<shared_buffer>?', None), - ast.StructField('n', None, None, 'some_interface&?', None), + ast.StructField('n', None, None, 'rcv<some_interface>?', None), ast.StructField('o', None, None, 'handle<platform>?', None) ])) ]) @@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase): r" *handle\?<data_pipe_consumer> a;$"): parser.Parse(source2, "my_file.mojom") - source3 = """\ - struct MyStruct { - some_interface?& a; - }; - """ - with self.assertRaisesRegexp( - parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n" - r" *some_interface\?& a;$"): - parser.Parse(source3, "my_file.mojom") - def testSimpleUnion(self): """Tests a simple .mojom source that just defines a union.""" source = """\ @@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase): source1 = """\ struct MyStruct { associated MyInterface a; - associated MyInterface& b; + pending_associated_receiver<MyInterface> b; associated MyInterface? c; - associated MyInterface&? d; + pending_associated_receiver<MyInterface>? d; }; """ expected1 = ast.Mojom(None, ast.ImportList(), [ @@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase): 'MyStruct', None, ast.StructBody([ ast.StructField('a', None, None, 'asso<MyInterface>', None), - ast.StructField('b', None, None, 'asso<MyInterface&>', None), + ast.StructField('b', None, None, 'rca<MyInterface>', None), ast.StructField('c', None, None, 'asso<MyInterface>?', None), - ast.StructField('d', None, None, 'asso<MyInterface&>?', None) + ast.StructField('d', None, None, 'rca<MyInterface>?', None) ])) ]) self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) source2 = """\ interface MyInterface { - MyMethod(associated A a) =>(associated B& b); + MyMethod(associated A a) =>(pending_associated_receiver<B> b); };""" expected2 = ast.Mojom(None, ast.ImportList(), [ ast.Interface( @@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase): ast.InterfaceBody( ast.Method( 'MyMethod', None, None, - ast.ParameterList( - ast.Parameter('a', None, None, 'asso<A>')), - ast.ParameterList( - ast.Parameter('b', None, None, 'asso<B&>'))))) + ast.ParameterList(ast.Parameter('a', None, None, + 'asso<A>')), + ast.ParameterList(ast.Parameter('b', None, None, + 'rca<B>'))))) ]) self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) @@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase): r" *associated\? MyInterface& a;$"): parser.Parse(source3, "my_file.mojom") - if __name__ == "__main__": unittest.main() diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser.py index eb90c825..9693090e 100755 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Parses mojom IDL files. @@ -11,6 +11,7 @@ generate usable language bindings. """ import argparse +import builtins import codecs import errno import json @@ -19,6 +20,7 @@ import multiprocessing import os import os.path import sys +import traceback from collections import defaultdict from mojom.generate import module @@ -28,16 +30,12 @@ from mojom.parse import conditional_features # Disable this for easier debugging. -# In Python 2, subprocesses just hang when exceptions are thrown :(. -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2 +_ENABLE_MULTIPROCESSING = True -if sys.version_info < (3, 4): - _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux') -else: - # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725 - if __name__ == '__main__' and sys.platform == 'darwin': - multiprocessing.set_start_method('fork') - _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork' +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725 +if __name__ == '__main__' and sys.platform == 'darwin': + multiprocessing.set_start_method('fork') +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork' def _ResolveRelativeImportPath(path, roots): @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots): raise ValueError('"%s" does not exist in any of %s' % (path, roots)) -def _RebaseAbsolutePath(path, roots): +def RebaseAbsolutePath(path, roots): """Rewrites an absolute file path as relative to an absolute directory path in roots. @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts, # Already done. return - for dep_abspath, dep_path in dependencies[mojom_abspath]: + for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]): if dep_abspath not in loaded_modules: _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies, loaded_modules, module_metadata) @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename): def collect(metadata_filename): processed_deps.add(metadata_filename) + + # Paths in the metadata file are relative to the metadata file's dir. + metadata_dir = os.path.abspath(os.path.dirname(metadata_filename)) + + def to_abs(s): + return os.path.normpath(os.path.join(metadata_dir, s)) + with open(metadata_filename) as f: metadata = json.load(f) allowed_imports.update( - map(os.path.normcase, map(os.path.normpath, metadata['sources']))) + [os.path.normcase(to_abs(s)) for s in metadata['sources']]) for dep_metadata in metadata['deps']: + dep_metadata = to_abs(dep_metadata) if dep_metadata not in processed_deps: collect(dep_metadata) @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename): # multiprocessing helper. -def _ParseAstHelper(args): - mojom_abspath, enabled_features = args +def _ParseAstHelper(mojom_abspath, enabled_features): with codecs.open(mojom_abspath, encoding='utf-8') as f: ast = parser.Parse(f.read(), mojom_abspath) conditional_features.RemoveDisabledDefinitions(ast, enabled_features) @@ -181,8 +186,7 @@ def _ParseAstHelper(args): # multiprocessing helper. -def _SerializeHelper(args): - mojom_abspath, mojom_path = args +def _SerializeHelper(mojom_abspath, mojom_path): module_path = os.path.join(_SerializeHelper.output_root_path, _GetModuleFilename(mojom_path)) module_dir = os.path.dirname(module_path) @@ -199,12 +203,33 @@ def _SerializeHelper(args): _SerializeHelper.loaded_modules[mojom_abspath].Dump(f) -def _Shard(target_func, args, processes=None): - args = list(args) +class _ExceptionWrapper: + def __init__(self): + # Do not capture exception object to ensure pickling works. + self.formatted_trace = traceback.format_exc() + + +class _FuncWrapper: + """Marshals exceptions and spreads args.""" + + def __init__(self, func): + self._func = func + + def __call__(self, args): + # multiprocessing does not gracefully handle excptions. + # https://crbug.com/1219044 + try: + return self._func(*args) + except: # pylint: disable=bare-except + return _ExceptionWrapper() + + +def _Shard(target_func, arg_list, processes=None): + arg_list = list(arg_list) if processes is None: processes = multiprocessing.cpu_count() # Seems optimal to have each process perform at least 2 tasks. - processes = min(processes, len(args) // 2) + processes = min(processes, len(arg_list) // 2) if sys.platform == 'win32': # TODO(crbug.com/1190269) - we can't use more than 56 @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None): # Don't spin up processes unless there is enough work to merit doing so. if not _ENABLE_MULTIPROCESSING or processes < 2: - for result in map(target_func, args): - yield result + for arg_tuple in arg_list: + yield target_func(*arg_tuple) return pool = multiprocessing.Pool(processes=processes) try: - for result in pool.imap_unordered(target_func, args): + wrapped_func = _FuncWrapper(target_func) + for result in pool.imap_unordered(wrapped_func, arg_list): + if isinstance(result, _ExceptionWrapper): + sys.stderr.write(result.formatted_trace) + sys.exit(1) yield result finally: pool.close() @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None): def _ParseMojoms(mojom_files, input_root_paths, output_root_path, + module_root_paths, enabled_features, module_metadata, allowed_imports=None): @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files, are based on the mojom's relative path, rebased onto this path. Additionally, the script expects this root to contain already-generated modules for any transitive dependencies not listed in mojom_files. + module_root_paths: A list of absolute filesystem paths which contain + already-generated modules for any non-transitive dependencies. enabled_features: A list of enabled feature names, controlling which AST - nodes are filtered by [EnableIf] attributes. + nodes are filtered by [EnableIf] or [EnableIfNot] attributes. module_metadata: A list of 2-tuples representing metadata key-value pairs to attach to each compiled module output. @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files, loaded_modules = {} input_dependencies = defaultdict(set) mojom_files_to_parse = dict((os.path.normcase(abs_path), - _RebaseAbsolutePath(abs_path, input_root_paths)) + RebaseAbsolutePath(abs_path, input_root_paths)) for abs_path in mojom_files) abs_paths = dict( (path, abs_path) for abs_path, path in mojom_files_to_parse.items()) @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files, loaded_mojom_asts[mojom_abspath] = ast logging.info('Processing dependencies') - for mojom_abspath, ast in loaded_mojom_asts.items(): + for mojom_abspath, ast in sorted(loaded_mojom_asts.items()): invalid_imports = [] for imp in ast.import_list: import_abspath = _ResolveRelativeImportPath(imp.import_filename, @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files, # be parsed and have a module file sitting in a corresponding output # location. module_path = _GetModuleFilename(imp.import_filename) - module_abspath = _ResolveRelativeImportPath(module_path, - [output_root_path]) + module_abspath = _ResolveRelativeImportPath( + module_path, module_root_paths + [output_root_path]) with open(module_abspath, 'rb') as module_file: loaded_modules[import_abspath] = module.Module.Load(module_file) @@ -371,6 +403,15 @@ already present in the provided output root.""") 'ROOT is also searched for existing modules of any transitive imports ' 'which were not included in the set of inputs.') arg_parser.add_argument( + '--module-root', + default=[], + action='append', + metavar='ROOT', + dest='module_root_paths', + help='Adds ROOT to the set of root paths to search for existing modules ' + 'of non-transitive imports. Provided root paths are always searched in ' + 'order from longest absolute path to shortest.') + arg_parser.add_argument( '--mojoms', nargs='+', dest='mojom_files', @@ -396,9 +437,9 @@ already present in the provided output root.""") help='Enables a named feature when parsing the given mojoms. Features ' 'are identified by arbitrary string values. Specifying this flag with a ' 'given FEATURE name will cause the parser to process any syntax elements ' - 'tagged with an [EnableIf=FEATURE] attribute. If this flag is not ' - 'provided for a given FEATURE, such tagged elements are discarded by the ' - 'parser and will not be present in the compiled output.') + 'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this ' + 'flag is not provided for a given FEATURE, such tagged elements are ' + 'discarded by the parser and will not be present in the compiled output.') arg_parser.add_argument( '--check-imports', dest='build_metadata_filename', @@ -436,6 +477,7 @@ already present in the provided output root.""") mojom_files = list(map(os.path.abspath, args.mojom_files)) input_roots = list(map(os.path.abspath, args.input_root_paths)) output_root = os.path.abspath(args.output_root_path) + module_roots = list(map(os.path.abspath, args.module_root_paths)) if args.build_metadata_filename: allowed_imports = _CollectAllowedImportsFromBuildMetadata( @@ -445,13 +487,16 @@ already present in the provided output root.""") module_metadata = list( map(lambda kvp: tuple(kvp.split('=')), args.module_metadata)) - _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features, - module_metadata, allowed_imports) + _ParseMojoms(mojom_files, input_roots, output_root, module_roots, + args.enabled_features, module_metadata, allowed_imports) logging.info('Finished') - # Exit without running GC, which can save multiple seconds due the large - # number of object created. - os._exit(0) if __name__ == '__main__': Run(sys.argv[1:]) + # Exit without running GC, which can save multiple seconds due to the large + # number of object created. But flush is necessary as os._exit doesn't do + # that. + sys.stdout.flush() + sys.stderr.flush() + os._exit(0) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py index e213fbfa..f0ee6966 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase): resolution, and module serialization and deserialization.""" def __init__(self, method_name): - super(MojomParserTestCase, self).__init__(method_name) + super().__init__(method_name) self._temp_dir = None def setUp(self): @@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase): self.ParseMojoms([filename]) m = self.LoadModule(filename) definitions = {} - for kinds in (m.enums, m.structs, m.unions, m.interfaces): + for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features): for kind in kinds: definitions[kind.mojom_name] = kind return definitions diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py index a93f34ba..353a2b6e 100644 --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py @@ -1,7 +1,9 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import json + from mojom_parser_test_case import MojomParserTestCase @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase): c = 'c.mojom' c_metadata = 'out/c.build_metadata' self.WriteFile(a_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) + json.dumps({ + "sources": [self.GetPath(a)], + "deps": [] + })) self.WriteFile( b_metadata, - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b), - self.GetPath(a_metadata))) + json.dumps({ + "sources": [self.GetPath(b)], + "deps": [self.GetPath(a_metadata)] + })) self.WriteFile( c_metadata, - '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c), - self.GetPath(b_metadata))) + json.dumps({ + "sources": [self.GetPath(c)], + "deps": [self.GetPath(b_metadata)] + })) self.WriteFile(a, """\ module a; struct Bar {};""") @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase): b = 'b.mojom' b_metadata = 'out/b.build_metadata' self.WriteFile(a_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a)) + json.dumps({ + "sources": [self.GetPath(a)], + "deps": [] + })) self.WriteFile(b_metadata, - '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b)) + json.dumps({ + "sources": [self.GetPath(b)], + "deps": [] + })) self.WriteFile(a, """\ module a; struct Bar {};""") diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py index d45ec586..d10d69c6 100644 --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py new file mode 100644 index 00000000..6b2525e5 --- /dev/null +++ b/utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py @@ -0,0 +1,44 @@ +# Copyright 2022 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from mojom_parser_test_case import MojomParserTestCase + + +class UnionTest(MojomParserTestCase): + """Tests union parsing behavior.""" + + def testExtensibleMustHaveDefault(self): + """Verifies that extensible unions must have a default field.""" + mojom = 'foo.mojom' + self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };') + with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'): + self.ParseMojoms([mojom]) + + def testExtensibleSingleDefault(self): + """Verifies that extensible unions must not have multiple default fields.""" + mojom = 'foo.mojom' + self.WriteFile( + mojom, """\ + module foo; + [Extensible] union U { + [Default] bool x; + [Default] bool y; + }; + """) + with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'): + self.ParseMojoms([mojom]) + + def testExtensibleDefaultTypeValid(self): + """Verifies that an extensible union's default field must be nullable or + integral type.""" + mojom = 'foo.mojom' + self.WriteFile( + mojom, """\ + module foo; + [Extensible] union U { + [Default] handle<message_pipe> p; + }; + """) + with self.assertRaisesRegexp(Exception, 'must be nullable or integral'): + self.ParseMojoms([mojom]) diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py index 65db4dc9..45e45ec5 100644 --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py +++ b/utils/codegen/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium Authors. All rights reserved. +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase): checker = module.BackwardCompatibilityChecker() compatibility_map = {} - for name in old.keys(): - compatibility_map[name] = checker.IsBackwardCompatible( - new[name], old[name]) + for name in old: + try: + compatibility_map[name] = checker.IsBackwardCompatible( + new[name], old[name]) + except Exception: + compatibility_map[name] = False return compatibility_map def assertBackwardCompatible(self, old_mojom, new_mojom): @@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase): """Adding a value to an existing version is not allowed, even if the old enum was marked [Extensible]. Note that it is irrelevant whether or not the new enum is marked [Extensible].""" - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', - 'enum E { kFoo, kBar, kBaz };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kFoo, kBar };', - '[Extensible] enum E { kFoo, kBar, kBaz };') + '[Extensible] enum E { [Default] kFoo, kBar };', + 'enum E { kFoo, kBar, kBaz };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { [Default] kFoo, kBar };', + '[Extensible] enum E { [Default] kFoo, kBar, kBaz };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kFoo, [MinVersion=1] kBar };', + '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };', 'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };') def testEnumValueRemoval(self): """Removal of an enum value is never valid even for [Extensible] enums.""" self.assertNotBackwardCompatible('enum E { kFoo, kBar };', 'enum E { kFoo };') - self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };', - '[Extensible] enum E { kFoo };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB };', - '[Extensible] enum E { kA, };') + '[Extensible] enum E { [Default] kFoo, kBar };', + '[Extensible] enum E { [Default] kFoo };') + self.assertNotBackwardCompatible( + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };', + '[Extensible] enum E { [Default] kA, };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };', - '[Extensible] enum E { kA, [MinVersion=1] kB };') + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB, + [MinVersion=1] kZ };""", + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };') def testNewExtensibleEnumValueWithMinVersion(self): """Adding a new and properly [MinVersion]'d value to an [Extensible] enum is a backward-compatible change. Note that it is irrelevant whether or not the new enum is marked [Extensible].""" - self.assertBackwardCompatible('[Extensible] enum E { kA, kB };', + self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };', 'enum E { kA, kB, [MinVersion=1] kC };') self.assertBackwardCompatible( - '[Extensible] enum E { kA, kB };', - '[Extensible] enum E { kA, kB, [MinVersion=1] kC };') + '[Extensible] enum E { [Default] kA, kB };', + '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };') self.assertBackwardCompatible( - '[Extensible] enum E { kA, [MinVersion=1] kB };', - '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };') + '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB, + [MinVersion=2] kC };""") def testRenameEnumValue(self): """Renaming an enum value does not affect backward-compatibility. Only @@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase): 'struct S {}; struct T { S s; };', 'struct S { [MinVersion=1] int32 x; }; struct T { S s; };') self.assertBackwardCompatible( - '[Extensible] enum E { kA }; struct S { E e; };', - '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };') + '[Extensible] enum E { [Default] kA }; struct S { E e; };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB }; + struct S { E e; };""") self.assertNotBackwardCompatible( 'struct S {}; struct T { S s; };', 'struct S { int32 x; }; struct T { S s; };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA }; struct S { E e; };', - '[Extensible] enum E { kA, kB }; struct S { E e; };') + '[Extensible] enum E { [Default] kA }; struct S { E e; };', + '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };') def testNewStructFieldWithInvalidMinVersion(self): """Adding a new field using an existing MinVersion breaks backward- @@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase): 'struct S {}; union U { S s; };', 'struct S { [MinVersion=1] int32 x; }; union U { S s; };') self.assertBackwardCompatible( - '[Extensible] enum E { kA }; union U { E e; };', - '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };') + '[Extensible] enum E { [Default] kA }; union U { E e; };', + """[Extensible] enum E { + [Default] kA, + [MinVersion=1] kB }; + union U { E e; };""") self.assertNotBackwardCompatible( 'struct S {}; union U { S s; };', 'struct S { int32 x; }; union U { S s; };') self.assertNotBackwardCompatible( - '[Extensible] enum E { kA }; union U { E e; };', - '[Extensible] enum E { kA, kB }; union U { E e; };') + '[Extensible] enum E { [Default] kA }; union U { E e; };', + '[Extensible] enum E { [Default] kA, kB }; union U { E e; };') def testNewUnionFieldWithInvalidMinVersion(self): """Adding a new field using an existing MinVersion breaks backward- diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/codegen/ipc/mojo/public/tools/run_all_python_unittests.py index b2010958..98bce18c 100755 --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py +++ b/utils/codegen/ipc/mojo/public/tools/run_all_python_unittests.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. +#!/usr/bin/env python3 +# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,11 +8,13 @@ import sys _TOOLS_DIR = os.path.dirname(__file__) _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom') +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings') _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir, os.path.pardir) # Ensure that the mojom library is discoverable. sys.path.append(_MOJOM_DIR) +sys.path.append(_BINDINGS_DIR) # Help Python find typ in //third_party/catapult/third_party/typ/ sys.path.append( @@ -21,7 +23,7 @@ import typ def Main(): - return typ.main(top_level_dir=_MOJOM_DIR) + return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR]) if __name__ == '__main__': diff --git a/utils/ipc/parser.py b/utils/codegen/ipc/parser.py index 231a3266..8e70322d 100755 --- a/utils/ipc/parser.py +++ b/utils/codegen/ipc/parser.py @@ -4,14 +4,11 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# parser.py - Run mojo parser with python3 +# Run mojo parser with python3 import os import sys -# TODO set sys.pycache_prefix for >= python3.8 -sys.dont_write_bytecode = True - # Make sure that mojom_parser.py can import mojom sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/mojom') diff --git a/utils/ipc/tools/README b/utils/codegen/ipc/tools/README index d5c24fc3..961cabd2 100644 --- a/utils/ipc/tools/README +++ b/utils/codegen/ipc/tools/README @@ -1,4 +1,4 @@ # SPDX-License-Identifier: CC0-1.0 -Files in this directory are imported from 9c138d992bfc of Chromium. Do not +Files in this directory are imported from 9be4263648d7 of Chromium. Do not modify them manually. diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/codegen/ipc/tools/diagnosis/crbug_1001171.py index 478fb8c1..40900d10 100644 --- a/utils/ipc/tools/diagnosis/crbug_1001171.py +++ b/utils/codegen/ipc/tools/diagnosis/crbug_1001171.py @@ -1,4 +1,4 @@ -# Copyright 2019 The Chromium Authors. All rights reserved. +# Copyright 2019 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/utils/codegen/meson.build b/utils/codegen/meson.build new file mode 100644 index 00000000..8d1c6908 --- /dev/null +++ b/utils/codegen/meson.build @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: CC0-1.0 + +## Code generation + +py_build_env = environment() +# \todo Investigate usage of PYTHONPYCACHEPREFIX for Python >= 3.8 +py_build_env.set('PYTHONDONTWRITEBYTECODE', '1') +py_build_env.prepend('PYTHONPATH', meson.current_source_dir()) + +py_modules += ['jinja2', 'yaml'] + +gen_controls = files('gen-controls.py') +gen_formats = files('gen-formats.py') +gen_gst_controls = files('gen-gst-controls.py') +gen_header = files('gen-header.sh') +gen_ipa_pub_key = files('gen-ipa-pub-key.py') +gen_tracepoints = files('gen-tp-header.py') + +py_mod_controls = files('controls.py') + +subdir('ipc') diff --git a/utils/gen-controls.py b/utils/gen-controls.py deleted file mode 100755 index 1075ae30..00000000 --- a/utils/gen-controls.py +++ /dev/null @@ -1,313 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2019, Google Inc. -# -# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> -# -# gen-controls.py - Generate control definitions from YAML - -import argparse -from functools import reduce -import operator -import string -import sys -import yaml - - -class ControlEnum(object): - def __init__(self, data): - self.__data = data - - @property - def description(self): - """The enum description""" - return self.__data.get('description') - - @property - def name(self): - """The enum name""" - return self.__data.get('name') - - @property - def value(self): - """The enum value""" - return self.__data.get('value') - - -class Control(object): - def __init__(self, name, data): - self.__name = name - self.__data = data - self.__enum_values = None - self.__size = None - - enum_values = data.get('enum') - if enum_values is not None: - self.__enum_values = [ControlEnum(enum) for enum in enum_values] - - size = self.__data.get('size') - if size is not None: - if len(size) == 0: - raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension') - - # Compute the total number of elements in the array. If any of the - # array dimension is a string, the array is variable-sized. - num_elems = 1 - for dim in size: - if type(dim) is str: - num_elems = 0 - break - - dim = int(dim) - if dim <= 0: - raise RuntimeError(f'Control `{self.__name}` size must have positive values only') - - num_elems *= dim - - self.__size = num_elems - - @property - def description(self): - """The control description""" - return self.__data.get('description') - - @property - def enum_values(self): - """The enum values, if the control is an enumeration""" - if self.__enum_values is None: - return - for enum in self.__enum_values: - yield enum - - @property - def is_enum(self): - """Is the control an enumeration""" - return self.__enum_values is not None - - @property - def is_draft(self): - """Is the control a draft control""" - return self.__data.get('draft') is not None - - @property - def name(self): - """The control name (CamelCase)""" - return self.__name - - @property - def q_name(self): - """The control name, qualified with a namespace""" - ns = 'draft::' if self.is_draft else '' - return ns + self.__name - - @property - def type(self): - typ = self.__data.get('type') - size = self.__data.get('size') - - if typ == 'string': - return 'std::string' - - if self.__size is None: - return typ - - if self.__size: - return f"Span<const {typ}, {self.__size}>" - else: - return f"Span<const {typ}>" - - -def snake_case(s): - return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_') - - -def format_description(description): - description = description.strip('\n').split('\n') - description[0] = '\\brief ' + description[0] - return '\n'.join([(line and ' * ' or ' *') + line for line in description]) - - -def generate_cpp(controls): - enum_doc_start_template = string.Template('''/** - * \\enum ${name}Enum - * \\brief Supported ${name} values''') - enum_doc_value_template = string.Template(''' * \\var ${value} -${description}''') - doc_template = string.Template('''/** - * \\var ${name} -${description} - */''') - def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, "${name}");') - enum_values_doc = string.Template('''/** - * \\var ${name}Values - * \\brief List of all $name supported values - */''') - enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''') - enum_values_values = string.Template('''\tstatic_cast<int32_t>(${name}),''') - - ctrls_doc = [] - ctrls_def = [] - draft_ctrls_doc = [] - draft_ctrls_def = [] - ctrls_map = [] - - for ctrl in controls: - id_name = snake_case(ctrl.name).upper() - - info = { - 'name': ctrl.name, - 'type': ctrl.type, - 'description': format_description(ctrl.description), - 'id_name': id_name, - } - - target_doc = ctrls_doc - target_def = ctrls_def - if ctrl.is_draft: - target_doc = draft_ctrls_doc - target_def = draft_ctrls_def - - if ctrl.is_enum: - enum_doc = [] - enum_doc.append(enum_doc_start_template.substitute(info)) - - num_entries = 0 - for enum in ctrl.enum_values: - value_info = { - 'name': ctrl.name, - 'value': enum.name, - 'description': format_description(enum.description), - } - enum_doc.append(enum_doc_value_template.substitute(value_info)) - num_entries += 1 - - enum_doc = '\n *\n'.join(enum_doc) - enum_doc += '\n */' - target_doc.append(enum_doc) - - values_info = { - 'name': info['name'], - 'size': num_entries, - } - target_doc.append(enum_values_doc.substitute(values_info)) - target_def.append(enum_values_start.substitute(values_info)) - for enum in ctrl.enum_values: - value_info = { - 'name': enum.name - } - target_def.append(enum_values_values.substitute(value_info)) - target_def.append("};") - - target_doc.append(doc_template.substitute(info)) - target_def.append(def_template.substitute(info)) - - ctrls_map.append('\t{ ' + id_name + ', &' + ctrl.q_name + ' },') - - return { - 'controls_doc': '\n\n'.join(ctrls_doc), - 'controls_def': '\n'.join(ctrls_def), - 'draft_controls_doc': '\n\n'.join(draft_ctrls_doc), - 'draft_controls_def': '\n\n'.join(draft_ctrls_def), - 'controls_map': '\n'.join(ctrls_map), - } - - -def generate_h(controls): - enum_template_start = string.Template('''enum ${name}Enum {''') - enum_value_template = string.Template('''\t${name} = ${value},''') - enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''') - template = string.Template('''extern const Control<${type}> ${name};''') - - ctrls = [] - draft_ctrls = [] - ids = [] - id_value = 1 - - for ctrl in controls: - id_name = snake_case(ctrl.name).upper() - - ids.append('\t' + id_name + ' = ' + str(id_value) + ',') - - info = { - 'name': ctrl.name, - 'type': ctrl.type, - } - - target_ctrls = ctrls - if ctrl.is_draft: - target_ctrls = draft_ctrls - - if ctrl.is_enum: - target_ctrls.append(enum_template_start.substitute(info)) - - num_entries = 0 - for enum in ctrl.enum_values: - value_info = { - 'name': enum.name, - 'value': enum.value, - } - target_ctrls.append(enum_value_template.substitute(value_info)) - num_entries += 1 - target_ctrls.append("};") - - values_info = { - 'name': info['name'], - 'size': num_entries, - } - target_ctrls.append(enum_values_template.substitute(values_info)) - - target_ctrls.append(template.substitute(info)) - id_value += 1 - - return { - 'ids': '\n'.join(ids), - 'controls': '\n'.join(ctrls), - 'draft_controls': '\n'.join(draft_ctrls) - } - - -def fill_template(template, data): - - template = open(template, 'rb').read() - template = template.decode('utf-8') - template = string.Template(template) - return template.substitute(data) - - -def main(argv): - - # Parse command line arguments - 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:]) - - data = open(args.input, 'rb').read() - controls = yaml.safe_load(data)['controls'] - controls = [Control(*ctrl.popitem()) for ctrl in controls] - - if args.template.endswith('.cpp.in'): - data = generate_cpp(controls) - elif args.template.endswith('.h.in'): - data = generate_h(controls) - else: - raise RuntimeError('Unknown template type') - - data = fill_template(args.template, data) - - if args.output: - output = open(args.output, 'wb') - output.write(data.encode('utf-8')) - output.close() - else: - sys.stdout.write(data) - - return 0 - - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/utils/gen-debug-controls.py b/utils/gen-debug-controls.py new file mode 100755 index 00000000..272597f4 --- /dev/null +++ b/utils/gen-debug-controls.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2024, Google Inc. +# +# Author: Stefan Klug <stefan.klug@ideasonboard.com> +# +# This script looks for occurrences of the debug metadata controls in the source +# tree and updates src/libcamera/control_ids_debug.yaml accordingly. It is meant +# to be used during development to ease updating of the yaml file while +# debugging. + +import argparse +import logging +import os +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + +try: + import ruamel.yaml as ruyaml +except: + logger.error( + f'Failed to import ruamel.yaml. Please install the ruamel.yaml package.') + sys.exit(1) + +@dataclass +class FoundMatch: + file: os.PathLike + whole_match: str + line: int + type: str + name: str + size: str = None + + +def get_control_name(control): + k = list(control.keys()) + if len(k) != 1: + raise Exception(f"Can't handle control entry with {len(k)} keys") + return k[0] + + +def find_debug_controls(dir): + extensions = ['.cpp', '.h'] + files = [p for p in dir.rglob('*') if p.suffix in extensions] + + # The following regex was tested on + # set<Span<type>>( controls::debug::something , static_cast<type>(var) ) + # set<>( controls::debug::something , static_cast<type>(var) ) + # set( controls::debug::something , static_cast<type> (var) ) + exp = re.compile(r'set' # set function + r'(?:\<((?:[^)(])*)\>)?' # followed by a optional template param + r'\(\s*controls::debug::(\w+)\s*,' # referencing a debug control + ) + matches = [] + for p in files: + with p.open('r') as f: + for idx, line in enumerate(f): + match = exp.search(line) + if match: + m = FoundMatch(file=p, line=idx, type=match.group(1), + name=match.group(2), whole_match=match.group(0)) + if m.type is not None and m.type.startswith('Span'): + # Simple span type detection treating the last word + # inside <> as type. + r = re.match(r'Span<(?:.*\s+)(.*)>', m.type) + m.type = r.group(1) + m.size = '[n]' + matches.append(m) + return matches + + +def main(argv): + parser = argparse.ArgumentParser( + description='Automatically updates control_ids_debug.yaml') + parser.parse_args(argv[1:]) + + yaml = ruyaml.YAML() + root_dir = Path(__file__).resolve().parent.parent + ctrl_file = root_dir.joinpath('src/libcamera/control_ids_debug.yaml') + + matches = find_debug_controls(root_dir.joinpath('src')) + + doc = yaml.load(ctrl_file) + + controls = doc['controls'] + + # Create a map of names in the existing yaml for easier updating. + controls_map = {} + for control in controls: + for k, v in control.items(): + controls_map[k] = v + + obsolete_names = list(controls_map.keys()) + + for m in matches: + if not m.type: + p = m.file.relative_to(Path.cwd(), walk_up=True) + logger.warning( + f'{p}:{m.line + 1}: Failed to deduce type from {m.whole_match} ... skipping') + continue + + p = m.file.relative_to(root_dir) + desc = {'type': m.type, + 'direction': 'out', + 'description': f'Debug control {m.name} found in {p}:{m.line}'} + if m.size is not None: + desc['size'] = m.size + + if m.name in controls_map: + # Can't use == for modified check because of the special yaml dicts. + update_needed = False + if list(controls_map[m.name].keys()) != list(desc.keys()): + update_needed = True + else: + for k, v in controls_map[m.name].items(): + if v != desc[k]: + update_needed = True + break + + if update_needed: + logger.info(f"Update control '{m.name}'") + controls_map[m.name].clear() + controls_map[m.name].update(desc) + + obsolete_names.remove(m.name) + else: + logger.info(f"Add control '{m.name}'") + insert_before = len(controls) + for idx, control in enumerate(controls): + if get_control_name(control).lower() > m.name.lower(): + insert_before = idx + break + controls.insert(insert_before, {m.name: desc}) + + # Remove elements from controls without recreating the list (to keep + # comments etc.). + idx = 0 + while idx < len(controls): + name = get_control_name(controls[idx]) + if name in obsolete_names: + logger.info(f"Remove control '{name}'") + controls.pop(idx) + else: + idx += 1 + + with ctrl_file.open('w') as f: + # Ruyaml looses the header. + f.write(("# SPDX-License-Identifier: LGPL-2.1-or-later\n" + "#\n" + "# This file was generated by utils/gen-debug-controls.py\n" + "#\n")) + yaml.dump(doc, f) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/utils/gen-ipa-priv-key.sh b/utils/gen-ipa-priv-key.sh index 919751f2..2ca7b883 100755 --- a/utils/gen-ipa-priv-key.sh +++ b/utils/gen-ipa-priv-key.sh @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# gen-ipa-priv-key.sh - Generate an RSA private key to sign IPA modules +# Generate an RSA private key to sign IPA modules key="$1" diff --git a/utils/gen-version.sh b/utils/gen-version.sh index e1f7ca7b..1b818e9e 100755 --- a/utils/gen-version.sh +++ b/utils/gen-version.sh @@ -42,7 +42,7 @@ if [ -z "$build_dir" ] || (echo "$build_dir" | grep -q "$src_dir") then git update-index --refresh > /dev/null 2>&1 fi -git diff-index --quiet HEAD || version="$version-dirty ($(date --iso-8601=seconds))" +git diff-index --quiet HEAD || version="$version-dirty ($(date +%Y-%m-%dT%H:%M:%S%Z))" # If a project version is provided, use it to replace the version number. if [ -n "$project_version" ] diff --git a/utils/hooks/pre-push b/utils/hooks/pre-push index 90ffdf6f..68dcbd0c 100755 --- a/utils/hooks/pre-push +++ b/utils/hooks/pre-push @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # SPDX-License-Identifier: GPL-2.0-or-later @@ -61,17 +61,17 @@ do msg=$(git cat-file commit "$commit") # 1. The commit message shall not contain a local changelog. - if echo "$msg" | grep -q '^--- *$' + if echo -E "$msg" | grep -q '^--- *$' then echo >&2 "Found local changelog in commit $commit" errors=$((errors+1)) fi # 2. The commit message shall have Signed-off-by lines - # corresponding the committer and the author. + # corresponding the committer, author, and all co-developers. committer=$(echo "$msg" | grep '^committer ' | head -1 | \ cut -d ' ' -f 2- | rev | cut -d ' ' -f 3- | rev) - if ! echo "$msg" | grep -F -q "Signed-off-by: ${committer}" + if ! echo -E "$msg" | grep -F -q "Signed-off-by: ${committer}" then echo >&2 "Missing committer Signed-off-by in commit $commit" errors=$((errors+1)) @@ -79,21 +79,30 @@ do author=$(echo "$msg" | grep '^author ' | head -1 | \ cut -d ' ' -f 2- | rev | cut -d ' ' -f 3- | rev) - if ! echo "$msg" | grep -F -q "Signed-off-by: ${author}" + if ! echo -E "$msg" | grep -F -q "Signed-off-by: ${author}" then echo >&2 "Missing author Signed-off-by in commit $commit" errors=$((errors+1)) fi + while read -r codev + do + if ! echo -E "$msg" | grep -F -q "Signed-off-by: ${codev}" + then + echo >&2 "Missing co-developer '${codev}' Signed-off-by in commit $commit" + errors=$((errors+1)) + fi + done < <(echo "$msg" | grep '^Co-developed-by: ' | cut -d ' ' -f 2-) + # 3. A Reviewed-by or Acked-by is required. - if ! echo "$msg" | grep -q '^\(Reviewed\|Acked\)-by: ' + if ! echo -E "$msg" | grep -q '^\(Reviewed\|Acked\)-by: ' then echo >&2 "No Reviewed-by or Acked-by in commit $commit" errors=$((errors+1)) fi # 4. The commit message shall not contain a Change-Id. - if echo "$msg" | grep -q '^Change-Id:' + if echo -E "$msg" | grep -q '^Change-Id:' then echo >&2 "Found Change-Id in commit $commit" errors=$((errors+1)) diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni deleted file mode 100644 index d8a13874..00000000 --- a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -_typemap_imports = [ - "//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni", - "//chrome/common/importer/typemaps.gni", - "//chrome/common/media_router/mojom/typemaps.gni", - "//chrome/typemaps.gni", - "//chromecast/typemaps.gni", - "//chromeos/typemaps.gni", - "//chromeos/components/multidevice/mojom/typemaps.gni", - "//chromeos/services/cros_healthd/public/mojom/typemaps.gni", - "//chromeos/services/device_sync/public/mojom/typemaps.gni", - "//chromeos/services/network_config/public/mojom/typemaps.gni", - "//chromeos/services/secure_channel/public/mojom/typemaps.gni", - "//components/arc/mojom/typemaps.gni", - "//components/chromeos_camera/common/typemaps.gni", - "//components/services/storage/public/cpp/filesystem/typemaps.gni", - "//components/sync/mojom/typemaps.gni", - "//components/typemaps.gni", - "//content/browser/typemaps.gni", - "//content/public/common/typemaps.gni", - "//sandbox/mac/mojom/typemaps.gni", - "//services/media_session/public/cpp/typemaps.gni", - "//services/proxy_resolver/public/cpp/typemaps.gni", - "//services/resource_coordinator/public/cpp/typemaps.gni", - "//services/service_manager/public/cpp/typemaps.gni", - "//services/tracing/public/mojom/typemaps.gni", -] - -_typemaps = [] -foreach(typemap_import, _typemap_imports) { - # Avoid reassignment error by assigning to empty scope first. - _imported = { - } - _imported = read_file(typemap_import, "scope") - _typemaps += _imported.typemaps -} - -typemaps = [] -foreach(typemap, _typemaps) { - typemaps += [ - { - filename = typemap - config = read_file(typemap, "scope") - }, - ] -} - -component_macro_suffix = "" diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py deleted file mode 100644 index a978901b..00000000 --- a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2019 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import os -import sys -import argparse - -_HERE_PATH = os.path.dirname(__file__) -_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..')) - -sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node')) -import node -import node_modules - -def main(argv): - parser = argparse.ArgumentParser() - parser.add_argument('--tsconfig_path', required=True) - args = parser.parse_args(argv) - - result = node.RunNode([node_modules.PathToTypescript()] + - ['--project', args.tsconfig_path]) - if len(result) != 0: - raise RuntimeError('Failed to compile Typescript: \n%s' % result) - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py deleted file mode 100755 index 7ac4af5f..00000000 --- a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -from __future__ import print_function - -import sys - -# This utility converts mojom dependencies into their corresponding typemap -# paths and formats them to be consumed by generate_type_mappings.py. - - -def FormatTypemap(typemap_filename): - # A simple typemap is valid Python with a minor alteration. - with open(typemap_filename) as f: - typemap_content = f.read().replace('=\n', '=') - typemap = {} - exec typemap_content in typemap - - for header in typemap.get('public_headers', []): - yield 'public_headers=%s' % header - for header in typemap.get('traits_headers', []): - yield 'traits_headers=%s' % header - for header in typemap.get('type_mappings', []): - yield 'type_mappings=%s' % header - - -def main(): - typemaps = sys.argv[1:] - print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap)) - for typemap in typemaps)) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py deleted file mode 100755 index 15f0e3ba..00000000 --- a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python -# Copyright 2020 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -"""Downgrades *.mojom files to the old mojo types for remotes and receivers.""" - -import argparse -import fnmatch -import os -import re -import shutil -import sys -import tempfile - -# List of patterns and replacements to match and use against the contents of a -# mojo file. Each replacement string will be used with Python string's format() -# function, so the '{}' substring is used to mark where the mojo type should go. -_MOJO_REPLACEMENTS = { - r'pending_remote': r'{}', - r'pending_receiver': r'{}&', - r'pending_associated_remote': r'associated {}', - r'pending_associated_receiver': r'associated {}&', -} - -# Pre-compiled regular expression that matches against any of the replacements. -_REGEXP_PATTERN = re.compile( - r'|'.join( - ['{}\s*<\s*(.*?)\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]), - flags=re.DOTALL) - - -def ReplaceFunction(match_object): - """Returns the right replacement for the string matched against the regexp.""" - for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1): - if match_object.group(0).startswith(match): - return repl.format(match_object.group(index)) - - -def DowngradeFile(path, output_dir=None): - """Downgrades the mojom file specified by |path| to the old mojo types. - - Optionally pass |output_dir| to place the result under a separate output - directory, preserving the relative path to the file included in |path|. - """ - # Use a temporary file to dump the new contents after replacing the patterns. - with open(path) as src_mojo_file: - with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file: - tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read()) - tmp_mojo_file.write(tmp_contents) - - # Files should be placed in the desired output directory - if output_dir: - output_filepath = os.path.join(output_dir, os.path.basename(path)) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - else: - output_filepath = path - - # Write the new contents preserving the original file's attributes. - shutil.copystat(path, tmp_mojo_file.name) - shutil.move(tmp_mojo_file.name, output_filepath) - - # Make sure to "touch" the new file so that access, modify and change times - # are always newer than the source file's, otherwise Modify time will be kept - # as per the call to shutil.copystat(), causing unnecessary generations of the - # output file in subsequent builds due to ninja considering it dirty. - os.utime(output_filepath, None) - - -def DowngradeDirectory(path, output_dir=None): - """Downgrades mojom files inside directory |path| to the old mojo types. - - Optionally pass |output_dir| to place the result under a separate output - directory, preserving the relative path to the file included in |path|. - """ - # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7 - mojom_filepaths = [] - for dir_path, _, filenames in os.walk(path): - for filename in fnmatch.filter(filenames, "*mojom"): - mojom_filepaths.append(os.path.join(dir_path, filename)) - - for path in mojom_filepaths: - absolute_dirpath = os.path.dirname(os.path.abspath(path)) - if output_dir: - dest_dirpath = output_dir + absolute_dirpath - else: - dest_dirpath = absolute_dirpath - DowngradeFile(path, dest_dirpath) - - -def DowngradePath(src_path, output_dir=None): - """Downgrades the mojom files pointed by |src_path| to the old mojo types. - - Optionally pass |output_dir| to place the result under a separate output - directory, preserving the relative path to the file included in |path|. - """ - if os.path.isdir(src_path): - DowngradeDirectory(src_path, output_dir) - elif os.path.isfile(src_path): - DowngradeFile(src_path, output_dir) - else: - print(">>> {} not pointing to a valid file or directory".format(src_path)) - sys.exit(1) - - -def main(): - parser = argparse.ArgumentParser( - description="Downgrade *.mojom files to use the old mojo types.") - parser.add_argument( - "srcpath", help="path to the file or directory to apply the conversion") - parser.add_argument( - "--outdir", help="the directory to place the converted file(s) under") - args = parser.parse_args() - - DowngradePath(args.srcpath, args.outdir) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py deleted file mode 100644 index 0dfd996e..00000000 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -"""Resolves the values used for constants and enums.""" - -from itertools import ifilter - -from mojom.generate import module as mojom - - -def ResolveConstants(module, expression_to_text): - in_progress = set() - computed = set() - - def GetResolvedValue(named_value): - assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue)) - if isinstance(named_value, mojom.EnumValue): - field = next( - ifilter(lambda field: field.name == named_value.name, - named_value.enum.fields), None) - if not field: - raise RuntimeError( - 'Unable to get computed value for field %s of enum %s' % - (named_value.name, named_value.enum.name)) - if field not in computed: - ResolveEnum(named_value.enum) - return field.resolved_value - else: - ResolveConstant(named_value.constant) - named_value.resolved_value = named_value.constant.resolved_value - return named_value.resolved_value - - def ResolveConstant(constant): - if constant in computed: - return - if constant in in_progress: - raise RuntimeError('Circular dependency for constant: %s' % constant.name) - in_progress.add(constant) - if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)): - resolved_value = GetResolvedValue(constant.value) - else: - resolved_value = expression_to_text(constant.value) - constant.resolved_value = resolved_value - in_progress.remove(constant) - computed.add(constant) - - def ResolveEnum(enum): - def ResolveEnumField(enum, field, default_value): - if field in computed: - return - if field in in_progress: - raise RuntimeError('Circular dependency for enum: %s' % enum.name) - in_progress.add(field) - if field.value: - if isinstance(field.value, mojom.EnumValue): - resolved_value = GetResolvedValue(field.value) - elif isinstance(field.value, str): - resolved_value = int(field.value, 0) - else: - raise RuntimeError('Unexpected value: %s' % field.value) - else: - resolved_value = default_value - field.resolved_value = resolved_value - in_progress.remove(field) - computed.add(field) - - current_value = 0 - for field in enum.fields: - ResolveEnumField(enum, field, current_value) - current_value = field.resolved_value + 1 - - for constant in module.constants: - ResolveConstant(constant) - - for enum in module.enums: - ResolveEnum(enum) - - for struct in module.structs: - for constant in struct.constants: - ResolveConstant(constant) - for enum in struct.enums: - ResolveEnum(enum) - for field in struct.fields: - if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)): - field.default.resolved_value = GetResolvedValue(field.default) - - for interface in module.interfaces: - for constant in interface.constants: - ResolveConstant(constant) - for enum in interface.enums: - ResolveEnum(enum) - - return module diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py deleted file mode 100644 index 19905c8a..00000000 --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import imp -import os.path -import sys -import unittest - -from mojom.generate import module as mojom -from mojom.generate import translate -from mojom.parse import ast - - -class TranslateTest(unittest.TestCase): - """Tests |parser.Parse()|.""" - - def testSimpleArray(self): - """Tests a simple int32[].""" - # pylint: disable=W0212 - self.assertEquals(translate._MapKind("int32[]"), "a:i32") - - def testAssociativeArray(self): - """Tests a simple uint8{string}.""" - # pylint: disable=W0212 - self.assertEquals(translate._MapKind("uint8{string}"), "m[s][u8]") - - def testLeftToRightAssociativeArray(self): - """Makes sure that parsing is done from right to left on the internal kinds - in the presence of an associative array.""" - # pylint: disable=W0212 - self.assertEquals(translate._MapKind("uint8[]{string}"), "m[s][a:u8]") - - def testTranslateSimpleUnions(self): - """Makes sure that a simple union is translated correctly.""" - tree = ast.Mojom(None, ast.ImportList(), [ - ast.Union( - "SomeUnion", None, - ast.UnionBody([ - ast.UnionField("a", None, None, "int32"), - ast.UnionField("b", None, None, "string") - ])) - ]) - - translation = translate.OrderedModule(tree, "mojom_tree", []) - self.assertEqual(1, len(translation.unions)) - - union = translation.unions[0] - self.assertTrue(isinstance(union, mojom.Union)) - self.assertEqual("SomeUnion", union.mojom_name) - self.assertEqual(2, len(union.fields)) - self.assertEqual("a", union.fields[0].mojom_name) - self.assertEqual(mojom.INT32.spec, union.fields[0].kind.spec) - self.assertEqual("b", union.fields[1].mojom_name) - self.assertEqual(mojom.STRING.spec, union.fields[1].kind.spec) - - def testMapKindRaisesWithDuplicate(self): - """Verifies _MapTreeForType() raises when passed two values with the same - name.""" - methods = [ - ast.Method('dup', None, None, ast.ParameterList(), None), - ast.Method('dup', None, None, ast.ParameterList(), None) - ] - with self.assertRaises(Exception): - translate._ElemsOfType(methods, ast.Method, 'scope') - - def testAssociatedKinds(self): - """Tests type spec translation of associated interfaces and requests.""" - # pylint: disable=W0212 - self.assertEquals( - translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface") - self.assertEquals( - translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface") diff --git a/utils/ipu3/ipu3-capture.sh b/utils/ipu3/ipu3-capture.sh index 9294d025..004a92b0 100755 --- a/utils/ipu3/ipu3-capture.sh +++ b/utils/ipu3/ipu3-capture.sh @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# ipu3-capture.sh - Capture raw frames from cameras based on the Intel IPU3 +# Capture raw frames from cameras based on the Intel IPU3 # # The scripts makes use of the following tools, which are expected to be # executable from the system-wide path or from the local directory: diff --git a/utils/ipu3/ipu3-pack.c b/utils/ipu3/ipu3-pack.c index decbfc6c..23d2db8b 100644 --- a/utils/ipu3/ipu3-pack.c +++ b/utils/ipu3/ipu3-pack.c @@ -8,6 +8,7 @@ #include <errno.h> #include <fcntl.h> +#include <libgen.h> #include <stdint.h> #include <stdio.h> #include <string.h> @@ -15,9 +16,8 @@ #include <sys/types.h> #include <unistd.h> -static void usage(const char *argv0) +static void usage(char *argv0) { - printf("Usage: %s input-file output-file\n", basename(argv0)); printf("Convert unpacked RAW10 Bayer data to the IPU3 packed Bayer formats\n"); printf("If the output-file '-', output data will be written to standard output\n"); diff --git a/utils/ipu3/ipu3-process.sh b/utils/ipu3/ipu3-process.sh index bb4abbe8..25bc849f 100755 --- a/utils/ipu3/ipu3-process.sh +++ b/utils/ipu3/ipu3-process.sh @@ -4,7 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# ipu3-process.sh - Process raw frames with the Intel IPU3 +# Process raw frames with the Intel IPU3 # # The scripts makes use of the following tools, which are expected to be # found in $PATH: diff --git a/utils/ipu3/ipu3-unpack.c b/utils/ipu3/ipu3-unpack.c index 9d2c1200..6ee8c45a 100644 --- a/utils/ipu3/ipu3-unpack.c +++ b/utils/ipu3/ipu3-unpack.c @@ -8,6 +8,7 @@ #include <errno.h> #include <fcntl.h> +#include <libgen.h> #include <stdint.h> #include <stdio.h> #include <string.h> @@ -15,7 +16,7 @@ #include <sys/types.h> #include <unistd.h> -static void usage(const char *argv0) +static void usage(char *argv0) { printf("Usage: %s input-file output-file\n", basename(argv0)); printf("Unpack the IPU3 raw Bayer format to 16-bit Bayer\n"); @@ -78,7 +79,7 @@ int main(int argc, char *argv[]) } ret = write(out_fd, out_data, 50); - if (ret < -1) { + if (ret == -1) { fprintf(stderr, "Failed to write output data: %s\n", strerror(errno)); goto done; diff --git a/utils/meson.build b/utils/meson.build index 8e28ada7..95d657ac 100644 --- a/utils/meson.build +++ b/utils/meson.build @@ -1,15 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 -subdir('ipc') +subdir('codegen') subdir('ipu3') -subdir('tracepoints') - -## Code generation -py_modules += ['yaml'] -gen_controls = files('gen-controls.py') -gen_formats = files('gen-formats.py') -gen_header = files('gen-header.sh') ## Module signing gen_ipa_priv_key = files('gen-ipa-priv-key.sh') -gen_ipa_pub_key = files('gen-ipa-pub-key.py') diff --git a/utils/raspberrypi/ctt/alsc_only.py b/utils/raspberrypi/ctt/alsc_only.py index 7cd0ac01..a521c4ad 100755 --- a/utils/raspberrypi/ctt/alsc_only.py +++ b/utils/raspberrypi/ctt/alsc_only.py @@ -2,12 +2,14 @@ # # SPDX-License-Identifier: BSD-2-Clause # -# Copyright (C) 2022, Raspberry Pi (Trading) Limited +# Copyright (C) 2022, Raspberry Pi Ltd # -# alsc_only.py - alsc tuning tool +# alsc tuning tool -from ctt import * +import sys +from ctt import * +from ctt_tools import parse_input if __name__ == '__main__': """ @@ -15,13 +17,14 @@ if __name__ == '__main__': """ if len(sys.argv) == 1: print(""" - Pisp Camera Tuning Tool version 1.0 + PiSP Lens Shading Camera Tuning Tool version 1.0 Required Arguments: '-i' : Calibration image directory. '-o' : Name of output json file. Optional Arguments: + '-t' : Target platform - 'pisp' or 'vc4'. Default 'vc4' '-c' : Config file for the CTT. If not passed, default parameters used. '-l' : Name of output log file. If not passed, 'ctt_log.txt' used. """) @@ -30,5 +33,10 @@ if __name__ == '__main__': """ parse input arguments """ - json_output, directory, config, log_output = parse_input() - run_ctt(json_output, directory, config, log_output, alsc_only=True) + json_output, directory, config, log_output, target = parse_input() + if target == 'pisp': + from ctt_pisp import json_template, grid_size + elif target == 'vc4': + from ctt_vc4 import json_template, grid_size + + run_ctt(json_output, directory, config, log_output, json_template, grid_size, target, alsc_only=True) diff --git a/utils/raspberrypi/ctt/cac_only.py b/utils/raspberrypi/ctt/cac_only.py new file mode 100644 index 00000000..1c0a8193 --- /dev/null +++ b/utils/raspberrypi/ctt/cac_only.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2023, Raspberry Pi (Trading) Ltd. +# +# cac_only.py - cac tuning tool + + +# This file allows you to tune only the chromatic aberration correction +# Specify any number of files in the command line args, and it shall iterate through +# and generate an averaged cac table from all the input images, which you can then +# input into your tuning file. + +# Takes .dng files produced by the camera modules of the dots grid and calculates the chromatic abberation of each dot. +# Then takes each dot, and works out where it was in the image, and uses that to output a tables of the shifts +# across the whole image. + +from PIL import Image +import numpy as np +import rawpy +import sys +import getopt + +from ctt_cac import * + + +def cac(filelist, output_filepath, plot_results=False): + np.set_printoptions(precision=3) + np.set_printoptions(suppress=True) + + # Create arrays to hold all the dots data and their colour offsets + red_shift = [] # Format is: [[Dot Center X, Dot Center Y, x shift, y shift]] + blue_shift = [] + # Iterate through the files + # Multiple files is reccomended to average out the lens aberration through rotations + for file in filelist: + print("\n Processing file " + str(file)) + # Read the raw RGB values from the .dng file + with rawpy.imread(file) as raw: + rgb = raw.postprocess() + sizes = (raw.sizes) + + image_size = [sizes[2], sizes[3]] # Image size, X, Y + # Create a colour copy of the RGB values to use later in the calibration + imout = Image.new(mode="RGB", size=image_size) + rgb_image = np.array(imout) + # The rgb values need reshaping from a 1d array to a 3d array to be worked with easily + rgb.reshape((image_size[0], image_size[1], 3)) + rgb_image = rgb + + # Pass the RGB image through to the dots locating program + # Returns an array of the dots (colour rectangles around the dots), and an array of their locations + print("Finding dots") + dots, dots_locations = find_dots_locations(rgb_image) + + # Now, analyse each dot. Work out the centroid of each colour channel, and use that to work out + # by how far the chromatic aberration has shifted each channel + print('Dots found: ' + str(len(dots))) + + for dot, dot_location in zip(dots, dots_locations): + if len(dot) > 0: + if (dot_location[0] > 0) and (dot_location[1] > 0): + ret = analyse_dot(dot, dot_location) + red_shift.append(ret[0]) + blue_shift.append(ret[1]) + + # Take our arrays of red shifts and locations, push them through to be interpolated into a 9x9 matrix + # for the CAC block to handle and then store these as a .json file to be added to the camera + # tuning file + print("\nCreating output grid") + rx, ry, bx, by = shifts_to_yaml(red_shift, blue_shift, image_size) + + print("CAC correction complete!") + + # The json format that we then paste into the tuning file (manually) + sample = ''' + { + "rpi.cac" : + { + "strength": 1.0, + "lut_rx" : [ + rx_vals + ], + "lut_ry" : [ + ry_vals + ], + "lut_bx" : [ + bx_vals + ], + "lut_by" : [ + by_vals + ] + } + } + ''' + + # Below, may look incorrect, however, the PiSP (standard) dimensions are flipped in comparison to + # PIL image coordinate directions, hence why xr -> yr. Also, the shifts calculated are colour shifts, + # and the PiSP block asks for the values it should shift (hence the * -1, to convert from colour shift to a pixel shift) + sample = sample.replace("rx_vals", pprint_array(ry * -1)) + sample = sample.replace("ry_vals", pprint_array(rx * -1)) + sample = sample.replace("bx_vals", pprint_array(by * -1)) + sample = sample.replace("by_vals", pprint_array(bx * -1)) + print("Successfully converted to JSON") + f = open(str(output_filepath), "w+") + f.write(sample) + f.close() + print("Successfully written to json file") + ''' + If you wish to see a plot of the colour channel shifts, add the -p or --plots option + Can be a quick way of validating if the data/dots you've got are good, or if you need to + change some parameters/take some better images + ''' + if plot_results: + plot_shifts(red_shift, blue_shift) + + +if __name__ == "__main__": + argv = sys.argv + # Detect the input and output file paths + arg_output = "output.json" + arg_help = "{0} -i <input> -o <output> -p <plot results>".format(argv[0]) + opts, args = getopt.getopt(argv[1:], "hi:o:p", ["help", "input=", "output=", "plot"]) + + output_location = 0 + input_location = 0 + filelist = [] + plot_results = False + for i in range(len(argv)): + if ("-h") in argv[i]: + print(arg_help) # print the help message + sys.exit(2) + if "-o" in argv[i]: + output_location = i + if ".dng" in argv[i]: + filelist.append(argv[i]) + if "-p" in argv[i]: + plot_results = True + + arg_output = argv[output_location + 1] + cac(filelist, arg_output, plot_results) diff --git a/utils/raspberrypi/ctt/colors.py b/utils/raspberrypi/ctt/colors.py index 1ab986d6..cb4d236b 100644 --- a/utils/raspberrypi/ctt/colors.py +++ b/utils/raspberrypi/ctt/colors.py @@ -1,4 +1,4 @@ -# colors.py - Program to convert from RGB to LAB color space +# Program to convert from RGB to LAB color space def RGB_to_LAB(RGB): # where RGB is a 1x3 array. e.g RGB = [100, 255, 230] num = 0 XYZ = [0, 0, 0] diff --git a/utils/raspberrypi/ctt/convert_tuning.py b/utils/raspberrypi/ctt/convert_tuning.py index f4504d45..83cf69d4 100755 --- a/utils/raspberrypi/ctt/convert_tuning.py +++ b/utils/raspberrypi/ctt/convert_tuning.py @@ -8,30 +8,104 @@ import argparse import json +import numpy as np import sys from ctt_pretty_print_json import pretty_print +from ctt_pisp import grid_size as grid_size_pisp +from ctt_pisp import json_template as json_template_pisp +from ctt_vc4 import grid_size as grid_size_vc4 +from ctt_vc4 import json_template as json_template_vc4 -def convert_v2(in_json: dict) -> str: +def interp_2d(in_ls, src_w, src_h, dst_w, dst_h): - if 'version' in in_json.keys() and in_json['version'] != 1.0: - print(f'The JSON config reports version {in_json["version"]} that is incompatible with this tool.') - sys.exit(-1) + out_ls = np.zeros((dst_h, dst_w)) + for i in range(src_h): + out_ls[i] = np.interp(np.linspace(0, dst_w - 1, dst_w), + np.linspace(0, dst_w - 1, src_w), + in_ls[i]) + for i in range(dst_w): + out_ls[:,i] = np.interp(np.linspace(0, dst_h - 1, dst_h), + np.linspace(0, dst_h - 1, src_h), + out_ls[:src_h, i]) + return out_ls - converted = { - 'version': 2.0, - 'target': 'bcm2835', - 'algorithms': [{algo: config} for algo, config in in_json.items()] - } - return pretty_print(converted) +def convert_target(in_json: dict, target: str): + + src_w, src_h = grid_size_pisp if target == 'vc4' else grid_size_vc4 + dst_w, dst_h = grid_size_vc4 if target == 'vc4' else grid_size_pisp + json_template = json_template_vc4 if target == 'vc4' else json_template_pisp + + # ALSC grid sizes + alsc = next(algo for algo in in_json['algorithms'] if 'rpi.alsc' in algo)['rpi.alsc'] + for colour in ['calibrations_Cr', 'calibrations_Cb']: + if colour not in alsc: + continue + for temperature in alsc[colour]: + in_ls = np.reshape(temperature['table'], (src_h, src_w)) + out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h) + temperature['table'] = np.round(out_ls.flatten(), 3).tolist() + + if 'luminance_lut' in alsc: + in_ls = np.reshape(alsc['luminance_lut'], (src_h, src_w)) + out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h) + alsc['luminance_lut'] = np.round(out_ls.flatten(), 3).tolist() + + # Denoise blocks + for i, algo in enumerate(in_json['algorithms']): + if list(algo.keys())[0] == 'rpi.sdn': + in_json['algorithms'][i] = {'rpi.denoise': json_template['rpi.sdn'] if target == 'vc4' else json_template['rpi.denoise']} + break + + # AGC mode weights + agc = next(algo for algo in in_json['algorithms'] if 'rpi.agc' in algo)['rpi.agc'] + if 'channels' in agc: + for i, channel in enumerate(agc['channels']): + target_agc_metering = json_template['rpi.agc']['channels'][i]['metering_modes'] + for mode, v in channel['metering_modes'].items(): + v['weights'] = target_agc_metering[mode]['weights'] + else: + for mode, v in agc["metering_modes"].items(): + target_agc_metering = json_template['rpi.agc']['channels'][0]['metering_modes'] + v['weights'] = target_agc_metering[mode]['weights'] + + # HDR + if target == 'pisp': + for i, algo in enumerate(in_json['algorithms']): + if list(algo.keys())[0] == 'rpi.hdr': + in_json['algorithms'][i] = {'rpi.hdr': json_template['rpi.hdr']} + + return in_json + + +def convert_v2(in_json: dict, target: str) -> str: + + if 'version' in in_json.keys() and in_json['version'] == 1.0: + converted = { + 'version': 2.0, + 'target': target, + 'algorithms': [{algo: config} for algo, config in in_json.items()] + } + else: + converted = in_json + + # Convert between vc4 <-> pisp targets. This is a best effort thing. + if converted['target'] != target: + converted = convert_target(converted, target) + converted['target'] = target + + grid_size = grid_size_vc4[0] if target == 'vc4' else grid_size_pisp[0] + return pretty_print(converted, custom_elems={'table': grid_size, 'luminance_lut': grid_size}) if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description= - 'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0.\n') + 'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0 and/or the vc4 <-> pisp targets.\n') parser.add_argument('input', type=str, help='Input tuning file.') + parser.add_argument('-t', '--target', type=str, help='Target platform.', + choices=['pisp', 'vc4'], default='vc4') parser.add_argument('output', type=str, nargs='?', help='Output converted tuning file. If not provided, the input file will be updated in-place.', default=None) @@ -40,7 +114,7 @@ if __name__ == "__main__": with open(args.input, 'r') as f: in_json = json.load(f) - out_json = convert_v2(in_json) + out_json = convert_v2(in_json, args.target) with open(args.output if args.output is not None else args.input, 'w') as f: f.write(out_json) diff --git a/utils/raspberrypi/ctt/ctt.py b/utils/raspberrypi/ctt/ctt.py index cd89f177..186afda5 100755 --- a/utils/raspberrypi/ctt/ctt.py +++ b/utils/raspberrypi/ctt/ctt.py @@ -4,11 +4,12 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt.py - camera tuning tool +# camera tuning tool import os import sys from ctt_image_load import * +from ctt_cac import * from ctt_ccm import * from ctt_awb import * from ctt_alsc import * @@ -22,9 +23,10 @@ import re """ This file houses the camera object, which is used to perform the calibrations. -The camera object houses all the calibration images as attributes in two lists: +The camera object houses all the calibration images as attributes in three lists: - imgs (macbeth charts) - imgs_alsc (alsc correction images) + - imgs_cac (cac correction images) Various calibrations are methods of the camera object, and the output is stored in a dictionary called self.json. Once all the caibration has been completed, the Camera.json is written into a @@ -67,139 +69,26 @@ Camera object that is the backbone of the tuning tool. Input is the desired path of the output json. """ class Camera: - def __init__(self, jfile): + def __init__(self, jfile, json): self.path = os.path.dirname(os.path.expanduser(__file__)) + '/' if self.path == '/': self.path = '' self.imgs = [] self.imgs_alsc = [] + self.imgs_cac = [] self.log = 'Log created : ' + time.asctime(time.localtime(time.time())) self.log_separator = '\n'+'-'*70+'\n' self.jf = jfile """ initial json dict populated by uncalibrated values """ - self.json = { - "rpi.black_level": { - "black_level": 4096 - }, - "rpi.dpc": { - }, - "rpi.lux": { - "reference_shutter_speed": 10000, - "reference_gain": 1, - "reference_aperture": 1.0 - }, - "rpi.noise": { - }, - "rpi.geq": { - }, - "rpi.sdn": { - }, - "rpi.awb": { - "priors": [ - {"lux": 0, "prior": [2000, 1.0, 3000, 0.0, 13000, 0.0]}, - {"lux": 800, "prior": [2000, 0.0, 6000, 2.0, 13000, 2.0]}, - {"lux": 1500, "prior": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]} - ], - "modes": { - "auto": {"lo": 2500, "hi": 8000}, - "incandescent": {"lo": 2500, "hi": 3000}, - "tungsten": {"lo": 3000, "hi": 3500}, - "fluorescent": {"lo": 4000, "hi": 4700}, - "indoor": {"lo": 3000, "hi": 5000}, - "daylight": {"lo": 5500, "hi": 6500}, - "cloudy": {"lo": 7000, "hi": 8600} - }, - "bayes": 1 - }, - "rpi.agc": { - "metering_modes": { - "centre-weighted": { - "weights": [3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0] - }, - "spot": { - "weights": [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - }, - "matrix": { - "weights": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - } - }, - "exposure_modes": { - "normal": { - "shutter": [100, 10000, 30000, 60000, 120000], - "gain": [1.0, 2.0, 4.0, 6.0, 6.0] - }, - "short": { - "shutter": [100, 5000, 10000, 20000, 120000], - "gain": [1.0, 2.0, 4.0, 6.0, 6.0] - } - }, - "constraint_modes": { - "normal": [ - {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]} - ], - "highlight": [ - {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]}, - {"bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.8, 1000, 0.8]} - ] - }, - "y_target": [0, 0.16, 1000, 0.165, 10000, 0.17] - }, - "rpi.alsc": { - 'omega': 1.3, - 'n_iter': 100, - 'luminance_strength': 0.7, - }, - "rpi.contrast": { - "ce_enable": 1, - "gamma_curve": [ - 0, 0, - 1024, 5040, - 2048, 9338, - 3072, 12356, - 4096, 15312, - 5120, 18051, - 6144, 20790, - 7168, 23193, - 8192, 25744, - 9216, 27942, - 10240, 30035, - 11264, 32005, - 12288, 33975, - 13312, 35815, - 14336, 37600, - 15360, 39168, - 16384, 40642, - 18432, 43379, - 20480, 45749, - 22528, 47753, - 24576, 49621, - 26624, 51253, - 28672, 52698, - 30720, 53796, - 32768, 54876, - 36864, 57012, - 40960, 58656, - 45056, 59954, - 49152, 61183, - 53248, 62355, - 57344, 63419, - 61440, 64476, - 65535, 65535 - ] - }, - "rpi.ccm": { - }, - "rpi.sharpen": { - } - } + self.json = json """ Perform colour correction calibrations by comparing macbeth patch colours to standard macbeth chart colours. """ - def ccm_cal(self, do_alsc_colour): + def ccm_cal(self, do_alsc_colour, grid_size): if 'rpi.ccm' in self.disable: return 1 print('\nStarting CCM calibration') @@ -245,7 +134,7 @@ class Camera: Do CCM calibration """ try: - ccms = ccm(self, cal_cr_list, cal_cb_list) + ccms = ccm(self, cal_cr_list, cal_cb_list, grid_size) except ArithmeticError: print('ERROR: Matrix is singular!\nTake new pictures and try again...') self.log += '\nERROR: Singular matrix encountered during fit!' @@ -259,11 +148,70 @@ class Camera: print('Finished CCM calibration') """ + Perform chromatic abberation correction using multiple dots images. + """ + def cac_cal(self, do_alsc_colour): + if 'rpi.cac' in self.disable: + return 1 + print('\nStarting CAC calibration') + self.log_new_sec('CAC') + """ + check if cac images have been taken + """ + if len(self.imgs_cac) == 0: + print('\nError:\nNo cac calibration images found') + self.log += '\nERROR: No CAC calibration images found!' + self.log += '\nCAC calibration aborted!' + return 1 + """ + if image is greyscale then CAC makes no sense + """ + if self.grey: + print('\nERROR: Can\'t do CAC on greyscale image!') + self.log += '\nERROR: Cannot perform CAC calibration ' + self.log += 'on greyscale image!\nCAC aborted!' + del self.json['rpi.cac'] + return 0 + a = time.time() + """ + Check if camera is greyscale or color. If not greyscale, then perform cac + """ + if do_alsc_colour: + """ + Here we have a color sensor. Perform cac + """ + try: + cacs = cac(self) + except ArithmeticError: + print('ERROR: Matrix is singular!\nTake new pictures and try again...') + self.log += '\nERROR: Singular matrix encountered during fit!' + self.log += '\nCAC aborted!' + return 1 + else: + """ + case where config options suggest greyscale camera. No point in doing CAC + """ + cal_cr_list, cal_cb_list = None, None + self.log += '\nWARNING: No ALSC tables found.\nCAC calibration ' + self.log += 'performed without ALSC correction...' + + """ + Write output to json + """ + if cacs: + self.json['rpi.cac']['cac'] = cacs + self.log += '\nCAC calibration written to json file' + print('Finished CAC calibration') + else: + self.log += "\nCAC calibration failed" + + + """ Auto white balance calibration produces a colour curve for various colour temperatures, as well as providing a maximum 'wiggle room' distance from this curve (transverse_neg/pos). """ - def awb_cal(self, greyworld, do_alsc_colour): + def awb_cal(self, greyworld, do_alsc_colour, grid_size): if 'rpi.awb' in self.disable: return 1 print('\nStarting AWB calibration') @@ -306,7 +254,7 @@ class Camera: call calibration function """ plot = "rpi.awb" in self.plot - awb_out = awb(self, cal_cr_list, cal_cb_list, plot) + awb_out = awb(self, cal_cr_list, cal_cb_list, plot, grid_size) ct_curve, transverse_neg, transverse_pos = awb_out """ write output to json @@ -324,7 +272,7 @@ class Camera: colour channel seperately, and then partially corrects for vignetting. The extent of the correction depends on the 'luminance_strength' parameter. """ - def alsc_cal(self, luminance_strength, do_alsc_colour): + def alsc_cal(self, luminance_strength, do_alsc_colour, grid_size, max_gain=8.0): if 'rpi.alsc' in self.disable: return 1 print('\nStarting ALSC calibration') @@ -347,10 +295,10 @@ class Camera: call calibration function """ plot = "rpi.alsc" in self.plot - alsc_out = alsc_all(self, do_alsc_colour, plot) + alsc_out = alsc_all(self, do_alsc_colour, plot, grid_size, max_gain=max_gain) cal_cr_list, cal_cb_list, luminance_lut, av_corn = alsc_out """ - write ouput to json and finish if not do_alsc_colour + write output to json and finish if not do_alsc_colour """ if not do_alsc_colour: self.json['rpi.alsc']['luminance_lut'] = luminance_lut @@ -393,7 +341,7 @@ class Camera: """ obtain worst-case scenario residual sigmas """ - sigma_r, sigma_b = get_sigma(self, cal_cr_list, cal_cb_list) + sigma_r, sigma_b = get_sigma(self, cal_cr_list, cal_cb_list, grid_size) """ write output to json """ @@ -509,19 +457,20 @@ class Camera: """ writes the json dictionary to the raw json file then make pretty """ - def write_json(self): + def write_json(self, version=2.0, target='bcm2835', grid_size=(16, 12)): """ Write json dictionary to file using our version 2 format """ out_json = { - "version": 2.0, - 'target': 'bcm2835', + "version": version, + 'target': target if target != 'vc4' else 'bcm2835', "algorithms": [{name: data} for name, data in self.json.items()], } with open(self.jf, 'w') as f: - f.write(pretty_print(out_json)) + f.write(pretty_print(out_json, + custom_elems={'table': grid_size[0], 'luminance_lut': grid_size[0]})) """ add a new section to the log file @@ -627,6 +576,16 @@ class Camera: self.log += '\nWARNING: Error reading colour temperature' self.log += '\nImage discarded!' print('DISCARDED') + elif 'cac' in filename: + Img = load_image(self, address, mac=False) + self.log += '\nIdentified as an CAC image' + Img.name = filename + self.log += '\nColour temperature: {} K'.format(col) + self.imgs_cac.append(Img) + if blacklevel != -1: + Img.blacklevel_16 = blacklevel + print(img_suc_msg) + continue else: self.log += '\nIdentified as macbeth chart image' """ @@ -672,6 +631,7 @@ class Camera: self.log += '\n\nImages found:' self.log += '\nMacbeth : {}'.format(len(self.imgs)) self.log += '\nALSC : {} '.format(len(self.imgs_alsc)) + self.log += '\nCAC: {} '.format(len(self.imgs_cac)) self.log += '\n\nCamera metadata' """ check usable images found @@ -680,22 +640,21 @@ class Camera: print('\nERROR: No usable macbeth chart images found') self.log += '\nERROR: No usable macbeth chart images found' return 0 - elif len(self.imgs) == 0 and len(self.imgs_alsc) == 0: + elif len(self.imgs) == 0 and len(self.imgs_alsc) == 0 and len(self.imgs_cac) == 0: print('\nERROR: No usable images found') self.log += '\nERROR: No usable images found' return 0 """ Double check that every image has come from the same camera... """ - all_imgs = self.imgs + self.imgs_alsc + all_imgs = self.imgs + self.imgs_alsc + self.imgs_cac camNames = list(set([Img.camName for Img in all_imgs])) patterns = list(set([Img.pattern for Img in all_imgs])) sigbitss = list(set([Img.sigbits for Img in all_imgs])) blacklevels = list(set([Img.blacklevel_16 for Img in all_imgs])) sizes = list(set([(Img.w, Img.h) for Img in all_imgs])) - if len(camNames) == 1 and len(patterns) == 1 and len(sigbitss) == 1 and \ - len(blacklevels) == 1 and len(sizes) == 1: + if 1: self.grey = (patterns[0] == 128) self.blacklevel_16 = blacklevels[0] self.log += '\nName: {}'.format(camNames[0]) @@ -712,7 +671,7 @@ class Camera: return 0 -def run_ctt(json_output, directory, config, log_output, alsc_only=False): +def run_ctt(json_output, directory, config, log_output, json_template, grid_size, target, alsc_only=False): """ check input files are jsons """ @@ -748,12 +707,14 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False): greyworld = get_config(awb_d, "greyworld", 0, 'bool') alsc_d = get_config(configs, "alsc", {}, 'dict') do_alsc_colour = get_config(alsc_d, "do_alsc_colour", 1, 'bool') - luminance_strength = get_config(alsc_d, "luminance_strength", 0.5, 'num') + luminance_strength = get_config(alsc_d, "luminance_strength", 0.8, 'num') + lsc_max_gain = get_config(alsc_d, "max_gain", 8.0, 'num') blacklevel = get_config(configs, "blacklevel", -1, 'num') macbeth_d = get_config(configs, "macbeth", {}, 'dict') mac_small = get_config(macbeth_d, "small", 0, 'bool') mac_show = get_config(macbeth_d, "show", 0, 'bool') mac_config = (mac_small, mac_show) + print("Read lsc_max_gain", lsc_max_gain) if blacklevel < -1 or blacklevel >= 2**16: print('\nInvalid blacklevel, defaulted to 64') @@ -772,7 +733,7 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False): initialise tuning tool and load images """ try: - Cam = Camera(json_output) + Cam = Camera(json_output, json=json_template) Cam.log_user_input(json_output, directory, config, log_output) if alsc_only: disable = set(Cam.json.keys()).symmetric_difference({"rpi.alsc"}) @@ -794,14 +755,17 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False): Cam.json['rpi.black_level']['black_level'] = Cam.blacklevel_16 Cam.json_remove(disable) print('\nSTARTING CALIBRATIONS') - Cam.alsc_cal(luminance_strength, do_alsc_colour) + Cam.alsc_cal(luminance_strength, do_alsc_colour, grid_size, max_gain=lsc_max_gain) Cam.geq_cal() Cam.lux_cal() Cam.noise_cal() - Cam.awb_cal(greyworld, do_alsc_colour) - Cam.ccm_cal(do_alsc_colour) + if "rpi.cac" in json_template: + Cam.cac_cal(do_alsc_colour) + Cam.awb_cal(greyworld, do_alsc_colour, grid_size) + Cam.ccm_cal(do_alsc_colour, grid_size) + print('\nFINISHED CALIBRATIONS') - Cam.write_json() + Cam.write_json(target=target, grid_size=grid_size) Cam.write_log(log_output) print('\nCalibrations written to: '+json_output) if log_output is None: @@ -811,20 +775,19 @@ def run_ctt(json_output, directory, config, log_output, alsc_only=False): else: Cam.write_log(log_output) - if __name__ == '__main__': """ initialise calibration """ if len(sys.argv) == 1: print(""" - Pisp Camera Tuning Tool version 1.0 - + PiSP Tuning Tool version 1.0 Required Arguments: '-i' : Calibration image directory. '-o' : Name of output json file. Optional Arguments: + '-t' : Target platform - 'pisp' or 'vc4'. Default 'vc4' '-c' : Config file for the CTT. If not passed, default parameters used. '-l' : Name of output log file. If not passed, 'ctt_log.txt' used. """) @@ -833,5 +796,10 @@ if __name__ == '__main__': """ parse input arguments """ - json_output, directory, config, log_output = parse_input() - run_ctt(json_output, directory, config, log_output) + json_output, directory, config, log_output, target = parse_input() + if target == 'pisp': + from ctt_pisp import json_template, grid_size + elif target == 'vc4': + from ctt_vc4 import json_template, grid_size + + run_ctt(json_output, directory, config, log_output, json_template, grid_size, target) diff --git a/utils/raspberrypi/ctt/ctt_alsc.py b/utils/raspberrypi/ctt/ctt_alsc.py index e51d6931..5d8b2ced 100644 --- a/utils/raspberrypi/ctt/ctt_alsc.py +++ b/utils/raspberrypi/ctt/ctt_alsc.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_alsc.py - camera tuning tool for ALSC (auto lens shading correction) +# camera tuning tool for ALSC (auto lens shading correction) from ctt_image_load import * import matplotlib.pyplot as plt @@ -13,8 +13,9 @@ from mpl_toolkits.mplot3d import Axes3D """ preform alsc calibration on a set of images """ -def alsc_all(Cam, do_alsc_colour, plot): +def alsc_all(Cam, do_alsc_colour, plot, grid_size=(16, 12), max_gain=8.0): imgs_alsc = Cam.imgs_alsc + grid_w, grid_h = grid_size """ create list of colour temperatures and associated calibration tables """ @@ -23,7 +24,7 @@ def alsc_all(Cam, do_alsc_colour, plot): list_cb = [] list_cg = [] for Img in imgs_alsc: - col, cr, cb, cg, size = alsc(Cam, Img, do_alsc_colour, plot) + col, cr, cb, cg, size = alsc(Cam, Img, do_alsc_colour, plot, grid_size=grid_size, max_gain=max_gain) list_col.append(col) list_cr.append(cr) list_cb.append(cb) @@ -68,11 +69,12 @@ def alsc_all(Cam, do_alsc_colour, plot): t_b = np.where((100*t_b) % 1 >= 0.95, t_b-0.001, t_b) t_r = np.round(t_r, 3) t_b = np.round(t_b, 3) - r_corners = (t_r[0], t_r[15], t_r[-1], t_r[-16]) - b_corners = (t_b[0], t_b[15], t_b[-1], t_b[-16]) - r_cen = t_r[5*16+7]+t_r[5*16+8]+t_r[6*16+7]+t_r[6*16+8] + r_corners = (t_r[0], t_r[grid_w - 1], t_r[-1], t_r[-grid_w]) + b_corners = (t_b[0], t_b[grid_w - 1], t_b[-1], t_b[-grid_w]) + middle_pos = (grid_h // 2 - 1) * grid_w + grid_w - 1 + r_cen = t_r[middle_pos]+t_r[middle_pos + 1]+t_r[middle_pos + grid_w]+t_r[middle_pos + grid_w + 1] r_cen = round(r_cen/4, 3) - b_cen = t_b[5*16+7]+t_b[5*16+8]+t_b[6*16+7]+t_b[6*16+8] + b_cen = t_b[middle_pos]+t_b[middle_pos + 1]+t_b[middle_pos + grid_w]+t_b[middle_pos + grid_w + 1] b_cen = round(b_cen/4, 3) Cam.log += '\nRed table corners: {}'.format(r_corners) Cam.log += '\nRed table centre: {}'.format(r_cen) @@ -116,43 +118,48 @@ def alsc_all(Cam, do_alsc_colour, plot): """ calculate g/r and g/b for 32x32 points arranged in a grid for a single image """ -def alsc(Cam, Img, do_alsc_colour, plot=False): +def alsc(Cam, Img, do_alsc_colour, plot=False, grid_size=(16, 12), max_gain=8.0): Cam.log += '\nProcessing image: ' + Img.name + grid_w, grid_h = grid_size """ get channel in correct order """ channels = [Img.channels[i] for i in Img.order] """ calculate size of single rectangle. - -(-(w-1)//32) is a ceiling division. w-1 is to deal robustly with the case - where w is a multiple of 32. + The divisions here must ensure the final row/column of cells has a non-zero number of + pixels. """ w, h = Img.w/2, Img.h/2 - dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12)) + dx, dy = int((w - 1) // (grid_w - 1)), int((h - 1) // (grid_h - 1)) + """ average the green channels into one """ av_ch_g = np.mean((channels[1:3]), axis=0) if do_alsc_colour: """ - obtain 16x12 grid of intensities for each channel and subtract black level + obtain grid_w x grid_h grid of intensities for each channel and subtract black level """ - g = get_16x12_grid(av_ch_g, dx, dy) - Img.blacklevel_16 - r = get_16x12_grid(channels[0], dx, dy) - Img.blacklevel_16 - b = get_16x12_grid(channels[3], dx, dy) - Img.blacklevel_16 + g = get_grid(av_ch_g, dx, dy, grid_size) - Img.blacklevel_16 + r = get_grid(channels[0], dx, dy, grid_size) - Img.blacklevel_16 + b = get_grid(channels[3], dx, dy, grid_size) - Img.blacklevel_16 """ calculate ratios as 32 bit in order to be supported by medianBlur function """ - cr = np.reshape(g/r, (12, 16)).astype('float32') - cb = np.reshape(g/b, (12, 16)).astype('float32') - cg = np.reshape(1/g, (12, 16)).astype('float32') + cr = np.reshape(g/r, (grid_h, grid_w)).astype('float32') + cb = np.reshape(g/b, (grid_h, grid_w)).astype('float32') + cg = np.reshape(1/g, (grid_h, grid_w)).astype('float32') """ median blur to remove peaks and save as float 64 """ cr = cv2.medianBlur(cr, 3).astype('float64') + cr = cr/np.min(cr) # gain tables are easier for humans to read if the minimum is 1.0 cb = cv2.medianBlur(cb, 3).astype('float64') + cb = cb/np.min(cb) cg = cv2.medianBlur(cg, 3).astype('float64') cg = cg/np.min(cg) + cg = [min(v, max_gain) for v in cg.flatten()] # never exceed the max luminance gain """ debugging code showing 2D surface plot of vignetting. Quite useful for @@ -164,7 +171,7 @@ def alsc(Cam, Img, do_alsc_colour, plot=False): """ note Y is plotted as -Y so plot has same axes as image """ - X, Y = np.meshgrid(range(16), range(12)) + X, Y = np.meshgrid(range(grid_w), range(grid_h)) ha.plot_surface(X, -Y, cr, cmap=cm.coolwarm, linewidth=0) ha.set_title('ALSC Plot\nImg: {}\n\ncr'.format(Img.str)) hb = hf.add_subplot(312, projection='3d') @@ -176,21 +183,22 @@ def alsc(Cam, Img, do_alsc_colour, plot=False): # print(Img.str) plt.show() - return Img.col, cr.flatten(), cb.flatten(), cg.flatten(), (w, h, dx, dy) + return Img.col, cr.flatten(), cb.flatten(), cg, (w, h, dx, dy) else: """ only perform calculations for luminance shading """ - g = get_16x12_grid(av_ch_g, dx, dy) - Img.blacklevel_16 - cg = np.reshape(1/g, (12, 16)).astype('float32') + g = get_grid(av_ch_g, dx, dy, grid_size) - Img.blacklevel_16 + cg = np.reshape(1/g, (grid_h, grid_w)).astype('float32') cg = cv2.medianBlur(cg, 3).astype('float64') cg = cg/np.min(cg) + cg = [min(v, max_gain) for v in cg.flatten()] # never exceed the max luminance gain if plot: hf = plt.figure(figssize=(8, 8)) ha = hf.add_subplot(1, 1, 1, projection='3d') - X, Y = np.meashgrid(range(16), range(12)) + X, Y = np.meashgrid(range(grid_w), range(grid_h)) ha.plot_surface(X, -Y, cg, cmap=cm.coolwarm, linewidth=0) ha.set_title('ALSC Plot (Luminance only!)\nImg: {}\n\ncg').format(Img.str) plt.show() @@ -199,21 +207,22 @@ def alsc(Cam, Img, do_alsc_colour, plot=False): """ -Compresses channel down to a 16x12 grid +Compresses channel down to a grid of the requested size """ -def get_16x12_grid(chan, dx, dy): +def get_grid(chan, dx, dy, grid_size): + grid_w, grid_h = grid_size grid = [] """ since left and bottom border will not necessarily have rectangles of dimension dx x dy, the 32nd iteration has to be handled separately. """ - for i in range(11): - for j in range(15): + for i in range(grid_h - 1): + for j in range(grid_w - 1): grid.append(np.mean(chan[dy*i:dy*(1+i), dx*j:dx*(1+j)])) - grid.append(np.mean(chan[dy*i:dy*(1+i), 15*dx:])) - for j in range(15): - grid.append(np.mean(chan[11*dy:, dx*j:dx*(1+j)])) - grid.append(np.mean(chan[11*dy:, 15*dx:])) + grid.append(np.mean(chan[dy*i:dy*(1+i), (grid_w - 1)*dx:])) + for j in range(grid_w - 1): + grid.append(np.mean(chan[(grid_h - 1)*dy:, dx*j:dx*(1+j)])) + grid.append(np.mean(chan[(grid_h - 1)*dy:, (grid_w - 1)*dx:])) """ return as np.array, ready for further manipulation """ @@ -223,7 +232,7 @@ def get_16x12_grid(chan, dx, dy): """ obtains sigmas for red and blue, effectively a measure of the 'error' """ -def get_sigma(Cam, cal_cr_list, cal_cb_list): +def get_sigma(Cam, cal_cr_list, cal_cb_list, grid_size): Cam.log += '\nCalculating sigmas' """ provided colour alsc tables were generated for two different colour @@ -241,8 +250,8 @@ def get_sigma(Cam, cal_cr_list, cal_cb_list): sigma_rs = [] sigma_bs = [] for i in range(len(cts)-1): - sigma_rs.append(calc_sigma(cal_cr_list[i]['table'], cal_cr_list[i+1]['table'])) - sigma_bs.append(calc_sigma(cal_cb_list[i]['table'], cal_cb_list[i+1]['table'])) + sigma_rs.append(calc_sigma(cal_cr_list[i]['table'], cal_cr_list[i+1]['table'], grid_size)) + sigma_bs.append(calc_sigma(cal_cb_list[i]['table'], cal_cb_list[i+1]['table'], grid_size)) Cam.log += '\nColour temperature interval {} - {} K'.format(cts[i], cts[i+1]) Cam.log += '\nSigma red: {}'.format(sigma_rs[-1]) Cam.log += '\nSigma blue: {}'.format(sigma_bs[-1]) @@ -263,12 +272,13 @@ def get_sigma(Cam, cal_cr_list, cal_cb_list): """ calculate sigma from two adjacent gain tables """ -def calc_sigma(g1, g2): +def calc_sigma(g1, g2, grid_size): + grid_w, grid_h = grid_size """ reshape into 16x12 matrix """ - g1 = np.reshape(g1, (12, 16)) - g2 = np.reshape(g2, (12, 16)) + g1 = np.reshape(g1, (grid_h, grid_w)) + g2 = np.reshape(g2, (grid_h, grid_w)) """ apply gains to gain table """ @@ -280,8 +290,8 @@ def calc_sigma(g1, g2): neighbours, then append to list """ diffs = [] - for i in range(10): - for j in range(14): + for i in range(grid_h - 2): + for j in range(grid_w - 2): """ note indexing is incremented by 1 since all patches on borders are not counted diff --git a/utils/raspberrypi/ctt/ctt_awb.py b/utils/raspberrypi/ctt/ctt_awb.py index bf45e54d..4af1fe41 100644 --- a/utils/raspberrypi/ctt/ctt_awb.py +++ b/utils/raspberrypi/ctt/ctt_awb.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_awb.py - camera tuning tool for AWB +# camera tuning tool for AWB from ctt_image_load import * import matplotlib.pyplot as plt @@ -13,7 +13,7 @@ from scipy.optimize import fmin """ obtain piecewise linear approximation for colour curve """ -def awb(Cam, cal_cr_list, cal_cb_list, plot): +def awb(Cam, cal_cr_list, cal_cb_list, plot, grid_size): imgs = Cam.imgs """ condense alsc calibration tables into one dictionary @@ -43,7 +43,7 @@ def awb(Cam, cal_cr_list, cal_cb_list, plot): Note: if alsc is disabled then colour_cals will be set to None and the function will just return the greyscale patches """ - r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals) + r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals, grid_size=grid_size) """ calculate ratio of r, b to g """ @@ -293,12 +293,13 @@ def awb(Cam, cal_cr_list, cal_cb_list, plot): """ obtain greyscale patches and perform alsc colour correction """ -def get_alsc_patches(Img, colour_cals, grey=True): +def get_alsc_patches(Img, colour_cals, grey=True, grid_size=(16, 12)): """ get patch centre coordinates, image colour and the actual patches for each channel, remembering to subtract blacklevel If grey then only greyscale patches considered """ + grid_w, grid_h = grid_size if grey: cen_coords = Img.cen_coords[3::4] col = Img.col @@ -345,12 +346,12 @@ def get_alsc_patches(Img, colour_cals, grey=True): bef_tabs = np.array(colour_cals[bef]) aft_tabs = np.array(colour_cals[aft]) col_tabs = (bef_tabs*db + aft_tabs*da)/(da+db) - col_tabs = np.reshape(col_tabs, (2, 12, 16)) + col_tabs = np.reshape(col_tabs, (2, grid_h, grid_w)) """ calculate dx, dy used to calculate alsc table """ w, h = Img.w/2, Img.h/2 - dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12)) + dx, dy = int(-(-(w-1)//grid_w)), int(-(-(h-1)//grid_h)) """ make list of pairs of gains for each patch by selecting the correct value in alsc colour calibration table diff --git a/utils/raspberrypi/ctt/ctt_cac.py b/utils/raspberrypi/ctt/ctt_cac.py new file mode 100644 index 00000000..a1183989 --- /dev/null +++ b/utils/raspberrypi/ctt/ctt_cac.py @@ -0,0 +1,250 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2023, Raspberry Pi Ltd +# +# ctt_cac.py - CAC (Chromatic Aberration Correction) tuning tool + +from PIL import Image +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import cm + +from ctt_dots_locator import find_dots_locations + + +# This is the wrapper file that creates a JSON entry for you to append +# to your camera tuning file. +# It calculates the chromatic aberration at different points throughout +# the image and uses that to produce a martix that can then be used +# in the camera tuning files to correct this aberration. + + +def pprint_array(array): + # Function to print the array in a tidier format + array = array + output = "" + for i in range(len(array)): + for j in range(len(array[0])): + output += str(round(array[i, j], 2)) + ", " + # Add the necessary indentation to the array + output += "\n " + # Cut off the end of the array (nicely formats it) + return output[:-22] + + +def plot_shifts(red_shifts, blue_shifts): + # If users want, they can pass a command line option to show the shifts on a graph + # Can be useful to check that the functions are all working, and that the sample + # images are doing the right thing + Xs = np.array(red_shifts)[:, 0] + Ys = np.array(red_shifts)[:, 1] + Zs = np.array(red_shifts)[:, 2] + Zs2 = np.array(red_shifts)[:, 3] + Zs3 = np.array(blue_shifts)[:, 2] + Zs4 = np.array(blue_shifts)[:, 3] + + fig, axs = plt.subplots(2, 2) + ax = fig.add_subplot(2, 2, 1, projection='3d') + ax.scatter(Xs, Ys, Zs, cmap=cm.jet, linewidth=0) + ax.set_title('Red X Shift') + ax = fig.add_subplot(2, 2, 2, projection='3d') + ax.scatter(Xs, Ys, Zs2, cmap=cm.jet, linewidth=0) + ax.set_title('Red Y Shift') + ax = fig.add_subplot(2, 2, 3, projection='3d') + ax.scatter(Xs, Ys, Zs3, cmap=cm.jet, linewidth=0) + ax.set_title('Blue X Shift') + ax = fig.add_subplot(2, 2, 4, projection='3d') + ax.scatter(Xs, Ys, Zs4, cmap=cm.jet, linewidth=0) + ax.set_title('Blue Y Shift') + fig.tight_layout() + plt.show() + + +def shifts_to_yaml(red_shift, blue_shift, image_dimensions, output_grid_size=9): + # Convert the shifts to a numpy array for easier handling and initialise other variables + red_shifts = np.array(red_shift) + blue_shifts = np.array(blue_shift) + # create a grid that's smaller than the output grid, which we then interpolate from to get the output values + xrgrid = np.zeros((output_grid_size - 1, output_grid_size - 1)) + xbgrid = np.zeros((output_grid_size - 1, output_grid_size - 1)) + yrgrid = np.zeros((output_grid_size - 1, output_grid_size - 1)) + ybgrid = np.zeros((output_grid_size - 1, output_grid_size - 1)) + + xrsgrid = [] + xbsgrid = [] + yrsgrid = [] + ybsgrid = [] + xg = np.zeros((output_grid_size - 1, output_grid_size - 1)) + yg = np.zeros((output_grid_size - 1, output_grid_size - 1)) + + # Format the grids - numpy doesn't work for this, it wants a + # nice uniformly spaced grid, which we don't know if we have yet, hence the rather mundane setup + for x in range(output_grid_size - 1): + xrsgrid.append([]) + yrsgrid.append([]) + xbsgrid.append([]) + ybsgrid.append([]) + for y in range(output_grid_size - 1): + xrsgrid[x].append([]) + yrsgrid[x].append([]) + xbsgrid[x].append([]) + ybsgrid[x].append([]) + + image_size = (image_dimensions[0], image_dimensions[1]) + gridxsize = image_size[0] / (output_grid_size - 1) + gridysize = image_size[1] / (output_grid_size - 1) + + # Iterate through each dot, and it's shift values and put these into the correct grid location + for red_shift in red_shifts: + xgridloc = int(red_shift[0] / gridxsize) + ygridloc = int(red_shift[1] / gridysize) + xrsgrid[xgridloc][ygridloc].append(red_shift[2]) + yrsgrid[xgridloc][ygridloc].append(red_shift[3]) + + for blue_shift in blue_shifts: + xgridloc = int(blue_shift[0] / gridxsize) + ygridloc = int(blue_shift[1] / gridysize) + xbsgrid[xgridloc][ygridloc].append(blue_shift[2]) + ybsgrid[xgridloc][ygridloc].append(blue_shift[3]) + + # Now calculate the average pixel shift for each square in the grid + grid_incomplete = False + for x in range(output_grid_size - 1): + for y in range(output_grid_size - 1): + if xrsgrid[x][y]: + xrgrid[x, y] = np.mean(xrsgrid[x][y]) + else: + grid_incomplete = True + if yrsgrid[x][y]: + yrgrid[x, y] = np.mean(yrsgrid[x][y]) + else: + grid_incomplete = True + if xbsgrid[x][y]: + xbgrid[x, y] = np.mean(xbsgrid[x][y]) + else: + grid_incomplete = True + if ybsgrid[x][y]: + ybgrid[x, y] = np.mean(ybsgrid[x][y]) + else: + grid_incomplete = True + + if grid_incomplete: + raise RuntimeError("\nERROR: CAC measurements do not span the image!" + "\nConsider using improved CAC images, or remove them entirely.\n") + + # Next, we start to interpolate the central points of the grid that gets passed to the tuning file + input_grids = np.array([xrgrid, yrgrid, xbgrid, ybgrid]) + output_grids = np.zeros((4, output_grid_size, output_grid_size)) + + # Interpolate the centre of the grid + output_grids[:, 1:-1, 1:-1] = (input_grids[:, 1:, :-1] + input_grids[:, 1:, 1:] + input_grids[:, :-1, 1:] + input_grids[:, :-1, :-1]) / 4 + + # Edge cases: + output_grids[:, 1:-1, 0] = ((input_grids[:, :-1, 0] + input_grids[:, 1:, 0]) / 2 - output_grids[:, 1:-1, 1]) * 2 + output_grids[:, 1:-1, 1] + output_grids[:, 1:-1, -1] = ((input_grids[:, :-1, 7] + input_grids[:, 1:, 7]) / 2 - output_grids[:, 1:-1, -2]) * 2 + output_grids[:, 1:-1, -2] + output_grids[:, 0, 1:-1] = ((input_grids[:, 0, :-1] + input_grids[:, 0, 1:]) / 2 - output_grids[:, 1, 1:-1]) * 2 + output_grids[:, 1, 1:-1] + output_grids[:, -1, 1:-1] = ((input_grids[:, 7, :-1] + input_grids[:, 7, 1:]) / 2 - output_grids[:, -2, 1:-1]) * 2 + output_grids[:, -2, 1:-1] + + # Corner Cases: + output_grids[:, 0, 0] = (output_grids[:, 0, 1] - output_grids[:, 1, 1]) + (output_grids[:, 1, 0] - output_grids[:, 1, 1]) + output_grids[:, 1, 1] + output_grids[:, 0, -1] = (output_grids[:, 0, -2] - output_grids[:, 1, -2]) + (output_grids[:, 1, -1] - output_grids[:, 1, -2]) + output_grids[:, 1, -2] + output_grids[:, -1, 0] = (output_grids[:, -1, 1] - output_grids[:, -2, 1]) + (output_grids[:, -2, 0] - output_grids[:, -2, 1]) + output_grids[:, -2, 1] + output_grids[:, -1, -1] = (output_grids[:, -2, -1] - output_grids[:, -2, -2]) + (output_grids[:, -1, -2] - output_grids[:, -2, -2]) + output_grids[:, -2, -2] + + # Below, we swap the x and the y coordinates, and also multiply by a factor of -1 + # This is due to the PiSP (standard) dimensions being flipped in comparison to + # PIL image coordinate directions, hence why xr -> yr. Also, the shifts calculated are colour shifts, + # and the PiSP block asks for the values it should shift by (hence the * -1, to convert from colour shift to a pixel shift) + + output_grid_yr, output_grid_xr, output_grid_yb, output_grid_xb = output_grids * -1 + return output_grid_xr, output_grid_yr, output_grid_xb, output_grid_yb + + +def analyse_dot(dot, dot_location=[0, 0]): + # Scan through the dot, calculate the centroid of each colour channel by doing: + # pixel channel brightness * distance from top left corner + # Sum these, and divide by the sum of each channel's brightnesses to get a centroid for each channel + red_channel = np.array(dot)[:, :, 0] + y_num_pixels = len(red_channel[0]) + x_num_pixels = len(red_channel) + yred_weight = np.sum(np.dot(red_channel, np.arange(y_num_pixels))) + xred_weight = np.sum(np.dot(np.arange(x_num_pixels), red_channel)) + red_sum = np.sum(red_channel) + + green_channel = np.array(dot)[:, :, 1] + ygreen_weight = np.sum(np.dot(green_channel, np.arange(y_num_pixels))) + xgreen_weight = np.sum(np.dot(np.arange(x_num_pixels), green_channel)) + green_sum = np.sum(green_channel) + + blue_channel = np.array(dot)[:, :, 2] + yblue_weight = np.sum(np.dot(blue_channel, np.arange(y_num_pixels))) + xblue_weight = np.sum(np.dot(np.arange(x_num_pixels), blue_channel)) + blue_sum = np.sum(blue_channel) + + # We return this structure. It contains 2 arrays that contain: + # the locations of the dot center, along with the channel shifts in the x and y direction: + # [ [red_center_x, red_center_y, red_x_shift, red_y_shift], [blue_center_x, blue_center_y, blue_x_shift, blue_y_shift] ] + + return [[int(dot_location[0]) + int(len(dot) / 2), int(dot_location[1]) + int(len(dot[0]) / 2), xred_weight / red_sum - xgreen_weight / green_sum, yred_weight / red_sum - ygreen_weight / green_sum], [dot_location[0] + int(len(dot) / 2), dot_location[1] + int(len(dot[0]) / 2), xblue_weight / blue_sum - xgreen_weight / green_sum, yblue_weight / blue_sum - ygreen_weight / green_sum]] + + +def cac(Cam): + filelist = Cam.imgs_cac + + Cam.log += '\nCAC analysing files: {}'.format(str(filelist)) + np.set_printoptions(precision=3) + np.set_printoptions(suppress=True) + + # Create arrays to hold all the dots data and their colour offsets + red_shift = [] # Format is: [[Dot Center X, Dot Center Y, x shift, y shift]] + blue_shift = [] + # Iterate through the files + # Multiple files is reccomended to average out the lens aberration through rotations + for file in filelist: + Cam.log += '\nCAC processing file' + print("\n Processing file") + # Read the raw RGB values + rgb = file.rgb + image_size = [file.h, file.w] # Image size, X, Y + # Create a colour copy of the RGB values to use later in the calibration + imout = Image.new(mode="RGB", size=image_size) + rgb_image = np.array(imout) + # The rgb values need reshaping from a 1d array to a 3d array to be worked with easily + rgb.reshape((image_size[0], image_size[1], 3)) + rgb_image = rgb + + # Pass the RGB image through to the dots locating program + # Returns an array of the dots (colour rectangles around the dots), and an array of their locations + print("Finding dots") + Cam.log += '\nFinding dots' + dots, dots_locations = find_dots_locations(rgb_image) + + # Now, analyse each dot. Work out the centroid of each colour channel, and use that to work out + # by how far the chromatic aberration has shifted each channel + Cam.log += '\nDots found: {}'.format(str(len(dots))) + print('Dots found: ' + str(len(dots))) + + for dot, dot_location in zip(dots, dots_locations): + if len(dot) > 0: + if (dot_location[0] > 0) and (dot_location[1] > 0): + ret = analyse_dot(dot, dot_location) + red_shift.append(ret[0]) + blue_shift.append(ret[1]) + + # Take our arrays of red shifts and locations, push them through to be interpolated into a 9x9 matrix + # for the CAC block to handle and then store these as a .json file to be added to the camera + # tuning file + print("\nCreating output grid") + Cam.log += '\nCreating output grid' + try: + rx, ry, bx, by = shifts_to_yaml(red_shift, blue_shift, image_size) + except RuntimeError as e: + print(str(e)) + Cam.log += "\nCAC correction failed! CAC will not be enabled." + return {} + + print("CAC correction complete!") + Cam.log += '\nCAC correction complete!' + + # Give the JSON dict back to the main ctt program + return {"strength": 1.0, "lut_rx": list(rx.round(2).reshape(81)), "lut_ry": list(ry.round(2).reshape(81)), "lut_bx": list(bx.round(2).reshape(81)), "lut_by": list(by.round(2).reshape(81))} diff --git a/utils/raspberrypi/ctt/ctt_ccm.py b/utils/raspberrypi/ctt/ctt_ccm.py index a09bfd09..07c943a8 100644 --- a/utils/raspberrypi/ctt/ctt_ccm.py +++ b/utils/raspberrypi/ctt/ctt_ccm.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_ccm.py - camera tuning tool for CCM (colour correction matrix) +# camera tuning tool for CCM (colour correction matrix) from ctt_image_load import * from ctt_awb import get_alsc_patches @@ -56,7 +56,7 @@ FInds colour correction matrices for list of images """ -def ccm(Cam, cal_cr_list, cal_cb_list): +def ccm(Cam, cal_cr_list, cal_cb_list, grid_size): global matrix_selection_types, typenum imgs = Cam.imgs """ @@ -133,9 +133,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list): Note: if alsc is disabled then colour_cals will be set to None and no the function will simply return the macbeth patches """ - r, b, g = get_alsc_patches(Img, colour_cals, grey=False) - # 256 values for each patch of sRGB values - + r, b, g = get_alsc_patches(Img, colour_cals, grey=False, grid_size=grid_size) """ do awb Note: awb is done by measuring the macbeth chart in the image, rather diff --git a/utils/raspberrypi/ctt/ctt_config_example.json b/utils/raspberrypi/ctt/ctt_config_example.json index c7f90761..1105862c 100644 --- a/utils/raspberrypi/ctt/ctt_config_example.json +++ b/utils/raspberrypi/ctt/ctt_config_example.json @@ -3,7 +3,8 @@ "plot": [], "alsc": { "do_alsc_colour": 1, - "luminance_strength": 0.5 + "luminance_strength": 0.8, + "max_gain": 8.0 }, "awb": { "greyworld": 0 @@ -13,4 +14,4 @@ "small": 0, "show": 0 } -}
\ No newline at end of file +} diff --git a/utils/raspberrypi/ctt/ctt_dots_locator.py b/utils/raspberrypi/ctt/ctt_dots_locator.py new file mode 100644 index 00000000..4945c04b --- /dev/null +++ b/utils/raspberrypi/ctt/ctt_dots_locator.py @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2023, Raspberry Pi Ltd +# +# find_dots.py - Used by CAC algorithm to convert image to set of dots + +''' +This file takes the black and white version of the image, along with +the color version. It then located the black dots on the image by +thresholding dark pixels. +In a rather fun way, the algorithm bounces around the thresholded area in a random path +We then use the maximum and minimum of these paths to determine the dot shape and size +This info is then used to return colored dots and locations back to the main file +''' + +import numpy as np +import random +from PIL import Image, ImageEnhance, ImageFilter + + +def find_dots_locations(rgb_image, color_threshold=100, dots_edge_avoid=75, image_edge_avoid=10, search_path_length=500, grid_scan_step_size=10, logfile=open("log.txt", "a+")): + # Initialise some starting variables + pixels = Image.fromarray(rgb_image) + pixels = pixels.convert("L") + enhancer = ImageEnhance.Contrast(pixels) + im_output = enhancer.enhance(1.4) + # We smooth it slightly to make it easier for the dot recognition program to locate the dots + im_output = im_output.filter(ImageFilter.GaussianBlur(radius=2)) + bw_image = np.array(im_output) + + location = [0, 0] + dots = [] + dots_location = [] + # the program takes away the edges - we don't want a dot that is half a circle, the + # centroids would all be wrong + for x in range(dots_edge_avoid, len(bw_image) - dots_edge_avoid, grid_scan_step_size): + for y in range(dots_edge_avoid, len(bw_image[0]) - dots_edge_avoid, grid_scan_step_size): + location = [x, y] + scrap_dot = False # A variable used to make sure that this is a valid dot + if (bw_image[location[0], location[1]] < color_threshold) and not (scrap_dot): + heading = "south" # Define a starting direction to move in + coords = [] + for i in range(search_path_length): # Creates a path of length `search_path_length`. This turns out to always be enough to work out the rough shape of the dot. + # Now make sure that the thresholded area doesn't come within 10 pixels of the edge of the image, ensures we capture all the CA + if ((image_edge_avoid < location[0] < len(bw_image) - image_edge_avoid) and (image_edge_avoid < location[1] < len(bw_image[0]) - image_edge_avoid)) and not (scrap_dot): + if heading == "south": + if bw_image[location[0] + 1, location[1]] < color_threshold: + # Here, notice it does not go south, but actually goes southeast + # This is crucial in ensuring that we make our way around the majority of the dot + location[0] = location[0] + 1 + location[1] = location[1] + 1 + heading = "south" + else: + # This happens when we reach a thresholded edge. We now randomly change direction and keep searching + dir = random.randint(1, 2) + if dir == 1: + heading = "west" + if dir == 2: + heading = "east" + + if heading == "east": + if bw_image[location[0], location[1] + 1] < color_threshold: + location[1] = location[1] + 1 + heading = "east" + else: + dir = random.randint(1, 2) + if dir == 1: + heading = "north" + if dir == 2: + heading = "south" + + if heading == "west": + if bw_image[location[0], location[1] - 1] < color_threshold: + location[1] = location[1] - 1 + heading = "west" + else: + dir = random.randint(1, 2) + if dir == 1: + heading = "north" + if dir == 2: + heading = "south" + + if heading == "north": + if bw_image[location[0] - 1, location[1]] < color_threshold: + location[0] = location[0] - 1 + heading = "north" + else: + dir = random.randint(1, 2) + if dir == 1: + heading = "west" + if dir == 2: + heading = "east" + # Log where our particle travels across the dot + coords.append([location[0], location[1]]) + else: + scrap_dot = True # We just don't have enough space around the dot, discard this one, and move on + if not scrap_dot: + # get the size of the dot surrounding the dot + x_coords = np.array(coords)[:, 0] + y_coords = np.array(coords)[:, 1] + hsquaresize = max(list(x_coords)) - min(list(x_coords)) + vsquaresize = max(list(y_coords)) - min(list(y_coords)) + # Create the bounding coordinates of the rectangle surrounding the dot + # Program uses the dotsize + half of the dotsize to ensure we get all that color fringing + extra_space_factor = 0.45 + top_left_x = (min(list(x_coords)) - int(hsquaresize * extra_space_factor)) + btm_right_x = max(list(x_coords)) + int(hsquaresize * extra_space_factor) + top_left_y = (min(list(y_coords)) - int(vsquaresize * extra_space_factor)) + btm_right_y = max(list(y_coords)) + int(vsquaresize * extra_space_factor) + # Overwrite the area of the dot to ensure we don't use it again + bw_image[top_left_x:btm_right_x, top_left_y:btm_right_y] = 255 + # Add the color version of the dot to the list to send off, along with some coordinates. + dots.append(rgb_image[top_left_x:btm_right_x, top_left_y:btm_right_y]) + dots_location.append([top_left_x, top_left_y]) + else: + # Dot was too close to the image border to be useable + pass + return dots, dots_location diff --git a/utils/raspberrypi/ctt/ctt_geq.py b/utils/raspberrypi/ctt/ctt_geq.py index c45addcd..5a91ebb4 100644 --- a/utils/raspberrypi/ctt/ctt_geq.py +++ b/utils/raspberrypi/ctt/ctt_geq.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_geq.py - camera tuning tool for GEQ (green equalisation) +# camera tuning tool for GEQ (green equalisation) from ctt_tools import * import matplotlib.pyplot as plt diff --git a/utils/raspberrypi/ctt/ctt_image_load.py b/utils/raspberrypi/ctt/ctt_image_load.py index 310c5e88..531de328 100644 --- a/utils/raspberrypi/ctt/ctt_image_load.py +++ b/utils/raspberrypi/ctt/ctt_image_load.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019-2020, Raspberry Pi Ltd # -# ctt_image_load.py - camera tuning tool image loading +# camera tuning tool image loading from ctt_tools import * from ctt_macbeth_locator import * @@ -350,6 +350,7 @@ def dng_load_image(Cam, im_str): c2 = np.left_shift(raw_data[1::2, 0::2].astype(np.int64), shift) c3 = np.left_shift(raw_data[1::2, 1::2].astype(np.int64), shift) Img.channels = [c0, c1, c2, c3] + Img.rgb = raw_im.postprocess() except Exception: print("\nERROR: failed to load DNG file", im_str) diff --git a/utils/raspberrypi/ctt/ctt_lux.py b/utils/raspberrypi/ctt/ctt_lux.py index 70855e1b..46be1512 100644 --- a/utils/raspberrypi/ctt/ctt_lux.py +++ b/utils/raspberrypi/ctt/ctt_lux.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_lux.py - camera tuning tool for lux level +# camera tuning tool for lux level from ctt_tools import * diff --git a/utils/raspberrypi/ctt/ctt_macbeth_locator.py b/utils/raspberrypi/ctt/ctt_macbeth_locator.py index 3e95df89..f22dbf31 100644 --- a/utils/raspberrypi/ctt/ctt_macbeth_locator.py +++ b/utils/raspberrypi/ctt/ctt_macbeth_locator.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_macbeth_locator.py - camera tuning tool Macbeth chart locator +# camera tuning tool Macbeth chart locator from ctt_ransac import * from ctt_tools import * @@ -57,6 +57,10 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): """ cor, mac, coords, msg = get_macbeth_chart(img, ref_data) + # Keep a list that will include this and any brightened up versions of + # the image for reuse. + all_images = [img] + """ following bits of code tries to fix common problems with simple techniques. @@ -71,6 +75,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): if cor < 0.75: a = 2 img_br = cv2.convertScaleAbs(img, alpha=a, beta=0) + all_images.append(img_br) cor_b, mac_b, coords_b, msg_b = get_macbeth_chart(img_br, ref_data) if cor_b > cor: cor, mac, coords, msg = cor_b, mac_b, coords_b, msg_b @@ -81,6 +86,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): if cor < 0.75: a = 4 img_br = cv2.convertScaleAbs(img, alpha=a, beta=0) + all_images.append(img_br) cor_b, mac_b, coords_b, msg_b = get_macbeth_chart(img_br, ref_data) if cor_b > cor: cor, mac, coords, msg = cor_b, mac_b, coords_b, msg_b @@ -128,23 +134,26 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): h_inc = int(h/6) """ for each subselection, look for a macbeth chart + loop over this and any brightened up images that we made to increase the + likelihood of success """ - for i in range(3): - for j in range(3): - w_s, h_s = i*w_inc, j*h_inc - img_sel = img[w_s:w_s+w_sel, h_s:h_s+h_sel] - cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data) - """ - if the correlation is better than the best then record the - scale and current subselection at which macbeth chart was - found. Also record the coordinates, macbeth chart and message. - """ - if cor_ij > cor: - cor = cor_ij - mac, coords, msg = mac_ij, coords_ij, msg_ij - ii, jj = i, j - w_best, h_best = w_inc, h_inc - d_best = 1 + for img_br in all_images: + for i in range(3): + for j in range(3): + w_s, h_s = i*w_inc, j*h_inc + img_sel = img_br[w_s:w_s+w_sel, h_s:h_s+h_sel] + cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data) + """ + if the correlation is better than the best then record the + scale and current subselection at which macbeth chart was + found. Also record the coordinates, macbeth chart and message. + """ + if cor_ij > cor: + cor = cor_ij + mac, coords, msg = mac_ij, coords_ij, msg_ij + ii, jj = i, j + w_best, h_best = w_inc, h_inc + d_best = 1 """ scale 2 @@ -157,17 +166,19 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): h_sel = int(h/2) w_inc = int(w/8) h_inc = int(h/8) - for i in range(5): - for j in range(5): - w_s, h_s = i*w_inc, j*h_inc - img_sel = img[w_s:w_s+w_sel, h_s:h_s+h_sel] - cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data) - if cor_ij > cor: - cor = cor_ij - mac, coords, msg = mac_ij, coords_ij, msg_ij - ii, jj = i, j - w_best, h_best = w_inc, h_inc - d_best = 2 + # Again, loop over any brightened up images as well + for img_br in all_images: + for i in range(5): + for j in range(5): + w_s, h_s = i*w_inc, j*h_inc + img_sel = img_br[w_s:w_s+w_sel, h_s:h_s+h_sel] + cor_ij, mac_ij, coords_ij, msg_ij = get_macbeth_chart(img_sel, ref_data) + if cor_ij > cor: + cor = cor_ij + mac, coords, msg = mac_ij, coords_ij, msg_ij + ii, jj = i, j + w_best, h_best = w_inc, h_inc + d_best = 2 """ The following code checks for macbeth charts at even smaller scales. This @@ -238,7 +249,7 @@ def find_macbeth(Cam, img, mac_config=(0, 0)): print error or success message """ print(msg) - Cam.log += '\n' + msg + Cam.log += '\n' + str(msg) if msg == success_msg: coords_fit = coords Cam.log += '\nMacbeth chart vertices:\n' @@ -606,7 +617,7 @@ def get_macbeth_chart(img, ref_data): '\nNot enough squares found' '\nPossible problems:\n' '- Macbeth chart is occluded\n' - '- Macbeth chart is too dark of bright\n' + '- Macbeth chart is too dark or bright\n' ) ref_cents = np.array(ref_cents) diff --git a/utils/raspberrypi/ctt/ctt_noise.py b/utils/raspberrypi/ctt/ctt_noise.py index 3270bf34..0b18d83f 100644 --- a/utils/raspberrypi/ctt/ctt_noise.py +++ b/utils/raspberrypi/ctt/ctt_noise.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_noise.py - camera tuning tool noise calibration +# camera tuning tool noise calibration from ctt_image_load import * import matplotlib.pyplot as plt diff --git a/utils/raspberrypi/ctt/ctt_pisp.py b/utils/raspberrypi/ctt/ctt_pisp.py new file mode 100755 index 00000000..a59b053c --- /dev/null +++ b/utils/raspberrypi/ctt/ctt_pisp.py @@ -0,0 +1,805 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# +# ctt_pisp.py - camera tuning tool data for PiSP platforms + + +json_template = { + "rpi.black_level": { + "black_level": 4096 + }, + "rpi.lux": { + "reference_shutter_speed": 10000, + "reference_gain": 1, + "reference_aperture": 1.0 + }, + "rpi.dpc": { + "strength": 1 + }, + "rpi.noise": { + }, + "rpi.geq": { + }, + "rpi.denoise": + { + "normal": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 0.8, + "threshold": 0.05 + } + }, + "hdr": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + }, + "night": + { + "sdn": + { + "deviation": 1.6, + "strength": 0.5, + "deviation2": 3.2, + "deviation_no_tdn": 3.2, + "strength_no_tdn": 0.75 + }, + "cdn": + { + "deviation": 200, + "strength": 0.3 + }, + "tdn": + { + "deviation": 1.3, + "threshold": 0.1 + } + } + }, + "rpi.awb": { + "priors": [ + {"lux": 0, "prior": [2000, 1.0, 3000, 0.0, 13000, 0.0]}, + {"lux": 800, "prior": [2000, 0.0, 6000, 2.0, 13000, 2.0]}, + {"lux": 1500, "prior": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]} + ], + "modes": { + "auto": {"lo": 2500, "hi": 7700}, + "incandescent": {"lo": 2500, "hi": 3000}, + "tungsten": {"lo": 3000, "hi": 3500}, + "fluorescent": {"lo": 4000, "hi": 4700}, + "indoor": {"lo": 3000, "hi": 5000}, + "daylight": {"lo": 5500, "hi": 6500}, + "cloudy": {"lo": 7000, "hi": 8000} + }, + "bayes": 1 + }, + "rpi.agc": + { + "channels": + [ + { + "comment": "Channel 0 is normal AGC", + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 10000, 30000, 60000, 66666 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 5000, 10000, 20000, 60000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 10000, 30000, 60000, 90000, 120000 ], + "gain": [ 1.0, 1.5, 2.0, 4.0, 8.0, 12.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + }, + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.5, + "y_target": + [ + 0, 0.17, + 1000, 0.17 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 1 is the HDR short channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 60000 ], + "gain": [ 1.0, 1.0, 1.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.95, + "q_hi": 1.0, + "y_target": + [ + 0, 0.7, + 1000, 0.7 + ] + }, + { + "bound": "LOWER", + "q_lo": 0.0, + "q_hi": 0.2, + "y_target": + [ + 0, 0.002, + 1000, 0.002 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 2 is the HDR long channel", + "desaturate": 0, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 30000, 60000 ], + "gain": [ 1.0, 2.0, 4.0, 8.0 ] + } + }, + "constraint_modes": + { + "normal": [ + ], + "highlight": [ + ], + "shadows": [ + ] + }, + "channel_constraints": + [ + { + "bound": "UPPER", + "channel": 4, + "factor": 8 + }, + { + "bound": "LOWER", + "channel": 4, + "factor": 2 + } + ], + "y_target": + [ + 0, 0.16, + 1000, 0.165, + 10000, 0.17 + ] + }, + { + "comment": "Channel 3 is the night mode channel", + "base_ev": 0.33, + "metering_modes": + { + "centre-weighted": + { + "weights": + [ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 3, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + ] + }, + "spot": + { + "weights": + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + }, + "matrix": + { + "weights": + [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + } + }, + "exposure_modes": + { + "normal": + { + "shutter": [ 100, 20000, 66666 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "short": + { + "shutter": [ 100, 20000, 33333 ], + "gain": [ 1.0, 2.0, 4.0 ] + }, + "long": + { + "shutter": [ 100, 20000, 66666, 120000 ], + "gain": [ 1.0, 2.0, 4.0, 4.0 ] + } + }, + "constraint_modes": + { + "normal": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ], + "highlight": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + }, + { + "bound": "UPPER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.8, + 1000, 0.8 + ] + } + ], + "shadows": [ + { + "bound": "LOWER", + "q_lo": 0.98, + "q_hi": 1.0, + "y_target": + [ + 0, 0.5, + 1000, 0.5 + ] + } + ] + }, + "y_target": + [ + 0, 0.16, + 1000, 0.16, + 10000, 0.17 + ] + } + ] + }, + "rpi.alsc": { + 'omega': 1.3, + 'n_iter': 100, + 'luminance_strength': 0.8, + }, + "rpi.contrast": { + "ce_enable": 1, + "gamma_curve": [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + }, + "rpi.ccm": { + }, + "rpi.cac": { + }, + "rpi.sharpen": { + "threshold": 0.25, + "limit": 1.0, + "strength": 1.0 + }, + "rpi.hdr": + { + "Off": + { + "cadence": [ 0 ] + }, + "MultiExposureUnmerged": + { + "cadence": [ 1, 2 ], + "channel_map": { "short": 1, "long": 2 } + }, + "SingleExposure": + { + "cadence": [1], + "channel_map": { "short": 1 }, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "MultiExposure": + { + "cadence": [1, 2], + "channel_map": { "short": 1, "long": 2 }, + "stitch_enable": 1, + "spatial_gain": 2.0, + "tonemap_enable": 1 + }, + "Night": + { + "cadence": [ 3 ], + "channel_map": { "night": 3 }, + "tonemap_enable": 1, + "tonemap": + [ + 0, 0, + 5000, 20000, + 10000, 30000, + 20000, 47000, + 30000, 55000, + 65535, 65535 + ] + } + } +} + +grid_size = (32, 32) diff --git a/utils/raspberrypi/ctt/ctt_pretty_print_json.py b/utils/raspberrypi/ctt/ctt_pretty_print_json.py index 3e3b8475..a4cae62d 100755 --- a/utils/raspberrypi/ctt/ctt_pretty_print_json.py +++ b/utils/raspberrypi/ctt/ctt_pretty_print_json.py @@ -19,13 +19,19 @@ class Encoder(json.JSONEncoder): self.indentation_level = 0 self.hard_break = 120 self.custom_elems = { + 'weights': 15, 'table': 16, 'luminance_lut': 16, 'ct_curve': 3, 'ccm': 3, + 'lut_rx': 9, + 'lut_bx': 9, + 'lut_by': 9, + 'lut_ry': 9, 'gamma_curve': 2, 'y_target': 2, - 'prior': 2 + 'prior': 2, + 'tonemap': 2 } def encode(self, o, node_key=None): @@ -87,7 +93,7 @@ class Encoder(json.JSONEncoder): return self.encode(o) -def pretty_print(in_json: dict) -> str: +def pretty_print(in_json: dict, custom_elems={}) -> str: if 'version' not in in_json or \ 'target' not in in_json or \ @@ -95,12 +101,15 @@ def pretty_print(in_json: dict) -> str: in_json['version'] < 2.0: raise RuntimeError('Incompatible JSON dictionary has been provided') - return json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False) + encoder = Encoder(indent=4, sort_keys=False) + encoder.custom_elems |= custom_elems + return encoder.encode(in_json) #json.dumps(in_json, cls=Encoder, indent=4, sort_keys=False) if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description= 'Prettify a version 2.0 camera tuning config JSON file.') + parser.add_argument('-t', '--target', type=str, help='Target platform', choices=['pisp', 'vc4'], default='vc4') parser.add_argument('input', type=str, help='Input tuning file.') parser.add_argument('output', type=str, nargs='?', help='Output converted tuning file. If not provided, the input file will be updated in-place.', @@ -110,7 +119,12 @@ if __name__ == "__main__": with open(args.input, 'r') as f: in_json = json.load(f) - out_json = pretty_print(in_json) + if args.target == 'pisp': + from ctt_pisp import grid_size + elif args.target == 'vc4': + from ctt_vc4 import grid_size + + out_json = pretty_print(in_json, custom_elems={'table': grid_size[0], 'luminance_lut': grid_size[0]}) with open(args.output if args.output is not None else args.input, 'w') as f: f.write(out_json) diff --git a/utils/raspberrypi/ctt/ctt_ransac.py b/utils/raspberrypi/ctt/ctt_ransac.py index 9ed7d93c..01bba302 100644 --- a/utils/raspberrypi/ctt/ctt_ransac.py +++ b/utils/raspberrypi/ctt/ctt_ransac.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_ransac.py - camera tuning tool RANSAC selector for Macbeth chart locator +# camera tuning tool RANSAC selector for Macbeth chart locator import numpy as np diff --git a/utils/raspberrypi/ctt/ctt_tools.py b/utils/raspberrypi/ctt/ctt_tools.py index 79195289..50b01ecf 100644 --- a/utils/raspberrypi/ctt/ctt_tools.py +++ b/utils/raspberrypi/ctt/ctt_tools.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# ctt_tools.py - camera tuning tool miscellaneous +# camera tuning tool miscellaneous import time import re @@ -65,11 +65,12 @@ def parse_input(): directory = get_config(args_dict, '-i', None, 'string') config = get_config(args_dict, '-c', None, 'string') log_path = get_config(args_dict, '-l', None, 'string') + target = get_config(args_dict, '-t', "vc4", 'string') if directory is None: raise ArgError('\n\nERROR! No input directory given.') if json_output is None: raise ArgError('\n\nERROR! No output json given.') - return json_output, directory, config, log_path + return json_output, directory, config, log_path, target """ diff --git a/utils/raspberrypi/ctt/ctt_vc4.py b/utils/raspberrypi/ctt/ctt_vc4.py new file mode 100755 index 00000000..7154e110 --- /dev/null +++ b/utils/raspberrypi/ctt/ctt_vc4.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# +# ctt_vc4.py - camera tuning tool data for VC4 platforms + + +json_template = { + "rpi.black_level": { + "black_level": 4096 + }, + "rpi.dpc": { + }, + "rpi.lux": { + "reference_shutter_speed": 10000, + "reference_gain": 1, + "reference_aperture": 1.0 + }, + "rpi.noise": { + }, + "rpi.geq": { + }, + "rpi.sdn": { + }, + "rpi.awb": { + "priors": [ + {"lux": 0, "prior": [2000, 1.0, 3000, 0.0, 13000, 0.0]}, + {"lux": 800, "prior": [2000, 0.0, 6000, 2.0, 13000, 2.0]}, + {"lux": 1500, "prior": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]} + ], + "modes": { + "auto": {"lo": 2500, "hi": 8000}, + "incandescent": {"lo": 2500, "hi": 3000}, + "tungsten": {"lo": 3000, "hi": 3500}, + "fluorescent": {"lo": 4000, "hi": 4700}, + "indoor": {"lo": 3000, "hi": 5000}, + "daylight": {"lo": 5500, "hi": 6500}, + "cloudy": {"lo": 7000, "hi": 8600} + }, + "bayes": 1 + }, + "rpi.agc": { + "metering_modes": { + "centre-weighted": { + "weights": [3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0] + }, + "spot": { + "weights": [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "matrix": { + "weights": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + } + }, + "exposure_modes": { + "normal": { + "shutter": [100, 10000, 30000, 60000, 120000], + "gain": [1.0, 2.0, 4.0, 6.0, 6.0] + }, + "short": { + "shutter": [100, 5000, 10000, 20000, 120000], + "gain": [1.0, 2.0, 4.0, 6.0, 6.0] + } + }, + "constraint_modes": { + "normal": [ + {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]} + ], + "highlight": [ + {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]}, + {"bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.8, 1000, 0.8]} + ] + }, + "y_target": [0, 0.16, 1000, 0.165, 10000, 0.17] + }, + "rpi.alsc": { + 'omega': 1.3, + 'n_iter': 100, + 'luminance_strength': 0.7, + }, + "rpi.contrast": { + "ce_enable": 1, + "gamma_curve": [ + 0, 0, + 1024, 5040, + 2048, 9338, + 3072, 12356, + 4096, 15312, + 5120, 18051, + 6144, 20790, + 7168, 23193, + 8192, 25744, + 9216, 27942, + 10240, 30035, + 11264, 32005, + 12288, 33975, + 13312, 35815, + 14336, 37600, + 15360, 39168, + 16384, 40642, + 18432, 43379, + 20480, 45749, + 22528, 47753, + 24576, 49621, + 26624, 51253, + 28672, 52698, + 30720, 53796, + 32768, 54876, + 36864, 57012, + 40960, 58656, + 45056, 59954, + 49152, 61183, + 53248, 62355, + 57344, 63419, + 61440, 64476, + 65535, 65535 + ] + }, + "rpi.ccm": { + }, + "rpi.sharpen": { + } +} + +grid_size = (16, 12) diff --git a/utils/rkisp1/gen-csc-table.py b/utils/rkisp1/gen-csc-table.py index c47f5042..2db84feb 100755 --- a/utils/rkisp1/gen-csc-table.py +++ b/utils/rkisp1/gen-csc-table.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2022, Ideas on Board Oy # @@ -147,6 +147,8 @@ def main(argv): description='Generate color space conversion table coefficients with ' 'configurable fixed-point precision.' ) + parser.add_argument('--format', '-f', choices=['dec', 'hex'], default='hex', + help='Number format') parser.add_argument('--invert', '-i', action='store_true', help='Invert the color space conversion (YUV -> RGB)') parser.add_argument('--precision', '-p', default='Q1.7', @@ -190,19 +192,29 @@ def main(argv): else: line = round_array(line) - # Convert coefficients to the number of bits selected by the precision. - # Negative values will be turned into positive integers using 2's - # complement. - line = [coeff & ((1 << precision.total) - 1) for coeff in line] + if args.format == 'hex': + # Convert coefficients to the number of bits selected by the precision. + # Negative values will be turned into positive integers using 2's + # complement. + line = [coeff & ((1 << precision.total) - 1) for coeff in line] + rounded_coeffs.append(line) # Print the result as C code. nbits = 1 << (precision.total - 1).bit_length() nbytes = nbits // 4 - print(f'static const u{nbits} {"yuv2rgb" if args.invert else "rgb2yuv"}_{args.encoding}_{quantization.name.lower()}_coeffs[] = {{') + + if args.format == 'hex': + coeff_fmt = '0x{0:0' + str(nbytes) + 'x}' + sign = 'u' + else: + coeff_fmt = '{0}' + sign = 's' + + print(f'static const {sign}{nbits} {"yuv2rgb" if args.invert else "rgb2yuv"}_{args.encoding}_{quantization.name.lower()}_coeffs[] = {{') for line in rounded_coeffs: - line = [f'0x{coeff:0{nbytes}x}' for coeff in line] + line = [coeff_fmt.format(coeff) for coeff in line] print(f'\t{", ".join(line)},') diff --git a/utils/rkisp1/rkisp1-capture.sh b/utils/rkisp1/rkisp1-capture.sh index c5f859f2..d767e31d 100755 --- a/utils/rkisp1/rkisp1-capture.sh +++ b/utils/rkisp1/rkisp1-capture.sh @@ -4,8 +4,7 @@ # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # -# rkisp-capture.sh - Capture processed frames from cameras based on the -# Rockchip ISP1 +# Capture processed frames from cameras based on the Rockchip ISP1 # # The scripts makes use of the following tools, which are expected to be # executable from the system-wide path or from the local directory: diff --git a/utils/tracepoints/analyze-ipa-trace.py b/utils/tracepoints/analyze-ipa-trace.py index 50fbbf42..92e8a235 100755 --- a/utils/tracepoints/analyze-ipa-trace.py +++ b/utils/tracepoints/analyze-ipa-trace.py @@ -4,7 +4,7 @@ # # Author: Paul Elder <paul.elder@ideasonboard.com> # -# analyze-ipa-trace.py - Example of how to extract information from libcamera lttng traces +# Example of how to extract information from libcamera lttng traces import argparse import bt2 diff --git a/utils/tracepoints/meson.build b/utils/tracepoints/meson.build deleted file mode 100644 index 807230fc..00000000 --- a/utils/tracepoints/meson.build +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 - -py_modules += ['jinja2'] - -gen_tracepoints_header = find_program('./gen-tp-header.py') diff --git a/utils/tuning/README.rst b/utils/tuning/README.rst index ce533b2c..89a1d61e 100644 --- a/utils/tuning/README.rst +++ b/utils/tuning/README.rst @@ -1,11 +1,20 @@ .. SPDX-License-Identifier: CC-BY-SA-4.0 -.. TODO: Write an overview of libtuning +libcamera tuning tools +====================== -Dependencies ------------- +.. Note:: The tuning tools are still very much work in progress. If in doubt, + please ask on the mailing list. + +.. todo:: + Write documentation + +Installation of dependencies +---------------------------- + +:: + # Using a venv + python3 -m venv venv + . ./venv/bin/activate + pip3 install -r requirements.txt -- cv2 -- numpy -- pyexiv2 -- rawpy diff --git a/utils/tuning/config-example.yaml b/utils/tuning/config-example.yaml new file mode 100644 index 00000000..5593eaef --- /dev/null +++ b/utils/tuning/config-example.yaml @@ -0,0 +1,54 @@ +general: + disable: [] + plot: [] + alsc: + do_alsc_colour: 1 + luminance_strength: 0.5 + awb: + # Algorithm can either be 'grey' or 'bayes' + algorithm: bayes + # Priors is only used for the bayes algorithm. They are defined in linear + # space. A good staring point is: + # - lux: 0 + # ct: [ 2000, 3000, 13000 ] + # probability: [ 1.005, 1.0, 1.0 ] + # - lux: 800 + # ct: [ 2000, 6000, 13000 ] + # probability: [ 1.0, 1.01, 1.01 ] + # - lux: 1500 + # ct: [ 2000, 4000, 6000, 6500, 7000, 13000 ] + # probability: [ 1.0, 1.005, 1.032, 1.037, 1.01, 1.01 ] + priors: + - lux: 0 + ct: [ 2000, 13000 ] + probability: [ 1.0, 1.0 ] + AwbMode: + AwbAuto: + lo: 2500 + hi: 8000 + AwbIncandescent: + lo: 2500 + hi: 3000 + AwbTungsten: + lo: 3000 + hi: 3500 + AwbFluorescent: + lo: 4000 + hi: 4700 + AwbIndoor: + lo: 3000 + hi: 5000 + AwbDaylight: + lo: 5500 + hi: 6500 + AwbCloudy: + lo: 6500 + hi: 8000 + # One custom mode can be defined if needed + #AwbCustom: + # lo: 2000 + # hi: 1300 + macbeth: + small: 1 + show: 0 +# blacklevel: 32
\ No newline at end of file diff --git a/utils/tuning/libtuning/average.py b/utils/tuning/libtuning/average.py index e28770d7..c41075a1 100644 --- a/utils/tuning/libtuning/average.py +++ b/utils/tuning/libtuning/average.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# average.py - Wrapper for numpy averaging functions to enable duck-typing +# Wrapper for numpy averaging functions to enable duck-typing import numpy as np diff --git a/utils/tuning/libtuning/ctt_awb.py b/utils/tuning/libtuning/ctt_awb.py new file mode 100644 index 00000000..240f37e6 --- /dev/null +++ b/utils/tuning/libtuning/ctt_awb.py @@ -0,0 +1,378 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# +# camera tuning tool for AWB + +import logging + +import matplotlib.pyplot as plt +from bisect import bisect_left +from scipy.optimize import fmin +import numpy as np + +from .image import Image + +logger = logging.getLogger(__name__) + +""" +obtain piecewise linear approximation for colour curve +""" +def awb(imgs, cal_cr_list, cal_cb_list, plot): + """ + condense alsc calibration tables into one dictionary + """ + if cal_cr_list is None: + colour_cals = None + else: + colour_cals = {} + for cr, cb in zip(cal_cr_list, cal_cb_list): + cr_tab = cr['table'] + cb_tab = cb['table'] + """ + normalise tables so min value is 1 + """ + cr_tab = cr_tab/np.min(cr_tab) + cb_tab = cb_tab/np.min(cb_tab) + colour_cals[cr['ct']] = [cr_tab, cb_tab] + """ + obtain data from greyscale macbeth patches + """ + rb_raw = [] + rbs_hat = [] + for Img in imgs: + logger.info(f'Processing {Img.name}') + """ + get greyscale patches with alsc applied if alsc enabled. + Note: if alsc is disabled then colour_cals will be set to None and the + function will just return the greyscale patches + """ + r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals) + """ + calculate ratio of r, b to g + """ + r_g = np.mean(r_patchs/g_patchs) + b_g = np.mean(b_patchs/g_patchs) + logger.info(f' r : {r_g:.4f} b : {b_g:.4f}') + """ + The curve tends to be better behaved in so-called hatspace. + R, B, G represent the individual channels. The colour curve is plotted in + r, b space, where: + r = R/G + b = B/G + This will be referred to as dehatspace... (sorry) + Hatspace is defined as: + r_hat = R/(R+B+G) + b_hat = B/(R+B+G) + To convert from dehatspace to hastpace (hat operation): + r_hat = r/(1+r+b) + b_hat = b/(1+r+b) + To convert from hatspace to dehatspace (dehat operation): + r = r_hat/(1-r_hat-b_hat) + b = b_hat/(1-r_hat-b_hat) + Proof is left as an excercise to the reader... + Throughout the code, r and b are sometimes referred to as r_g and b_g + as a reminder that they are ratios + """ + r_g_hat = r_g/(1+r_g+b_g) + b_g_hat = b_g/(1+r_g+b_g) + logger.info(f' r_hat : {r_g_hat:.4f} b_hat : {b_g_hat:.4f}') + rbs_hat.append((r_g_hat, b_g_hat, Img.color)) + rb_raw.append((r_g, b_g)) + + logger.info('Finished processing images') + """ + sort all lits simultaneously by r_hat + """ + rbs_zip = list(zip(rbs_hat, rb_raw)) + rbs_zip.sort(key=lambda x: x[0][0]) + rbs_hat, rb_raw = list(zip(*rbs_zip)) + """ + unzip tuples ready for processing + """ + rbs_hat = list(zip(*rbs_hat)) + rb_raw = list(zip(*rb_raw)) + """ + fit quadratic fit to r_g hat and b_g_hat + """ + a, b, c = np.polyfit(rbs_hat[0], rbs_hat[1], 2) + logger.info('Fit quadratic curve in hatspace') + """ + the algorithm now approximates the shortest distance from each point to the + curve in dehatspace. Since the fit is done in hatspace, it is easier to + find the actual shortest distance in hatspace and use the projection back + into dehatspace as an overestimate. + The distance will be used for two things: + 1) In the case that colour temperature does not strictly decrease with + increasing r/g, the closest point to the line will be chosen out of an + increasing pair of colours. + + 2) To calculate transverse negative an dpositive, the maximum positive + and negative distance from the line are chosen. This benefits from the + overestimate as the transverse pos/neg are upper bound values. + """ + """ + define fit function + """ + def f(x): + return a*x**2 + b*x + c + """ + iterate over points (R, B are x and y coordinates of points) and calculate + distance to line in dehatspace + """ + dists = [] + for i, (R, B) in enumerate(zip(rbs_hat[0], rbs_hat[1])): + """ + define function to minimise as square distance between datapoint and + point on curve. Squaring is monotonic so minimising radius squared is + equivalent to minimising radius + """ + def f_min(x): + y = f(x) + return((x-R)**2+(y-B)**2) + """ + perform optimisation with scipy.optmisie.fmin + """ + x_hat = fmin(f_min, R, disp=0)[0] + y_hat = f(x_hat) + """ + dehat + """ + x = x_hat/(1-x_hat-y_hat) + y = y_hat/(1-x_hat-y_hat) + rr = R/(1-R-B) + bb = B/(1-R-B) + """ + calculate euclidean distance in dehatspace + """ + dist = ((x-rr)**2+(y-bb)**2)**0.5 + """ + return negative if point is below the fit curve + """ + if (x+y) > (rr+bb): + dist *= -1 + dists.append(dist) + logger.info('Found closest point on fit line to each point in dehatspace') + """ + calculate wiggle factors in awb. 10% added since this is an upper bound + """ + transverse_neg = - np.min(dists) * 1.1 + transverse_pos = np.max(dists) * 1.1 + logger.info(f'Transverse pos : {transverse_pos:.5f}') + logger.info(f'Transverse neg : {transverse_neg:.5f}') + """ + set minimum transverse wiggles to 0.1 . + Wiggle factors dictate how far off of the curve the algorithm searches. 0.1 + is a suitable minimum that gives better results for lighting conditions not + within calibration dataset. Anything less will generalise poorly. + """ + if transverse_pos < 0.01: + transverse_pos = 0.01 + logger.info('Forced transverse pos to 0.01') + if transverse_neg < 0.01: + transverse_neg = 0.01 + logger.info('Forced transverse neg to 0.01') + + """ + generate new b_hat values at each r_hat according to fit + """ + r_hat_fit = np.array(rbs_hat[0]) + b_hat_fit = a*r_hat_fit**2 + b*r_hat_fit + c + """ + transform from hatspace to dehatspace + """ + r_fit = r_hat_fit/(1-r_hat_fit-b_hat_fit) + b_fit = b_hat_fit/(1-r_hat_fit-b_hat_fit) + c_fit = np.round(rbs_hat[2], 0) + """ + round to 4dp + """ + r_fit = np.where((1000*r_fit) % 1 <= 0.05, r_fit+0.0001, r_fit) + r_fit = np.where((1000*r_fit) % 1 >= 0.95, r_fit-0.0001, r_fit) + b_fit = np.where((1000*b_fit) % 1 <= 0.05, b_fit+0.0001, b_fit) + b_fit = np.where((1000*b_fit) % 1 >= 0.95, b_fit-0.0001, b_fit) + r_fit = np.round(r_fit, 4) + b_fit = np.round(b_fit, 4) + """ + The following code ensures that colour temperature decreases with + increasing r/g + """ + """ + iterate backwards over list for easier indexing + """ + i = len(c_fit) - 1 + while i > 0: + if c_fit[i] > c_fit[i-1]: + logger.info('Colour temperature increase found') + logger.info(f'{c_fit[i - 1]} K at r = {r_fit[i - 1]} to ') + logger.info(f'{c_fit[i]} K at r = {r_fit[i]}') + """ + if colour temperature increases then discard point furthest from + the transformed fit (dehatspace) + """ + error_1 = abs(dists[i-1]) + error_2 = abs(dists[i]) + logger.info('Distances from fit:') + logger.info(f'{c_fit[i]} K : {error_1:.5f}') + logger.info(f'{c_fit[i - 1]} K : {error_2:.5f}') + """ + find bad index + note that in python false = 0 and true = 1 + """ + bad = i - (error_1 < error_2) + logger.info(f'Point at {c_fit[bad]} K deleted as ') + logger.info('it is furthest from fit') + """ + delete bad point + """ + r_fit = np.delete(r_fit, bad) + b_fit = np.delete(b_fit, bad) + c_fit = np.delete(c_fit, bad).astype(np.uint16) + """ + note that if a point has been discarded then the length has decreased + by one, meaning that decreasing the index by one will reassess the kept + point against the next point. It is therefore possible, in theory, for + two adjacent points to be discarded, although probably rare + """ + i -= 1 + + """ + return formatted ct curve, ordered by increasing colour temperature + """ + ct_curve = list(np.array(list(zip(b_fit, r_fit, c_fit))).flatten())[::-1] + logger.info('Final CT curve:') + for i in range(len(ct_curve)//3): + j = 3*i + logger.info(f' ct: {ct_curve[j]} ') + logger.info(f' r: {ct_curve[j + 1]} ') + logger.info(f' b: {ct_curve[j + 2]} ') + + """ + plotting code for debug + """ + if plot: + x = np.linspace(np.min(rbs_hat[0]), np.max(rbs_hat[0]), 100) + y = a*x**2 + b*x + c + plt.subplot(2, 1, 1) + plt.title('hatspace') + plt.plot(rbs_hat[0], rbs_hat[1], ls='--', color='blue') + plt.plot(x, y, color='green', ls='-') + plt.scatter(rbs_hat[0], rbs_hat[1], color='red') + for i, ct in enumerate(rbs_hat[2]): + plt.annotate(str(ct), (rbs_hat[0][i], rbs_hat[1][i])) + plt.xlabel('$\\hat{r}$') + plt.ylabel('$\\hat{b}$') + """ + optional set axes equal to shortest distance so line really does + looks perpendicular and everybody is happy + """ + # ax = plt.gca() + # ax.set_aspect('equal') + plt.grid() + plt.subplot(2, 1, 2) + plt.title('dehatspace - indoors?') + plt.plot(r_fit, b_fit, color='blue') + plt.scatter(rb_raw[0], rb_raw[1], color='green') + plt.scatter(r_fit, b_fit, color='red') + for i, ct in enumerate(c_fit): + plt.annotate(str(ct), (r_fit[i], b_fit[i])) + plt.xlabel('$r$') + plt.ylabel('$b$') + """ + optional set axes equal to shortest distance so line really does + looks perpendicular and everybody is happy + """ + # ax = plt.gca() + # ax.set_aspect('equal') + plt.subplots_adjust(hspace=0.5) + plt.grid() + plt.show() + """ + end of plotting code + """ + return(ct_curve, np.round(transverse_pos, 5), np.round(transverse_neg, 5)) + + +""" +obtain greyscale patches and perform alsc colour correction +""" +def get_alsc_patches(Img, colour_cals, grey=True): + """ + get patch centre coordinates, image colour and the actual + patches for each channel, remembering to subtract blacklevel + If grey then only greyscale patches considered + """ + patches = Img.patches + if grey: + cen_coords = Img.cen_coords[3::4] + col = Img.color + r_patchs = patches[0][3::4] - Img.blacklevel_16 + b_patchs = patches[3][3::4] - Img.blacklevel_16 + """ + note two green channels are averages + """ + g_patchs = (patches[1][3::4]+patches[2][3::4])/2 - Img.blacklevel_16 + else: + cen_coords = Img.cen_coords + col = Img.color + r_patchs = patches[0] - Img.blacklevel_16 + b_patchs = patches[3] - Img.blacklevel_16 + g_patchs = (patches[1]+patches[2])/2 - Img.blacklevel_16 + + if colour_cals is None: + return r_patchs, b_patchs, g_patchs + """ + find where image colour fits in alsc colour calibration tables + """ + cts = list(colour_cals.keys()) + pos = bisect_left(cts, col) + """ + if img colour is below minimum or above maximum alsc calibration colour, simply + pick extreme closest to img colour + """ + if pos % len(cts) == 0: + """ + this works because -0 = 0 = first and -1 = last index + """ + col_tabs = np.array(colour_cals[cts[-pos//len(cts)]]) + """ + else, perform linear interpolation between existing alsc colour + calibration tables + """ + else: + bef = cts[pos-1] + aft = cts[pos] + da = col-bef + db = aft-col + bef_tabs = np.array(colour_cals[bef]) + aft_tabs = np.array(colour_cals[aft]) + col_tabs = (bef_tabs*db + aft_tabs*da)/(da+db) + col_tabs = np.reshape(col_tabs, (2, 12, 16)) + """ + calculate dx, dy used to calculate alsc table + """ + w, h = Img.w/2, Img.h/2 + dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12)) + """ + make list of pairs of gains for each patch by selecting the correct value + in alsc colour calibration table + """ + patch_gains = [] + for cen in cen_coords: + x, y = cen[0]//dx, cen[1]//dy + # We could probably do with some better spatial interpolation here? + col_gains = (col_tabs[0][y][x], col_tabs[1][y][x]) + patch_gains.append(col_gains) + + """ + multiply the r and b channels in each patch by the respective gain, finally + performing the alsc colour correction + """ + for i, gains in enumerate(patch_gains): + r_patchs[i] = r_patchs[i] * gains[0] + b_patchs[i] = b_patchs[i] * gains[1] + + """ + return greyscale patches, g channel and correct r, b channels + """ + return r_patchs, b_patchs, g_patchs diff --git a/utils/tuning/libtuning/ctt_ccm.py b/utils/tuning/libtuning/ctt_ccm.py new file mode 100644 index 00000000..2e87a667 --- /dev/null +++ b/utils/tuning/libtuning/ctt_ccm.py @@ -0,0 +1,408 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# +# camera tuning tool for CCM (colour correction matrix) + +import logging + +import numpy as np +from scipy.optimize import minimize + +from . import ctt_colors as colors +from .image import Image +from .ctt_awb import get_alsc_patches +from .utils import visualise_macbeth_chart + +logger = logging.getLogger(__name__) + +""" +takes 8-bit macbeth chart values, degammas and returns 16 bit +""" + +''' +This program has many options from which to derive the color matrix from. +The first is average. This minimises the average delta E across all patches of +the macbeth chart. Testing across all cameras yeilded this as the most color +accurate and vivid. Other options are avalible however. +Maximum minimises the maximum Delta E of the patches. It iterates through till +a minimum maximum is found (so that there is +not one patch that deviates wildly.) +This yields generally good results but overall the colors are less accurate +Have a fiddle with maximum and see what you think. +The final option allows you to select the patches for which to average across. +This means that you can bias certain patches, for instance if you want the +reds to be more accurate. +''' + +matrix_selection_types = ["average", "maximum", "patches"] +typenum = 0 # select from array above, 0 = average, 1 = maximum, 2 = patches +test_patches = [1, 2, 5, 8, 9, 12, 14] + +''' +Enter patches to test for. Can also be entered twice if you +would like twice as much bias on one patch. +''' + + +def degamma(x): + x = x / ((2 ** 8) - 1) # takes 255 and scales it down to one + x = np.where(x < 0.04045, x / 12.92, ((x + 0.055) / 1.055) ** 2.4) + x = x * ((2 ** 16) - 1) # takes one and scales up to 65535, 16 bit color + return x + + +def gamma(x): + # Take 3 long array of color values and gamma them + return [((colour / 255) ** (1 / 2.4) * 1.055 - 0.055) * 255 for colour in x] + + +""" +FInds colour correction matrices for list of images +""" + + +def ccm(imgs, cal_cr_list, cal_cb_list): + global matrix_selection_types, typenum + """ + standard macbeth chart colour values + """ + m_rgb = np.array([ # these are in RGB + [116, 81, 67], # dark skin + [199, 147, 129], # light skin + [91, 122, 156], # blue sky + [90, 108, 64], # foliage + [130, 128, 176], # blue flower + [92, 190, 172], # bluish green + [224, 124, 47], # orange + [68, 91, 170], # purplish blue + [198, 82, 97], # moderate red + [94, 58, 106], # purple + [159, 189, 63], # yellow green + [230, 162, 39], # orange yellow + [35, 63, 147], # blue + [67, 149, 74], # green + [180, 49, 57], # red + [238, 198, 20], # yellow + [193, 84, 151], # magenta + [0, 136, 170], # cyan (goes out of gamut) + [245, 245, 243], # white 9.5 + [200, 202, 202], # neutral 8 + [161, 163, 163], # neutral 6.5 + [121, 121, 122], # neutral 5 + [82, 84, 86], # neutral 3.5 + [49, 49, 51] # black 2 + ]) + """ + convert reference colours from srgb to rgb + """ + m_srgb = degamma(m_rgb) # now in 16 bit color. + + # Produce array of LAB values for ideal color chart + m_lab = [colors.RGB_to_LAB(color / 256) for color in m_srgb] + + """ + reorder reference values to match how patches are ordered + """ + m_srgb = np.array([m_srgb[i::6] for i in range(6)]).reshape((24, 3)) + m_lab = np.array([m_lab[i::6] for i in range(6)]).reshape((24, 3)) + m_rgb = np.array([m_rgb[i::6] for i in range(6)]).reshape((24, 3)) + """ + reformat alsc correction tables or set colour_cals to None if alsc is + deactivated + """ + if cal_cr_list is None: + colour_cals = None + else: + colour_cals = {} + for cr, cb in zip(cal_cr_list, cal_cb_list): + cr_tab = cr['table'] + cb_tab = cb['table'] + """ + normalise tables so min value is 1 + """ + cr_tab = cr_tab / np.min(cr_tab) + cb_tab = cb_tab / np.min(cb_tab) + colour_cals[cr['ct']] = [cr_tab, cb_tab] + + """ + for each image, perform awb and alsc corrections. + Then calculate the colour correction matrix for that image, recording the + ccm and the colour tempertaure. + """ + ccm_tab = {} + for Img in imgs: + logger.info('Processing image: ' + Img.name) + """ + get macbeth patches with alsc applied if alsc enabled. + Note: if alsc is disabled then colour_cals will be set to None and no + the function will simply return the macbeth patches + """ + r, b, g = get_alsc_patches(Img, colour_cals, grey=False) + # 256 values for each patch of sRGB values + + """ + do awb + Note: awb is done by measuring the macbeth chart in the image, rather + than from the awb calibration. This is done so the awb will be perfect + and the ccm matrices will be more accurate. + """ + r_greys, b_greys, g_greys = r[3::4], b[3::4], g[3::4] + r_g = np.mean(r_greys / g_greys) + b_g = np.mean(b_greys / g_greys) + r = r / r_g + b = b / b_g + """ + normalise brightness wrt reference macbeth colours and then average + each channel for each patch + """ + gain = np.mean(m_srgb) / np.mean((r, g, b)) + logger.info(f'Gain with respect to standard colours: {gain:.3f}') + r = np.mean(gain * r, axis=1) + b = np.mean(gain * b, axis=1) + g = np.mean(gain * g, axis=1) + """ + calculate ccm matrix + """ + # ==== All of below should in sRGB ===## + sumde = 0 + ccm = do_ccm(r, g, b, m_srgb) + # This is the initial guess that our optimisation code works with. + original_ccm = ccm + r1 = ccm[0] + r2 = ccm[1] + g1 = ccm[3] + g2 = ccm[4] + b1 = ccm[6] + b2 = ccm[7] + ''' + COLOR MATRIX LOOKS AS BELOW + R1 R2 R3 Rval Outr + G1 G2 G3 * Gval = G + B1 B2 B3 Bval B + Will be optimising 6 elements and working out the third element using 1-r1-r2 = r3 + ''' + + x0 = [r1, r2, g1, g2, b1, b2] + ''' + We use our old CCM as the initial guess for the program to find the + optimised matrix + ''' + result = minimize(guess, x0, args=(r, g, b, m_lab), tol=0.01) + ''' + This produces a color matrix which has the lowest delta E possible, + based off the input data. Note it is impossible for this to reach + zero since the input data is imperfect + ''' + + [r1, r2, g1, g2, b1, b2] = result.x + # The new, optimised color correction matrix values + # This is the optimised Color Matrix (preserving greys by summing rows up to 1) + optimised_ccm = [r1, r2, (1 - r1 - r2), g1, g2, (1 - g1 - g2), b1, b2, (1 - b1 - b2)] + + logger.info(f'Optimized Matrix: {np.round(optimised_ccm, 4)}') + logger.info(f'Old Matrix: {np.round(ccm, 4)}') + + formatted_ccm = np.array(original_ccm).reshape((3, 3)) + + ''' + below is a whole load of code that then applies the latest color + matrix, and returns LAB values for color. This can then be used + to calculate the final delta E + ''' + optimised_ccm_rgb = [] # Original Color Corrected Matrix RGB / LAB + optimised_ccm_lab = [] + + formatted_optimised_ccm = np.array(optimised_ccm).reshape((3, 3)) + after_gamma_rgb = [] + after_gamma_lab = [] + + for RGB in zip(r, g, b): + ccm_applied_rgb = np.dot(formatted_ccm, (np.array(RGB) / 256)) + optimised_ccm_rgb.append(gamma(ccm_applied_rgb)) + optimised_ccm_lab.append(colors.RGB_to_LAB(ccm_applied_rgb)) + + optimised_ccm_applied_rgb = np.dot(formatted_optimised_ccm, np.array(RGB) / 256) + after_gamma_rgb.append(gamma(optimised_ccm_applied_rgb)) + after_gamma_lab.append(colors.RGB_to_LAB(optimised_ccm_applied_rgb)) + ''' + Gamma After RGB / LAB - not used in calculations, only used for visualisation + We now want to spit out some data that shows + how the optimisation has improved the color matrices + ''' + logger.info("Here are the Improvements") + + # CALCULATE WORST CASE delta e + old_worst_delta_e = 0 + before_average = transform_and_evaluate(formatted_ccm, r, g, b, m_lab) + new_worst_delta_e = 0 + after_average = transform_and_evaluate(formatted_optimised_ccm, r, g, b, m_lab) + for i in range(24): + old_delta_e = deltae(optimised_ccm_lab[i], m_lab[i]) # Current Old Delta E + new_delta_e = deltae(after_gamma_lab[i], m_lab[i]) # Current New Delta E + if old_delta_e > old_worst_delta_e: + old_worst_delta_e = old_delta_e + if new_delta_e > new_worst_delta_e: + new_worst_delta_e = new_delta_e + + logger.info(f'delta E optimized: average: {after_average:.2f} max:{new_worst_delta_e:.2f}') + logger.info(f'delta E old: average: {before_average:.2f} max:{old_worst_delta_e:.2f}') + + visualise_macbeth_chart(m_rgb, optimised_ccm_rgb, after_gamma_rgb, str(Img.color) + str(matrix_selection_types[typenum])) + ''' + The program will also save some visualisations of improvements. + Very pretty to look at. Top rectangle is ideal, Left square is + before optimisation, right square is after. + ''' + + """ + if a ccm has already been calculated for that temperature then don't + overwrite but save both. They will then be averaged later on + """ # Now going to use optimised color matrix, optimised_ccm + if Img.color in ccm_tab.keys(): + ccm_tab[Img.color].append(optimised_ccm) + else: + ccm_tab[Img.color] = [optimised_ccm] + + logger.info('Finished processing images') + """ + average any ccms that share a colour temperature + """ + for k, v in ccm_tab.items(): + tab = np.mean(v, axis=0) + tab = np.where((10000 * tab) % 1 <= 0.05, tab + 0.00001, tab) + tab = np.where((10000 * tab) % 1 >= 0.95, tab - 0.00001, tab) + ccm_tab[k] = list(np.round(tab, 5)) + logger.info(f'Matrix calculated for colour temperature of {k} K') + + """ + return all ccms with respective colour temperature in the correct format, + sorted by their colour temperature + """ + sorted_ccms = sorted(ccm_tab.items(), key=lambda kv: kv[0]) + ccms = [] + for i in sorted_ccms: + ccms.append({ + 'ct': i[0], + 'ccm': i[1] + }) + return ccms + + +def guess(x0, r, g, b, m_lab): # provides a method of numerical feedback for the optimisation code + [r1, r2, g1, g2, b1, b2] = x0 + ccm = np.array([r1, r2, (1 - r1 - r2), + g1, g2, (1 - g1 - g2), + b1, b2, (1 - b1 - b2)]).reshape((3, 3)) # format the matrix correctly + return transform_and_evaluate(ccm, r, g, b, m_lab) + + +def transform_and_evaluate(ccm, r, g, b, m_lab): # Transforms colors to LAB and applies the correction matrix + # create list of matrix changed colors + realrgb = [] + for RGB in zip(r, g, b): + rgb_post_ccm = np.dot(ccm, np.array(RGB) / 256) # This is RGB values after the color correction matrix has been applied + realrgb.append(colors.RGB_to_LAB(rgb_post_ccm)) + # now compare that with m_lab and return numeric result, averaged for each patch + return (sumde(realrgb, m_lab) / 24) # returns an average result of delta E + + +def sumde(listA, listB): + global typenum, test_patches + sumde = 0 + maxde = 0 + patchde = [] # Create array of the delta E values for each patch. useful for optimisation of certain patches + for listA_item, listB_item in zip(listA, listB): + if maxde < (deltae(listA_item, listB_item)): + maxde = deltae(listA_item, listB_item) + patchde.append(deltae(listA_item, listB_item)) + sumde += deltae(listA_item, listB_item) + ''' + The different options specified at the start allow for + the maximum to be returned, average or specific patches + ''' + if typenum == 0: + return sumde + if typenum == 1: + return maxde + if typenum == 2: + output = sum([patchde[test_patch] for test_patch in test_patches]) + # Selects only certain patches and returns the output for them + return output + + +""" +calculates the ccm for an individual image. +ccms are calculated in rgb space, and are fit by hand. Although it is a 3x3 +matrix, each row must add up to 1 in order to conserve greyness, simplifying +calculation. +The initial CCM is calculated in RGB, and then optimised in LAB color space +This simplifies the initial calculation but then gets us the accuracy of +using LAB color space. +""" + + +def do_ccm(r, g, b, m_srgb): + rb = r-b + gb = g-b + rb_2s = (rb * rb) + rb_gbs = (rb * gb) + gb_2s = (gb * gb) + + r_rbs = rb * (m_srgb[..., 0] - b) + r_gbs = gb * (m_srgb[..., 0] - b) + g_rbs = rb * (m_srgb[..., 1] - b) + g_gbs = gb * (m_srgb[..., 1] - b) + b_rbs = rb * (m_srgb[..., 2] - b) + b_gbs = gb * (m_srgb[..., 2] - b) + + """ + Obtain least squares fit + """ + rb_2 = np.sum(rb_2s) + gb_2 = np.sum(gb_2s) + rb_gb = np.sum(rb_gbs) + r_rb = np.sum(r_rbs) + r_gb = np.sum(r_gbs) + g_rb = np.sum(g_rbs) + g_gb = np.sum(g_gbs) + b_rb = np.sum(b_rbs) + b_gb = np.sum(b_gbs) + + det = rb_2 * gb_2 - rb_gb * rb_gb + + """ + Raise error if matrix is singular... + This shouldn't really happen with real data but if it does just take new + pictures and try again, not much else to be done unfortunately... + """ + if det < 0.001: + raise ArithmeticError + + r_a = (gb_2 * r_rb - rb_gb * r_gb) / det + r_b = (rb_2 * r_gb - rb_gb * r_rb) / det + """ + Last row can be calculated by knowing the sum must be 1 + """ + r_c = 1 - r_a - r_b + + g_a = (gb_2 * g_rb - rb_gb * g_gb) / det + g_b = (rb_2 * g_gb - rb_gb * g_rb) / det + g_c = 1 - g_a - g_b + + b_a = (gb_2 * b_rb - rb_gb * b_gb) / det + b_b = (rb_2 * b_gb - rb_gb * b_rb) / det + b_c = 1 - b_a - b_b + + """ + format ccm + """ + ccm = [r_a, r_b, r_c, g_a, g_b, g_c, b_a, b_b, b_c] + + return ccm + + +def deltae(colorA, colorB): + return ((colorA[0] - colorB[0]) ** 2 + (colorA[1] - colorB[1]) ** 2 + (colorA[2] - colorB[2]) ** 2) ** 0.5 + # return ((colorA[1]-colorB[1]) * * 2 + (colorA[2]-colorB[2]) * * 2) * * 0.5 + # UNCOMMENT IF YOU WANT TO NEGLECT LUMINANCE FROM CALCULATION OF DELTA E diff --git a/utils/tuning/libtuning/ctt_colors.py b/utils/tuning/libtuning/ctt_colors.py new file mode 100644 index 00000000..cb4d236b --- /dev/null +++ b/utils/tuning/libtuning/ctt_colors.py @@ -0,0 +1,30 @@ +# Program to convert from RGB to LAB color space +def RGB_to_LAB(RGB): # where RGB is a 1x3 array. e.g RGB = [100, 255, 230] + num = 0 + XYZ = [0, 0, 0] + # converted all the three R, G, B to X, Y, Z + X = RGB[0] * 0.4124 + RGB[1] * 0.3576 + RGB[2] * 0.1805 + Y = RGB[0] * 0.2126 + RGB[1] * 0.7152 + RGB[2] * 0.0722 + Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9505 + + XYZ[0] = X / 255 * 100 + XYZ[1] = Y / 255 * 100 # XYZ Must be in range 0 -> 100, so scale down from 255 + XYZ[2] = Z / 255 * 100 + XYZ[0] = XYZ[0] / 95.047 # ref_X = 95.047 Observer= 2°, Illuminant= D65 + XYZ[1] = XYZ[1] / 100.0 # ref_Y = 100.000 + XYZ[2] = XYZ[2] / 108.883 # ref_Z = 108.883 + num = 0 + for value in XYZ: + if value > 0.008856: + value = value ** (0.3333333333333333) + else: + value = (7.787 * value) + (16 / 116) + XYZ[num] = value + num = num + 1 + + # L, A, B, values calculated below + L = (116 * XYZ[1]) - 16 + a = 500 * (XYZ[0] - XYZ[1]) + b = 200 * (XYZ[1] - XYZ[2]) + + return [L, a, b] diff --git a/utils/tuning/libtuning/ctt_ransac.py b/utils/tuning/libtuning/ctt_ransac.py new file mode 100644 index 00000000..01bba302 --- /dev/null +++ b/utils/tuning/libtuning/ctt_ransac.py @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# +# camera tuning tool RANSAC selector for Macbeth chart locator + +import numpy as np + +scale = 2 + + +""" +constructs normalised macbeth chart corners for ransac algorithm +""" +def get_square_verts(c_err=0.05, scale=scale): + """ + define macbeth chart corners + """ + b_bord_x, b_bord_y = scale*8.5, scale*13 + s_bord = 6*scale + side = 41*scale + x_max = side*6 + 5*s_bord + 2*b_bord_x + y_max = side*4 + 3*s_bord + 2*b_bord_y + c1 = (0, 0) + c2 = (0, y_max) + c3 = (x_max, y_max) + c4 = (x_max, 0) + mac_norm = np.array((c1, c2, c3, c4), np.float32) + mac_norm = np.array([mac_norm]) + + square_verts = [] + square_0 = np.array(((0, 0), (0, side), + (side, side), (side, 0)), np.float32) + offset_0 = np.array((b_bord_x, b_bord_y), np.float32) + c_off = side * c_err + offset_cont = np.array(((c_off, c_off), (c_off, -c_off), + (-c_off, -c_off), (-c_off, c_off)), np.float32) + square_0 += offset_0 + square_0 += offset_cont + """ + define macbeth square corners + """ + for i in range(6): + shift_i = np.array(((i*side, 0), (i*side, 0), + (i*side, 0), (i*side, 0)), np.float32) + shift_bord = np.array(((i*s_bord, 0), (i*s_bord, 0), + (i*s_bord, 0), (i*s_bord, 0)), np.float32) + square_i = square_0 + shift_i + shift_bord + for j in range(4): + shift_j = np.array(((0, j*side), (0, j*side), + (0, j*side), (0, j*side)), np.float32) + shift_bord = np.array(((0, j*s_bord), + (0, j*s_bord), (0, j*s_bord), + (0, j*s_bord)), np.float32) + square_j = square_i + shift_j + shift_bord + square_verts.append(square_j) + # print('square_verts') + # print(square_verts) + return np.array(square_verts, np.float32), mac_norm + + +def get_square_centres(c_err=0.05, scale=scale): + """ + define macbeth square centres + """ + verts, mac_norm = get_square_verts(c_err, scale=scale) + + centres = np.mean(verts, axis=1) + # print('centres') + # print(centres) + return np.array(centres, np.float32) diff --git a/utils/tuning/libtuning/generators/generator.py b/utils/tuning/libtuning/generators/generator.py index 7c8c9b99..77a8ba4a 100644 --- a/utils/tuning/libtuning/generators/generator.py +++ b/utils/tuning/libtuning/generators/generator.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# generator.py - Base class for a generator to convert dict to tuning file +# Base class for a generator to convert dict to tuning file from pathlib import Path diff --git a/utils/tuning/libtuning/generators/raspberrypi_output.py b/utils/tuning/libtuning/generators/raspberrypi_output.py index 813491cd..47b49059 100644 --- a/utils/tuning/libtuning/generators/raspberrypi_output.py +++ b/utils/tuning/libtuning/generators/raspberrypi_output.py @@ -2,7 +2,7 @@ # # Copyright 2022 Raspberry Pi Ltd # -# raspberrypi_output.py - Generate tuning file in Raspberry Pi's json format +# Generate tuning file in Raspberry Pi's json format # # (Copied from ctt_pretty_print_json.py) diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py index effb4fb3..c490081d 100644 --- a/utils/tuning/libtuning/generators/yaml_output.py +++ b/utils/tuning/libtuning/generators/yaml_output.py @@ -2,15 +2,16 @@ # # Copyright 2022 Paul Elder <paul.elder@ideasonboard.com> # -# yaml_output.py - Generate tuning file in YAML format +# Generate tuning file in YAML format from .generator import Generator from numbers import Number from pathlib import Path -import libtuning.utils as utils +import logging +logger = logging.getLogger(__name__) class YamlOutput(Generator): def __init__(self): @@ -106,13 +107,16 @@ class YamlOutput(Generator): ] for module in output_order: + if module not in output_dict: + continue + out_lines.append(f' - {module.out_name}:') if len(output_dict[module]) == 0: continue if not isinstance(output_dict[module], dict): - utils.eprint(f'Error: Output of {module.type} is not a dictionary') + logger.error(f'Error: Output of {module.type} is not a dictionary') continue lines = self._stringify_dict(output_dict[module]) diff --git a/utils/tuning/libtuning/gradient.py b/utils/tuning/libtuning/gradient.py index 5106f821..b643f502 100644 --- a/utils/tuning/libtuning/gradient.py +++ b/utils/tuning/libtuning/gradient.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# gradient.py - Gradients that can be used to distribute or map numbers +# Gradients that can be used to distribute or map numbers import libtuning as lt diff --git a/utils/tuning/libtuning/image.py b/utils/tuning/libtuning/image.py index aa9d20b5..ecd334bd 100644 --- a/utils/tuning/libtuning/image.py +++ b/utils/tuning/libtuning/image.py @@ -2,7 +2,7 @@ # # Copyright (C) 2019, Raspberry Pi Ltd # -# image.py - Container for an image and associated metadata +# Container for an image and associated metadata import binascii import numpy as np @@ -13,6 +13,9 @@ import re import libtuning as lt import libtuning.utils as utils +import logging + +logger = logging.getLogger(__name__) class Image: @@ -21,17 +24,18 @@ class Image: self.lsc_only = False self.color = -1 self.lux = -1 + self.macbeth = None try: self._load_metadata_exif() except Exception as e: - utils.eprint(f'Failed to load metadata from {self.path}: {e}') + logger.error(f'Failed to load metadata from {self.path}: {e}') raise e try: self._read_image_dng() except Exception as e: - utils.eprint(f'Failed to load image data from {self.path}: {e}') + logger.error(f'Failed to load image data from {self.path}: {e}') raise e @property @@ -79,7 +83,7 @@ class Image: # is R, then G, then G, then B. bayer_case = { '0 1 1 2': (lt.Color.R, lt.Color.GR, lt.Color.GB, lt.Color.B), - '1 2 0 1': (lt.Color.GB, lt.Color.R, lt.Color.B, lt.Color.GR), + '1 2 0 1': (lt.Color.GB, lt.Color.B, lt.Color.R, lt.Color.GR), '2 1 1 0': (lt.Color.B, lt.Color.GB, lt.Color.GR, lt.Color.R), '1 0 2 1': (lt.Color.GR, lt.Color.R, lt.Color.B, lt.Color.GB) } @@ -131,6 +135,6 @@ class Image: all_patches.append(ch_patches) - self.patches = all_patches + self.patches = np.array(all_patches) return not saturated diff --git a/utils/tuning/libtuning/libtuning.py b/utils/tuning/libtuning/libtuning.py index d84c148f..bac57323 100644 --- a/utils/tuning/libtuning/libtuning.py +++ b/utils/tuning/libtuning/libtuning.py @@ -2,16 +2,17 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# libtuning.py - An infrastructure for camera tuning tools +# An infrastructure for camera tuning tools import argparse +import logging import libtuning as lt import libtuning.utils as utils -from libtuning.utils import eprint from enum import Enum, IntEnum +logger = logging.getLogger(__name__) class Color(IntEnum): R = 0 @@ -94,7 +95,10 @@ class Tuner(object): self.output = {} def add(self, module): - self.modules.append(module) + if isinstance(module, list): + self.modules.extend(module) + else: + self.modules.append(module) def set_input_parser(self, parser): self.parser = parser @@ -112,10 +116,10 @@ class Tuner(object): for module_type in output_order: modules = [module for module in self.modules if module.type == module_type.type] if len(modules) > 1: - eprint(f'Multiple modules found for module type "{module_type.type}"') + logger.error(f'Multiple modules found for module type "{module_type.type}"') return False if len(modules) < 1: - eprint(f'No module found for module type "{module_type.type}"') + logger.error(f'No module found for module type "{module_type.type}"') return False self.output_order.append(modules[0]) @@ -124,19 +128,19 @@ class Tuner(object): # \todo Validate parser and generator at Tuner construction time? def _validate_settings(self): if self.parser is None: - eprint('Missing parser') + logger.error('Missing parser') return False if self.generator is None: - eprint('Missing generator') + logger.error('Missing generator') return False if len(self.modules) == 0: - eprint('No modules added') + logger.error('No modules added') return False if len(self.output_order) != len(self.modules): - eprint('Number of outputs does not match number of modules') + logger.error('Number of outputs does not match number of modules') return False return True @@ -183,7 +187,7 @@ class Tuner(object): for module in self.modules: if not module.validate_config(self.config): - eprint(f'Config is invalid for module {module.type}') + logger.error(f'Config is invalid for module {module.type}') return -1 has_lsc = any(isinstance(m, lt.modules.lsc.LSC) for m in self.modules) @@ -192,15 +196,15 @@ class Tuner(object): images = utils.load_images(args.input, self.config, not has_only_lsc, has_lsc) if images is None or len(images) == 0: - eprint(f'No images were found, or able to load') + logger.error(f'No images were found, or able to load') return -1 # Do the tuning for module in self.modules: out = module.process(self.config, images, self.output) if out is None: - eprint(f'Module {module.name} failed to process, aborting') - break + logger.warning(f'Module {module.hr_name} failed to process...') + continue self.output[module] = out self.generator.write(args.output, self.output, self.output_order) diff --git a/utils/tuning/libtuning/macbeth.py b/utils/tuning/libtuning/macbeth.py index 5faddf66..4a2006b0 100644 --- a/utils/tuning/libtuning/macbeth.py +++ b/utils/tuning/libtuning/macbeth.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: BSD-2-Clause # # Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2024, Ideas on Board Oy # -# macbeth.py - Locate and extract Macbeth charts from images +# Locate and extract Macbeth charts from images # (Copied from: ctt_macbeth_locator.py) # \todo Add debugging @@ -11,8 +12,18 @@ import cv2 import os from pathlib import Path import numpy as np +import warnings +import logging +from sklearn import cluster as cluster -from libtuning.image import Image +from .ctt_ransac import get_square_verts, get_square_centres +from .image import Image + +logger = logging.getLogger(__name__) + + +class MacbethError(Exception): + pass # Reshape image to fixed width without distorting returns image and scale @@ -369,7 +380,9 @@ def get_macbeth_chart(img, ref_data): # Catch macbeth errors and continue with code except MacbethError as error: - eprint(error) + # \todo: This happens so many times in a normal run, that it shadows + # all the relevant output + # logger.warning(error) return (0, None, None, False) @@ -403,10 +416,15 @@ def find_macbeth(img, mac_config): # nothing more is tried as this is a high enough confidence to ensure # reliable macbeth square centre placement. + # Keep a list that will include this and any brightened up versions of + # the image for reuse. + all_images = [img] + for brightness in [2, 4]: if cor >= 0.75: break img_br = cv2.convertScaleAbs(img, alpha=brightness, beta=0) + all_images.append(img_br) cor_b, mac_b, coords_b, ret_b = get_macbeth_chart(img_br, ref_data) if cor_b > cor: cor, mac, coords, ret = cor_b, mac_b, coords_b, ret_b @@ -456,23 +474,24 @@ def find_macbeth(img, mac_config): w_inc = int(w * pair['inc']) h_inc = int(h * pair['inc']) - loop = ((1 - pair['sel']) / pair['inc']) + 1 + loop = int(((1 - pair['sel']) / pair['inc']) + 1) # For each subselection, look for a macbeth chart - for i in range(loop): - for j in range(loop): - w_s, h_s = i * w_inc, j * h_inc - img_sel = img[w_s:w_s + w_sel, h_s:h_s + h_sel] - cor_ij, mac_ij, coords_ij, ret_ij = get_macbeth_chart(img_sel, ref_data) - - # If the correlation is better than the best then record the - # scale and current subselection at which macbeth chart was - # found. Also record the coordinates, macbeth chart and message. - if cor_ij > cor: - cor = cor_ij - mac, coords, ret = mac_ij, coords_ij, ret_ij - ii, jj = i, j - w_best, h_best = w_inc, h_inc - d_best = index + 1 + for img_br in all_images: + for i in range(loop): + for j in range(loop): + w_s, h_s = i * w_inc, j * h_inc + img_sel = img_br[w_s:w_s + w_sel, h_s:h_s + h_sel] + cor_ij, mac_ij, coords_ij, ret_ij = get_macbeth_chart(img_sel, ref_data) + + # If the correlation is better than the best then record the + # scale and current subselection at which macbeth chart was + # found. Also record the coordinates, macbeth chart and message. + if cor_ij > cor: + cor = cor_ij + mac, coords, ret = mac_ij, coords_ij, ret_ij + ii, jj = i, j + w_best, h_best = w_inc, h_inc + d_best = index + 1 # Transform coordinates from subselection to original image if ii != -1: @@ -486,7 +505,7 @@ def find_macbeth(img, mac_config): coords_fit = coords if cor < 0.75: - eprint(f'Warning: Low confidence {cor:.3f} for macbeth chart in {img.path.name}') + logger.warning(f'Low confidence {cor:.3f} for macbeth chart') if show: draw_macbeth_results(img, coords_fit) @@ -499,18 +518,20 @@ def locate_macbeth(image: Image, config: dict): av_chan = (np.mean(np.array(image.channels), axis=0) / (2**16)) av_val = np.mean(av_chan) if av_val < image.blacklevel_16 / (2**16) + 1 / 64: - eprint(f'Image {image.path.name} too dark') + logger.warning(f'Image {image.path.name} too dark') return None macbeth = find_macbeth(av_chan, config['general']['macbeth']) if macbeth is None: - eprint(f'No macbeth chart found in {image.path.name}') + logger.warning(f'No macbeth chart found in {image.path.name}') return None mac_cen_coords = macbeth[1] if not image.get_patches(mac_cen_coords): - eprint(f'Macbeth patches have saturated in {image.path.name}') + logger.warning(f'Macbeth patches have saturated in {image.path.name}') return None + image.macbeth = macbeth + return macbeth diff --git a/utils/tuning/libtuning/macbeth_ref.pgm b/utils/tuning/libtuning/macbeth_ref.pgm index 37897140..089ea91f 100644 --- a/utils/tuning/libtuning/macbeth_ref.pgm +++ b/utils/tuning/libtuning/macbeth_ref.pgm @@ -1,5 +1,5 @@ -# SPDX-License-Identifier: BSD-2-Clause P5 +# SPDX-License-Identifier: BSD-2-Clause # Reference macbeth chart 120 80 255 diff --git a/utils/tuning/libtuning/modules/agc/__init__.py b/utils/tuning/libtuning/modules/agc/__init__.py new file mode 100644 index 00000000..4db9ca37 --- /dev/null +++ b/utils/tuning/libtuning/modules/agc/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + +from libtuning.modules.agc.agc import AGC +from libtuning.modules.agc.rkisp1 import AGCRkISP1 diff --git a/utils/tuning/libtuning/modules/agc/agc.py b/utils/tuning/libtuning/modules/agc/agc.py new file mode 100644 index 00000000..9c8899ba --- /dev/null +++ b/utils/tuning/libtuning/modules/agc/agc.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + +from ..module import Module + +import libtuning as lt + + +class AGC(Module): + type = 'agc' + hr_name = 'AGC (Base)' + out_name = 'GenericAGC' + + # \todo Add sector shapes and stuff just like lsc + def __init__(self, *, + debug: list): + super().__init__() + + self.debug = debug diff --git a/utils/tuning/libtuning/modules/agc/rkisp1.py b/utils/tuning/libtuning/modules/agc/rkisp1.py new file mode 100644 index 00000000..2dad3a09 --- /dev/null +++ b/utils/tuning/libtuning/modules/agc/rkisp1.py @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> +# +# rkisp1.py - AGC module for tuning rkisp1 + +from .agc import AGC + +import libtuning as lt + + +class AGCRkISP1(AGC): + hr_name = 'AGC (RkISP1)' + out_name = 'Agc' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # We don't actually need anything from the config file + def validate_config(self, config: dict) -> bool: + return True + + def _generate_metering_modes(self) -> dict: + centre_weighted = [ + 0, 0, 0, 0, 0, + 0, 6, 8, 6, 0, + 0, 8, 16, 8, 0, + 0, 6, 8, 6, 0, + 0, 0, 0, 0, 0 + ] + + spot = [ + 0, 0, 0, 0, 0, + 0, 2, 4, 2, 0, + 0, 4, 16, 4, 0, + 0, 2, 4, 2, 0, + 0, 0, 0, 0, 0 + ] + + matrix = [1 for i in range(0, 25)] + + return { + 'MeteringCentreWeighted': centre_weighted, + 'MeteringSpot': spot, + 'MeteringMatrix': matrix + } + + def _generate_exposure_modes(self) -> dict: + normal = {'exposureTime': [100, 10000, 30000, 60000, 120000], + 'gain': [2.0, 4.0, 6.0, 6.0, 6.0]} + short = {'exposureTime': [100, 5000, 10000, 20000, 120000], + 'gain': [2.0, 4.0, 6.0, 6.0, 6.0]} + + return {'ExposureNormal': normal, 'ExposureShort': short} + + def _generate_constraint_modes(self) -> dict: + normal = {'lower': {'qLo': 0.98, 'qHi': 1.0, 'yTarget': 0.5}} + highlight = { + 'lower': {'qLo': 0.98, 'qHi': 1.0, 'yTarget': 0.5}, + 'upper': {'qLo': 0.98, 'qHi': 1.0, 'yTarget': 0.8} + } + + return {'ConstraintNormal': normal, 'ConstraintHighlight': highlight} + + def _generate_y_target(self) -> list: + return 0.5 + + def process(self, config: dict, images: list, outputs: dict) -> dict: + output = {} + + output['AeMeteringMode'] = self._generate_metering_modes() + output['AeExposureMode'] = self._generate_exposure_modes() + output['AeConstraintMode'] = self._generate_constraint_modes() + output['relativeLuminanceTarget'] = self._generate_y_target() + + # \todo Debug functionality + + return output diff --git a/utils/tuning/libtuning/modules/awb/__init__.py b/utils/tuning/libtuning/modules/awb/__init__.py new file mode 100644 index 00000000..2d67f10c --- /dev/null +++ b/utils/tuning/libtuning/modules/awb/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas On Board + +from libtuning.modules.awb.awb import AWB +from libtuning.modules.awb.rkisp1 import AWBRkISP1 diff --git a/utils/tuning/libtuning/modules/awb/awb.py b/utils/tuning/libtuning/modules/awb/awb.py new file mode 100644 index 00000000..0dc4f59d --- /dev/null +++ b/utils/tuning/libtuning/modules/awb/awb.py @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas On Board + +import logging + +from ..module import Module + +from libtuning.ctt_awb import awb +import numpy as np + +logger = logging.getLogger(__name__) + + +class AWB(Module): + type = 'awb' + hr_name = 'AWB (Base)' + out_name = 'GenericAWB' + + def __init__(self, *, debug: list): + super().__init__() + + self.debug = debug + + def do_calculation(self, images): + logger.info('Starting AWB calculation') + + imgs = [img for img in images if img.macbeth is not None] + + ct_curve, transverse_pos, transverse_neg = awb(imgs, None, None, False) + ct_curve = np.reshape(ct_curve, (-1, 3)) + gains = [{ + 'ct': int(v[0]), + 'gains': [float(1.0 / v[1]), float(1.0 / v[2])] + } for v in ct_curve] + + return {'colourGains': gains, + 'transversePos': transverse_pos, + 'transverseNeg': transverse_neg} + diff --git a/utils/tuning/libtuning/modules/awb/rkisp1.py b/utils/tuning/libtuning/modules/awb/rkisp1.py new file mode 100644 index 00000000..d562d26e --- /dev/null +++ b/utils/tuning/libtuning/modules/awb/rkisp1.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas On Board +# +# AWB module for tuning rkisp1 + +from .awb import AWB + +class AWBRkISP1(AWB): + hr_name = 'AWB (RkISP1)' + out_name = 'Awb' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def validate_config(self, config: dict) -> bool: + return True + + def process(self, config: dict, images: list, outputs: dict) -> dict: + if not 'awb' in config['general']: + raise ValueError('AWB configuration missing') + awb_config = config['general']['awb'] + algorithm = awb_config['algorithm'] + + output = {'algorithm': algorithm} + data = self.do_calculation(images) + if algorithm == 'grey': + output['colourGains'] = data['colourGains'] + elif algorithm == 'bayes': + output['AwbMode'] = awb_config['AwbMode'] + output['priors'] = awb_config['priors'] + output.update(data) + else: + raise ValueError(f"Unknown AWB algorithm {output['algorithm']}") + + return output diff --git a/utils/tuning/libtuning/modules/ccm/__init__.py b/utils/tuning/libtuning/modules/ccm/__init__.py new file mode 100644 index 00000000..322602af --- /dev/null +++ b/utils/tuning/libtuning/modules/ccm/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> + +from libtuning.modules.ccm.ccm import CCM +from libtuning.modules.ccm.rkisp1 import CCMRkISP1 diff --git a/utils/tuning/libtuning/modules/ccm/ccm.py b/utils/tuning/libtuning/modules/ccm/ccm.py new file mode 100644 index 00000000..18702f8d --- /dev/null +++ b/utils/tuning/libtuning/modules/ccm/ccm.py @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> +# Copyright (C) 2024, Ideas on Board +# +# Base Ccm tuning module + +from ..module import Module + +from libtuning.ctt_ccm import ccm +import logging + +logger = logging.getLogger(__name__) + + +class CCM(Module): + type = 'ccm' + hr_name = 'CCM (Base)' + out_name = 'GenericCCM' + + def __init__(self, debug: list): + super().__init__() + + self.debug = debug + + def do_calibration(self, images): + logger.info('Starting CCM calibration') + + imgs = [img for img in images if img.macbeth is not None] + + # todo: Take LSC calibration results into account. + cal_cr_list = None + cal_cb_list = None + + try: + ccms = ccm(imgs, cal_cr_list, cal_cb_list) + except ArithmeticError: + logger.error('CCM calibration failed') + return None + + return ccms diff --git a/utils/tuning/libtuning/modules/ccm/rkisp1.py b/utils/tuning/libtuning/modules/ccm/rkisp1.py new file mode 100644 index 00000000..be0252d9 --- /dev/null +++ b/utils/tuning/libtuning/modules/ccm/rkisp1.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com> +# Copyright (C) 2024, Ideas on Board +# +# Ccm module for tuning rkisp1 + +from .ccm import CCM + + +class CCMRkISP1(CCM): + hr_name = 'Crosstalk Correction (RkISP1)' + out_name = 'Ccm' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # We don't need anything from the config file. + def validate_config(self, config: dict) -> bool: + return True + + def process(self, config: dict, images: list, outputs: dict) -> dict: + output = {} + + ccms = self.do_calibration(images) + output['ccms'] = ccms + + return output diff --git a/utils/tuning/libtuning/modules/lsc/lsc.py b/utils/tuning/libtuning/modules/lsc/lsc.py index 344a07a3..e0ca22eb 100644 --- a/utils/tuning/libtuning/modules/lsc/lsc.py +++ b/utils/tuning/libtuning/modules/lsc/lsc.py @@ -59,7 +59,10 @@ class LSC(Module): def _lsc_single_channel(self, channel: np.array, image: lt.Image, green_grid: np.array = None): grid = self._get_grid(channel, image.w, image.h) - grid -= image.blacklevel_16 + # Clamp the values to a small positive, so that the following 1/grid + # doesn't produce negative results. + grid = np.maximum(grid - image.blacklevel_16, 0.1) + if green_grid is None: table = np.reshape(1 / grid, self.sector_shape[::-1]) else: diff --git a/utils/tuning/libtuning/modules/lsc/raspberrypi.py b/utils/tuning/libtuning/modules/lsc/raspberrypi.py index 58f5000d..99bc4fe6 100644 --- a/utils/tuning/libtuning/modules/lsc/raspberrypi.py +++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py @@ -3,7 +3,7 @@ # Copyright (C) 2019, Raspberry Pi Ltd # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# raspberrypi.py - ALSC module for tuning Raspberry Pi +# ALSC module for tuning Raspberry Pi from .lsc import LSC @@ -12,7 +12,9 @@ import libtuning.utils as utils from numbers import Number import numpy as np +import logging +logger = logging.getLogger(__name__) class ALSCRaspberryPi(LSC): # Override the type name so that the parser can match the entry in the @@ -35,7 +37,7 @@ class ALSCRaspberryPi(LSC): def validate_config(self, config: dict) -> bool: if self not in config: - utils.eprint(f'{self.type} not in config') + logger.error(f'{self.type} not in config') return False valid = True @@ -46,14 +48,14 @@ class ALSCRaspberryPi(LSC): color_key = self.do_color.name if lum_key not in conf and self.luminance_strength.required: - utils.eprint(f'{lum_key} is not in config') + logger.error(f'{lum_key} is not in config') valid = False if lum_key in conf and (conf[lum_key] < 0 or conf[lum_key] > 1): - utils.eprint(f'Warning: {lum_key} is not in range [0, 1]; defaulting to 0.5') + logger.warning(f'{lum_key} is not in range [0, 1]; defaulting to 0.5') if color_key not in conf and self.do_color.required: - utils.eprint(f'{color_key} is not in config') + logger.error(f'{color_key} is not in config') valid = False return valid @@ -235,7 +237,7 @@ class ALSCRaspberryPi(LSC): if count == 1: output['sigma'] = 0.005 output['sigma_Cb'] = 0.005 - utils.eprint('Warning: Only one alsc calibration found; standard sigmas used for adaptive algorithm.') + logger.warning('Only one alsc calibration found; standard sigmas used for adaptive algorithm.') return output # Obtain worst-case scenario residual sigmas diff --git a/utils/tuning/libtuning/modules/lsc/rkisp1.py b/utils/tuning/libtuning/modules/lsc/rkisp1.py index 5701ae0a..c02b2306 100644 --- a/utils/tuning/libtuning/modules/lsc/rkisp1.py +++ b/utils/tuning/libtuning/modules/lsc/rkisp1.py @@ -3,7 +3,7 @@ # Copyright (C) 2019, Raspberry Pi Ltd # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# rkisp1.py - LSC module for tuning rkisp1 +# LSC module for tuning rkisp1 from .lsc import LSC @@ -33,13 +33,13 @@ class LSCRkISP1(LSC): # table, flattened array of (blue's) green calibration table def _do_single_lsc(self, image: lt.Image): - cgr, gr = self._lsc_single_channel(image.channels[lt.Color.GR], image) - cgb, gb = self._lsc_single_channel(image.channels[lt.Color.GB], image) - - # \todo Should these ratio against the average of both greens or just - # each green like we've done here? - cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, gr) - cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, gb) + # Perform LSC on each colour channel independently. A future enhancement + # worth investigating would be splitting the luminance and chrominance + # LSC as done by Raspberry Pi. + cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR], image) + cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB], image) + cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image) + cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image) return image.color, cr.flatten(), cb.flatten(), cgr.flatten(), cgb.flatten() @@ -80,7 +80,8 @@ class LSCRkISP1(LSC): tables = [] for lis in [list_cr, list_cgr, list_cgb, list_cb]: table = np.mean(lis[indices], axis=0) - table = output_map_func((1, 3.999), (1024, 4095), table) + table = output_map_func((1, 4), (1024, 4096), table) + table = np.clip(table, 1024, 4095) table = np.round(table).astype('int32').tolist() tables.append(table) @@ -106,6 +107,9 @@ class LSCRkISP1(LSC): output['sets'] = self._do_all_lsc(images) + if len(output['sets']) == 0: + return None + # \todo Validate images from greyscale camera and force grescale mode # \todo Debug functionality diff --git a/utils/tuning/libtuning/modules/lux/__init__.py b/utils/tuning/libtuning/modules/lux/__init__.py new file mode 100644 index 00000000..af9d4e08 --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2025, Ideas on Board + +from libtuning.modules.lux.lux import Lux +from libtuning.modules.lux.rkisp1 import LuxRkISP1 diff --git a/utils/tuning/libtuning/modules/lux/lux.py b/utils/tuning/libtuning/modules/lux/lux.py new file mode 100644 index 00000000..4bad429a --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/lux.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2025, Ideas on Board +# +# Base Lux tuning module + +from ..module import Module + +import logging +import numpy as np + +logger = logging.getLogger(__name__) + + +class Lux(Module): + type = 'lux' + hr_name = 'Lux (Base)' + out_name = 'GenericLux' + + def __init__(self, debug: list): + super().__init__() + + self.debug = debug + + def calculate_lux_reference_values(self, images): + # The lux calibration is done on a single image. For best effects, the + # image with lux level closest to 1000 is chosen. + imgs = [img for img in images if img.macbeth is not None] + lux_values = [img.lux for img in imgs] + index = lux_values.index(min(lux_values, key=lambda l: abs(1000 - l))) + img = imgs[index] + logger.info(f'Selected image {img.name} for lux calibration') + + if img.lux < 50: + logger.warning(f'A Lux level of {img.lux} is very low for proper lux calibration') + + ref_y = self.calculate_y(img) + exposure_time = img.exposure + gain = img.againQ8_norm + aperture = 1 + logger.info(f'RefY:{ref_y} Exposure time:{exposure_time}µs Gain:{gain} Aperture:{aperture}') + return {'referenceY': ref_y, + 'referenceExposureTime': exposure_time, + 'referenceAnalogueGain': gain, + 'referenceDigitalGain': 1.0, + 'referenceLux': img.lux} + + def calculate_y(self, img): + max16Bit = 0xffff + # Average over all grey patches. + ap_r = np.mean(img.patches[0][3::4]) / max16Bit + ap_g = (np.mean(img.patches[1][3::4]) + np.mean(img.patches[2][3::4])) / 2 / max16Bit + ap_b = np.mean(img.patches[3][3::4]) / max16Bit + logger.debug(f'Averaged grey patches: Red: {ap_r}, Green: {ap_g}, Blue: {ap_b}') + + # Calculate white balance gains. + gr = ap_g / ap_r + gb = ap_g / ap_b + logger.debug(f'WB gains: Red: {gr} Blue: {gb}') + + # Calculate the mean Y value of the whole image + a_r = np.mean(img.channels[0]) * gr + a_g = (np.mean(img.channels[1]) + np.mean(img.channels[2])) / 2 + a_b = np.mean(img.channels[3]) * gb + y = 0.299 * a_r + 0.587 * a_g + 0.114 * a_b + y /= max16Bit + + return y + diff --git a/utils/tuning/libtuning/modules/lux/rkisp1.py b/utils/tuning/libtuning/modules/lux/rkisp1.py new file mode 100644 index 00000000..62d3f94c --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/rkisp1.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas on Board +# +# Lux module for tuning rkisp1 + +from .lux import Lux + + +class LuxRkISP1(Lux): + hr_name = 'Lux (RkISP1)' + out_name = 'Lux' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # We don't need anything from the config file. + def validate_config(self, config: dict) -> bool: + return True + + def process(self, config: dict, images: list, outputs: dict) -> dict: + return self.calculate_lux_reference_values(images) diff --git a/utils/tuning/libtuning/modules/module.py b/utils/tuning/libtuning/modules/module.py index 12e2fc7c..de624384 100644 --- a/utils/tuning/libtuning/modules/module.py +++ b/utils/tuning/libtuning/modules/module.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# module.py - Base class for algorithm-specific tuning modules +# Base class for algorithm-specific tuning modules # @var type Type of the module. Defined in the base module. diff --git a/utils/tuning/libtuning/modules/static.py b/utils/tuning/libtuning/modules/static.py new file mode 100644 index 00000000..4d0f7e18 --- /dev/null +++ b/utils/tuning/libtuning/modules/static.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas on Board +# +# Module implementation for static data + +from .module import Module + + +# This module can be used in cases where the tuning file should contain +# static data. +class StaticModule(Module): + def __init__(self, out_name: str, output: dict = {}): + super().__init__() + self.out_name = out_name + self.hr_name = f'Static {out_name}' + self.type = f'static_{out_name}' + self.output = output + + def validate_config(self, config: dict) -> bool: + return True + + def process(self, config: dict, images: list, outputs: dict) -> dict: + return self.output diff --git a/utils/tuning/libtuning/parsers/parser.py b/utils/tuning/libtuning/parsers/parser.py index a17d8d71..0c3944c7 100644 --- a/utils/tuning/libtuning/parsers/parser.py +++ b/utils/tuning/libtuning/parsers/parser.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# parser.py - Base class for a parser for a specific format of config file +# Base class for a parser for a specific format of config file class Parser(object): def __init__(self): diff --git a/utils/tuning/libtuning/parsers/raspberrypi_parser.py b/utils/tuning/libtuning/parsers/raspberrypi_parser.py index d26586ba..f1da4592 100644 --- a/utils/tuning/libtuning/parsers/raspberrypi_parser.py +++ b/utils/tuning/libtuning/parsers/raspberrypi_parser.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# raspberrypi_parser.py - Parser for Raspberry Pi config file format +# Parser for Raspberry Pi config file format from .parser import Parser diff --git a/utils/tuning/libtuning/parsers/yaml_parser.py b/utils/tuning/libtuning/parsers/yaml_parser.py index 5c1673a5..1fa6b7a8 100644 --- a/utils/tuning/libtuning/parsers/yaml_parser.py +++ b/utils/tuning/libtuning/parsers/yaml_parser.py @@ -2,16 +2,19 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# yaml_parser.py - Parser for YAML format config file +# Parser for YAML format config file from .parser import Parser +import yaml class YamlParser(Parser): def __init__(self): super().__init__() - # \todo Implement this (it's fine for now as we don't need a config for - # rkisp1 LSC, which is the only user of this so far) def parse(self, config_file: str, modules: list) -> (dict, list): - return {}, [] + # Dummy implementation that just reads the file + with open(config_file, 'r') as f: + config = yaml.safe_load(f) + + return config, [] diff --git a/utils/tuning/libtuning/smoothing.py b/utils/tuning/libtuning/smoothing.py index b8a5a242..de4d920c 100644 --- a/utils/tuning/libtuning/smoothing.py +++ b/utils/tuning/libtuning/smoothing.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# smoothing.py - Wrapper for cv2 smoothing functions to enable duck-typing +# Wrapper for cv2 smoothing functions to enable duck-typing import cv2 diff --git a/utils/tuning/libtuning/utils.py b/utils/tuning/libtuning/utils.py index b60f2c9b..e35cf409 100644 --- a/utils/tuning/libtuning/utils.py +++ b/utils/tuning/libtuning/utils.py @@ -3,8 +3,9 @@ # Copyright (C) 2019, Raspberry Pi Ltd # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# utils.py - Utilities for libtuning +# Utilities for libtuning +import cv2 import decimal import math import numpy as np @@ -12,16 +13,15 @@ import os from pathlib import Path import re import sys +import logging import libtuning as lt from libtuning.image import Image -from libtuning.macbeth import locate_macbeth - -# Utility functions +from .macbeth import locate_macbeth +logger = logging.getLogger(__name__) -def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) +# Utility functions def get_module_by_type_name(modules, name): @@ -43,16 +43,30 @@ def _list_image_files(directory): def _parse_image_filename(fn: Path): - result = re.search(r'^(alsc_)?(\d+)[kK]_(\d+)?[lLuU]?.\w{3,4}$', fn.name) - if result is None: - eprint(f'The file name of {fn.name} is incorrectly formatted') - return None, None, None + lsc_only = False + color_temperature = None + lux = None + + parts = fn.stem.split('_') + for part in parts: + if part == 'alsc': + lsc_only = True + continue + r = re.match(r'(\d+)[kK]', part) + if r: + color_temperature = int(r.group(1)) + continue + r = re.match(r'(\d+)[lLuU]', part) + if r: + lux = int(r.group(1)) + + if color_temperature is None: + logger.error(f'The file name of "{fn.name}" does not contain a color temperature') - color = int(result.group(2)) - lsc_only = result.group(1) is not None - lux = None if lsc_only else int(result.group(3)) + if lux is None and lsc_only is False: + logger.error(f'The file name of "{fn.name}" must either contain alsc or a lux level') - return color, lux, lsc_only + return color_temperature, lux, lsc_only # \todo Implement this from check_imgs() in ctt.py @@ -72,30 +86,34 @@ def _validate_images(images): def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) -> list: files = _list_image_files(input_dir) if len(files) == 0: - eprint(f'No images found in {input_dir}') + logger.error(f'No images found in {input_dir}') return None images = [] for f in files: color, lux, lsc_only = _parse_image_filename(f) + if color is None: + logger.warning(f'Ignoring "{f.name}" as it has no associated color temperature') continue + logger.info(f'Process image "{f.name}" (color={color}, lux={lux}, lsc_only={lsc_only})') + # Skip lsc image if we don't need it if lsc_only and not load_lsc: - eprint(f'Skipping {f.name} as this tuner has no LSC module') + logger.warning(f'Skipping {f.name} as this tuner has no LSC module') continue # Skip non-lsc image if we don't need it if not lsc_only and not load_nonlsc: - eprint(f'Skipping {f.name} as this tuner only has an LSC module') + logger.warning(f'Skipping {f.name} as this tuner only has an LSC module') continue # Load image try: image = Image(f) except Exception as e: - eprint(f'Failed to load image {f.name}: {e}') + logger.error(f'Failed to load image {f.name}: {e}') continue # Populate simple fields @@ -113,7 +131,7 @@ def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) continue # Handle macbeth - macbeth = locate_macbeth(config) + macbeth = locate_macbeth(image, config) if macbeth is None: continue @@ -123,3 +141,46 @@ def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) return None return images + + + +""" +Some code that will save virtual macbeth charts that show the difference between optimised matrices and non optimised matrices + +The function creates an image that is 1550 by 1050 pixels wide, and fills it with patches which are 200x200 pixels in size +Each patch contains the ideal color, the color from the original matrix, and the color from the final matrix +_________________ +| | +| Ideal Color | +|_______________| +| Old | new | +| Color | Color | +|_______|_______| + +Nice way of showing how the optimisation helps change the colors and the color matricies +""" +def visualise_macbeth_chart(macbeth_rgb, original_rgb, new_rgb, output_filename): + image = np.zeros((1050, 1550, 3), dtype=np.uint8) + colorindex = -1 + for y in range(6): + for x in range(4): # Creates 6 x 4 grid of macbeth chart + colorindex += 1 + xlocation = 50 + 250 * x # Means there is 50px of black gap between each square, more like the real macbeth chart. + ylocation = 50 + 250 * y + for g in range(200): + for i in range(100): + image[xlocation + i, ylocation + g] = macbeth_rgb[colorindex] + xlocation = 150 + 250 * x + ylocation = 50 + 250 * y + for i in range(100): + for g in range(100): + image[xlocation + i, ylocation + g] = original_rgb[colorindex] # Smaller squares below to compare the old colors with the new ones + xlocation = 150 + 250 * x + ylocation = 150 + 250 * y + for i in range(100): + for g in range(100): + image[xlocation + i, ylocation + g] = new_rgb[colorindex] + + im_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + cv2.imwrite(f'{output_filename} Generated Macbeth Chart.png', im_bgr) + diff --git a/utils/tuning/raspberrypi/alsc.py b/utils/tuning/raspberrypi/alsc.py index 024eb5a3..ba8fc9e1 100644 --- a/utils/tuning/raspberrypi/alsc.py +++ b/utils/tuning/raspberrypi/alsc.py @@ -2,7 +2,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# alsc.py - ALSC module instance for Raspberry Pi tuning scripts +# ALSC module instance for Raspberry Pi tuning scripts import libtuning as lt from libtuning.modules.lsc import ALSCRaspberryPi diff --git a/utils/tuning/raspberrypi_alsc_only.py b/utils/tuning/raspberrypi_alsc_only.py index af04e6a8..777d8007 100755 --- a/utils/tuning/raspberrypi_alsc_only.py +++ b/utils/tuning/raspberrypi_alsc_only.py @@ -3,7 +3,7 @@ # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> # -# raspberrypi_alsc_only.py - Tuning script for raspberrypi, ALSC only +# Tuning script for raspberrypi, ALSC only import sys diff --git a/utils/tuning/requirements.txt b/utils/tuning/requirements.txt new file mode 100644 index 00000000..3705769b --- /dev/null +++ b/utils/tuning/requirements.txt @@ -0,0 +1,9 @@ +coloredlogs +matplotlib +numpy +opencv-python +py3exiv2 +pyyaml +rawpy +scikit-learn +scipy diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py index 1cea6ddb..207b717a 100755 --- a/utils/tuning/rkisp1.py +++ b/utils/tuning/rkisp1.py @@ -2,39 +2,60 @@ # SPDX-License-Identifier: GPL-2.0-or-later # # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> +# Copyright (C) 2024, Ideas On Board # -# rkisp1.py - Tuning script for rkisp1 +# Tuning script for rkisp1 +import logging import sys +import coloredlogs import libtuning as lt -from libtuning.parsers import YamlParser from libtuning.generators import YamlOutput +from libtuning.modules.agc import AGCRkISP1 +from libtuning.modules.awb import AWBRkISP1 +from libtuning.modules.ccm import CCMRkISP1 from libtuning.modules.lsc import LSCRkISP1 +from libtuning.modules.lux import LuxRkISP1 +from libtuning.modules.static import StaticModule +from libtuning.parsers import YamlParser + +coloredlogs.install(level=logging.INFO, fmt='%(name)s %(levelname)s %(message)s') + +agc = AGCRkISP1(debug=[lt.Debug.Plot]) +awb = AWBRkISP1(debug=[lt.Debug.Plot]) +blc = StaticModule('BlackLevelCorrection') +ccm = CCMRkISP1(debug=[lt.Debug.Plot]) +color_processing = StaticModule('ColorProcessing') +filter = StaticModule('Filter') +gamma_out = StaticModule('GammaOutCorrection', {'gamma': 2.2}) +lsc = LSCRkISP1(debug=[lt.Debug.Plot], + # This is for the actual LSC tuning, and is part of the base LSC + # module. rkisp1's table sector sizes (16x16 programmed as mirrored + # 8x8) are separate, and is hardcoded in its specific LSC tuning + # module. + sector_shape=(17, 17), + + sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), + + # This is the function that will be used to average the pixels in + # each sector. This can also be a custom function. + sector_average_function=lt.average.Mean(), + + # This is the function that will be used to smooth the color ratio + # values. This can also be a custom function. + smoothing_function=lt.smoothing.MedianBlur(3),) +lux = LuxRkISP1(debug=[lt.Debug.Plot]) tuner = lt.Tuner('RkISP1') -tuner.add(LSCRkISP1( - debug=[lt.Debug.Plot], - # This is for the actual LSC tuning, and is part of the base LSC - # module. rkisp1's table sector sizes (16x16 programmed as mirrored - # 8x8) are separate, and is hardcoded in its specific LSC tuning - # module. - sector_shape=(17, 17), - - sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), - sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront), - - # This is the function that will be used to average the pixels in - # each sector. This can also be a custom function. - sector_average_function=lt.average.Mean(), - - # This is the function that will be used to smooth the color ratio - # values. This can also be a custom function. - smoothing_function=lt.smoothing.MedianBlur(3), - )) +tuner.add([agc, awb, blc, ccm, color_processing, filter, gamma_out, lsc, lux]) tuner.set_input_parser(YamlParser()) tuner.set_output_formatter(YamlOutput()) -tuner.set_output_order([LSCRkISP1]) + +# Bayesian AWB uses the lux value, so insert the lux algorithm before AWB. +tuner.set_output_order([agc, lux, awb, blc, ccm, color_processing, + filter, gamma_out, lsc]) if __name__ == '__main__': sys.exit(tuner.run(sys.argv)) diff --git a/utils/update-kernel-headers.sh b/utils/update-kernel-headers.sh index 590986d2..9a64dfb5 100755 --- a/utils/update-kernel-headers.sh +++ b/utils/update-kernel-headers.sh @@ -9,7 +9,7 @@ if [ $# != 1 ] ; then fi header_dir="$(dirname "$(realpath "$0")")/../include/linux" -kernel_dir="$1" +kernel_dir="$(realpath "$1")" # Bail out if the directory doesn't contain kernel sources line=$(head -3 "${kernel_dir}/Kbuild" 2>/dev/null | tail -1) @@ -52,6 +52,7 @@ headers=" linux/media-bus-format.h linux/media.h linux/rkisp1-config.h + linux/udmabuf.h linux/v4l2-common.h linux/v4l2-controls.h linux/v4l2-mediabus.h diff --git a/utils/update-mojo.sh b/utils/update-mojo.sh index fcbc81e7..09c8ff5b 100755 --- a/utils/update-mojo.sh +++ b/utils/update-mojo.sh @@ -3,13 +3,23 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Update mojo copy from a chromium source tree +set -e + if [ $# != 1 ] ; then echo "Usage: $0 <chromium dir>" exit 1 fi ipc_dir="$(dirname "$(realpath "$0")")/ipc" -chromium_dir="$1" +chromium_dir="$(realpath "$1")" + +cd "${ipc_dir}/../../" + +# Reject dirty libcamera trees +if [ -n "$(git status --porcelain -uno)" ] ; then + echo "libcamera tree is dirty" + exit 1 +fi if [ ! -d "${chromium_dir}/mojo" ] ; then echo "Directory ${chromium_dir} doesn't contain mojo" @@ -24,19 +34,23 @@ fi # Get the chromium commit id version=$(git -C "${chromium_dir}" rev-parse --short HEAD) -# Reject dirty trees +# Reject dirty chromium trees if [ -n "$(git -C "${chromium_dir}" status --porcelain)" ] ; then echo "Chromium tree in ${chromium_dir} is dirty" exit 1 fi +# Remove the previously imported files. +rm -rf utils/ipc/mojo/ +rm -rf utils/ipc/tools/ + # Copy the diagnosis file -cp "${chromium_dir}/tools/diagnosis/crbug_1001171.py" "${ipc_dir}/tools/diagnosis" +mkdir -p utils/ipc/tools/diagnosis/ +cp "${chromium_dir}/tools/diagnosis/crbug_1001171.py" utils/ipc/tools/diagnosis/ # Copy the rest of mojo -cp "${chromium_dir}/mojo/public/LICENSE" "${ipc_dir}/mojo/public" - -rm -rf "${ipc_dir}/mojo/public/tools/*" +mkdir -p utils/ipc/mojo/public/ +cp "${chromium_dir}/mojo/public/LICENSE" utils/ipc/mojo/public/ ( cd "${chromium_dir}" || exit @@ -55,12 +69,22 @@ modify them manually. EOF ) -echo "$readme" > "${ipc_dir}/mojo/README" -echo "$readme" > "${ipc_dir}/tools/README" +echo "$readme" > utils/ipc/mojo/README +echo "$readme" > utils/ipc/tools/README -cat <<EOF ------------------------------------------------------------- -mojo updated. Please review and up-port local changes before -committing. ------------------------------------------------------------- -EOF +# Commit the update. Use 'git commit -n' to avoid checkstyle pre-commit hook +# failures, as mojo doesn't comply with the Python coding style enforced by +# checkstyle.py. +git add utils/ipc/mojo/ +git add utils/ipc/tools/ + +echo "utils: ipc: Update mojo + +Update mojo from commit + +$(git -C "${chromium_dir}" show --pretty='%H "%s"' --no-patch) + +from the Chromium repository. + +The update-mojo.sh script was used for this update." | \ +git commit -n -s -F - |