[{"id":30297,"web_url":"https://patchwork.libcamera.org/comment/30297/","msgid":"<ZoZl00F3rbHMRn2M@pyrite.rasen.tech>","date":"2024-07-04T09:05:23","subject":"Re: [PATCH v3 07/23] libtuning: Migrate prints to python logging\n\tframework","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Wed, Jul 03, 2024 at 04:16:56PM +0200, Stefan Klug wrote:\n> In ctt_ccm.py the logging functionality of the Cam object was used. As\n> we don't want to port over that class, it needs to be replaced anyways.\n> While at it, also replace the eprint function as it doesn't add any\n> value over the logging framework and misses the ability for easy log\n> formatting.\n> \n> For nice output formatting add the coloredlogs library.\n> \n> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> ---\n>  utils/tuning/libtuning/ctt_ccm.py             | 27 ++++++++++---------\n>  .../libtuning/generators/yaml_output.py       |  5 ++--\n>  utils/tuning/libtuning/image.py               |  7 +++--\n>  utils/tuning/libtuning/libtuning.py           | 21 ++++++++-------\n>  utils/tuning/libtuning/macbeth.py             | 13 +++++----\n>  .../libtuning/modules/lsc/raspberrypi.py      | 12 +++++----\n>  utils/tuning/libtuning/utils.py               | 17 ++++++------\n>  utils/tuning/requirements.txt                 |  1 +\n>  utils/tuning/rkisp1.py                        |  5 ++++\n>  9 files changed, 62 insertions(+), 46 deletions(-)\n> \n> diff --git a/utils/tuning/libtuning/ctt_ccm.py b/utils/tuning/libtuning/ctt_ccm.py\n> index f37adaf45538..c4362756c3c0 100644\n> --- a/utils/tuning/libtuning/ctt_ccm.py\n> +++ b/utils/tuning/libtuning/ctt_ccm.py\n> @@ -4,6 +4,8 @@\n>  #\n>  # camera tuning tool for CCM (colour correction matrix)\n>  \n> +import logging\n> +\n>  import numpy as np\n>  from scipy.optimize import minimize\n>  \n> @@ -12,6 +14,8 @@ from .image import Image\n>  from .ctt_awb import get_alsc_patches\n>  from .utils import visualise_macbeth_chart\n>  \n> +logger = logging.getLogger(__name__)\n> +\n>  \"\"\"\n>  takes 8-bit macbeth chart values, degammas and returns 16 bit\n>  \"\"\"\n> @@ -129,7 +133,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>      \"\"\"\n>      ccm_tab = {}\n>      for Img in imgs:\n> -        Cam.log += '\\nProcessing image: ' + Img.name\n> +        logger.info('Processing image: ' + Img.name)\n>          \"\"\"\n>          get macbeth patches with alsc applied if alsc enabled.\n>          Note: if alsc is disabled then colour_cals will be set to None and no\n> @@ -154,7 +158,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>          each channel for each patch\n>          \"\"\"\n>          gain = np.mean(m_srgb) / np.mean((r, g, b))\n> -        Cam.log += '\\nGain with respect to standard colours: {:.3f}'.format(gain)\n> +        logger.info(f'Gain with respect to standard colours: {gain:.3f}')\n>          r = np.mean(gain * r, axis=1)\n>          b = np.mean(gain * b, axis=1)\n>          g = np.mean(gain * g, axis=1)\n> @@ -192,15 +196,13 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>          zero since the input data is imperfect\n>          '''\n>  \n> -        Cam.log += (\"\\n \\n Optimised Matrix Below: \\n \\n\")\n>          [r1, r2, g1, g2, b1, b2] = result.x\n>          # The new, optimised color correction matrix values\n> +        # This is the optimised Color Matrix (preserving greys by summing rows up to 1)\n>          optimised_ccm = [r1, r2, (1 - r1 - r2), g1, g2, (1 - g1 - g2), b1, b2, (1 - b1 - b2)]\n>  \n> -        # This is the optimised Color Matrix (preserving greys by summing rows up to 1)\n> -        Cam.log += str(optimised_ccm)\n> -        Cam.log += \"\\n Old Color Correction Matrix Below \\n\"\n> -        Cam.log += str(ccm)\n> +        logger.info(f'Optimized Matrix: {np.round(optimised_ccm, 4)}')\n> +        logger.info(f'Old Matrix:       {np.round(ccm, 4)}')\n>  \n>          formatted_ccm = np.array(original_ccm).reshape((3, 3))\n>  \n> @@ -229,7 +231,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>          We now want to spit out some data that shows\n>          how the optimisation has improved the color matrices\n>          '''\n> -        Cam.log += \"Here are the Improvements\"\n> +        logger.info(\"Here are the Improvements\")\n>  \n>          # CALCULATE WORST CASE delta e\n>          old_worst_delta_e = 0\n> @@ -244,8 +246,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>              if new_delta_e > new_worst_delta_e:\n>                  new_worst_delta_e = new_delta_e\n>  \n> -        Cam.log += \"Before color correction matrix was optimised, we got an average delta E of \" + str(before_average) + \" and a maximum delta E of \" + str(old_worst_delta_e)\n> -        Cam.log += \"After color correction matrix was optimised, we got an average delta E of \" + str(after_average) + \" and a maximum delta E of \" + str(new_worst_delta_e)\n> +        logger.info(f'delta E optimized: average: {after_average:.2f}  max:{new_worst_delta_e:.2f}')\n> +        logger.info(f'delta E old:       average: {before_average:.2f}  max:{old_worst_delta_e:.2f}')\n>  \n>          visualise_macbeth_chart(m_rgb, optimised_ccm_rgb, after_gamma_rgb, str(Img.col) + str(matrix_selection_types[typenum]))\n>          '''\n> @@ -262,9 +264,8 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>              ccm_tab[Img.col].append(optimised_ccm)\n>          else:\n>              ccm_tab[Img.col] = [optimised_ccm]\n> -        Cam.log += '\\n'\n>  \n> -    Cam.log += '\\nFinished processing images'\n> +    logger.info('Finished processing images')\n>      \"\"\"\n>      average any ccms that share a colour temperature\n>      \"\"\"\n> @@ -273,7 +274,7 @@ def ccm(Cam, cal_cr_list, cal_cb_list):\n>          tab = np.where((10000 * tab) % 1 <= 0.05, tab + 0.00001, tab)\n>          tab = np.where((10000 * tab) % 1 >= 0.95, tab - 0.00001, tab)\n>          ccm_tab[k] = list(np.round(tab, 5))\n> -        Cam.log += '\\nMatrix calculated for colour temperature of {} K'.format(k)\n> +        logger.info(f'Matrix calculated for colour temperature of {k} K')\n>  \n>      \"\"\"\n>      return all ccms with respective colour temperature in the correct format,\n> diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py\n> index 8f22d386f1b3..31e265df4ea7 100644\n> --- a/utils/tuning/libtuning/generators/yaml_output.py\n> +++ b/utils/tuning/libtuning/generators/yaml_output.py\n> @@ -9,8 +9,9 @@ from .generator import Generator\n>  from numbers import Number\n>  from pathlib import Path\n>  \n> -import libtuning.utils as utils\n> +import logging\n>  \n> +logger = logging.getLogger(__name__)\n>  \n>  class YamlOutput(Generator):\n>      def __init__(self):\n> @@ -112,7 +113,7 @@ class YamlOutput(Generator):\n>                  continue\n>  \n>              if not isinstance(output_dict[module], dict):\n> -                utils.eprint(f'Error: Output of {module.type} is not a dictionary')\n> +                logger.error(f'Error: Output of {module.type} is not a dictionary')\n>                  continue\n>  \n>              lines = self._stringify_dict(output_dict[module])\n> diff --git a/utils/tuning/libtuning/image.py b/utils/tuning/libtuning/image.py\n> index 6ff60ec17ec4..2c4d774f11e2 100644\n> --- a/utils/tuning/libtuning/image.py\n> +++ b/utils/tuning/libtuning/image.py\n> @@ -13,6 +13,9 @@ import re\n>  \n>  import libtuning as lt\n>  import libtuning.utils as utils\n> +import logging\n> +\n> +logger = logging.getLogger(__name__)\n>  \n>  \n>  class Image:\n> @@ -25,13 +28,13 @@ class Image:\n>          try:\n>              self._load_metadata_exif()\n>          except Exception as e:\n> -            utils.eprint(f'Failed to load metadata from {self.path}: {e}')\n> +            logger.error(f'Failed to load metadata from {self.path}: {e}')\n>              raise e\n>  \n>          try:\n>              self._read_image_dng()\n>          except Exception as e:\n> -            utils.eprint(f'Failed to load image data from {self.path}: {e}')\n> +            logger.error(f'Failed to load image data from {self.path}: {e}')\n>              raise e\n>  \n>      @property\n> diff --git a/utils/tuning/libtuning/libtuning.py b/utils/tuning/libtuning/libtuning.py\n> index 5e22288df49b..5342e5d6daaa 100644\n> --- a/utils/tuning/libtuning/libtuning.py\n> +++ b/utils/tuning/libtuning/libtuning.py\n> @@ -5,13 +5,14 @@\n>  # An infrastructure for camera tuning tools\n>  \n>  import argparse\n> +import logging\n>  \n>  import libtuning as lt\n>  import libtuning.utils as utils\n> -from libtuning.utils import eprint\n>  \n>  from enum import Enum, IntEnum\n>  \n> +logger = logging.getLogger(__name__)\n>  \n>  class Color(IntEnum):\n>      R = 0\n> @@ -112,10 +113,10 @@ class Tuner(object):\n>          for module_type in output_order:\n>              modules = [module for module in self.modules if module.type == module_type.type]\n>              if len(modules) > 1:\n> -                eprint(f'Multiple modules found for module type \"{module_type.type}\"')\n> +                logger.error(f'Multiple modules found for module type \"{module_type.type}\"')\n>                  return False\n>              if len(modules) < 1:\n> -                eprint(f'No module found for module type \"{module_type.type}\"')\n> +                logger.error(f'No module found for module type \"{module_type.type}\"')\n>                  return False\n>              self.output_order.append(modules[0])\n>  \n> @@ -124,19 +125,19 @@ class Tuner(object):\n>      # \\todo Validate parser and generator at Tuner construction time?\n>      def _validate_settings(self):\n>          if self.parser is None:\n> -            eprint('Missing parser')\n> +            logger.error('Missing parser')\n>              return False\n>  \n>          if self.generator is None:\n> -            eprint('Missing generator')\n> +            logger.error('Missing generator')\n>              return False\n>  \n>          if len(self.modules) == 0:\n> -            eprint('No modules added')\n> +            logger.error('No modules added')\n>              return False\n>  \n>          if len(self.output_order) != len(self.modules):\n> -            eprint('Number of outputs does not match number of modules')\n> +            logger.error('Number of outputs does not match number of modules')\n>              return False\n>  \n>          return True\n> @@ -183,7 +184,7 @@ class Tuner(object):\n>  \n>          for module in self.modules:\n>              if not module.validate_config(self.config):\n> -                eprint(f'Config is invalid for module {module.type}')\n> +                logger.error(f'Config is invalid for module {module.type}')\n>                  return -1\n>  \n>          has_lsc = any(isinstance(m, lt.modules.lsc.LSC) for m in self.modules)\n> @@ -192,14 +193,14 @@ class Tuner(object):\n>  \n>          images = utils.load_images(args.input, self.config, not has_only_lsc, has_lsc)\n>          if images is None or len(images) == 0:\n> -            eprint(f'No images were found, or able to load')\n> +            logger.error(f'No images were found, or able to load')\n>              return -1\n>  \n>          # Do the tuning\n>          for module in self.modules:\n>              out = module.process(self.config, images, self.output)\n>              if out is None:\n> -                eprint(f'Module {module.name} failed to process, aborting')\n> +                logger.error(f'Module {module.hr_name} failed to process...')\n>                  break\n>              self.output[module] = out\n>  \n> diff --git a/utils/tuning/libtuning/macbeth.py b/utils/tuning/libtuning/macbeth.py\n> index 265a33d68378..28051de8155c 100644\n> --- a/utils/tuning/libtuning/macbeth.py\n> +++ b/utils/tuning/libtuning/macbeth.py\n> @@ -13,12 +13,15 @@ import os\n>  from pathlib import Path\n>  import numpy as np\n>  import warnings\n> +import logging\n>  from sklearn import cluster as cluster\n>  \n>  from .ctt_ransac import get_square_verts, get_square_centres\n>  \n>  from libtuning.image import Image\n>  \n> +logger = logging.getLogger(__name__)\n> +\n>  \n>  # Reshape image to fixed width without distorting returns image and scale\n>  # factor\n> @@ -374,7 +377,7 @@ def get_macbeth_chart(img, ref_data):\n>  \n>      # Catch macbeth errors and continue with code\n>      except MacbethError as error:\n> -        eprint(error)\n> +        logger.warning(error)\n>          return (0, None, None, False)\n>  \n>  \n> @@ -497,7 +500,7 @@ def find_macbeth(img, mac_config):\n>  \n>      coords_fit = coords\n>      if cor < 0.75:\n> -        eprint(f'Warning: Low confidence {cor:.3f} for macbeth chart in {img.path.name}')\n> +        logger.warning(f'Low confidence {cor:.3f} for macbeth chart')\n>  \n>      if show:\n>          draw_macbeth_results(img, coords_fit)\n> @@ -510,18 +513,18 @@ def locate_macbeth(image: Image, config: dict):\n>      av_chan = (np.mean(np.array(image.channels), axis=0) / (2**16))\n>      av_val = np.mean(av_chan)\n>      if av_val < image.blacklevel_16 / (2**16) + 1 / 64:\n> -        eprint(f'Image {image.path.name} too dark')\n> +        logger.warning(f'Image {image.path.name} too dark')\n>          return None\n>  \n>      macbeth = find_macbeth(av_chan, config['general']['macbeth'])\n>  \n>      if macbeth is None:\n> -        eprint(f'No macbeth chart found in {image.path.name}')\n> +        logger.warning(f'No macbeth chart found in {image.path.name}')\n>          return None\n>  \n>      mac_cen_coords = macbeth[1]\n>      if not image.get_patches(mac_cen_coords):\n> -        eprint(f'Macbeth patches have saturated in {image.path.name}')\n> +        logger.warning(f'Macbeth patches have saturated in {image.path.name}')\n>          return None\n>  \n>      return macbeth\n> diff --git a/utils/tuning/libtuning/modules/lsc/raspberrypi.py b/utils/tuning/libtuning/modules/lsc/raspberrypi.py\n> index f19c71637b89..99bc4fe6e07f 100644\n> --- a/utils/tuning/libtuning/modules/lsc/raspberrypi.py\n> +++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py\n> @@ -12,7 +12,9 @@ import libtuning.utils as utils\n>  \n>  from numbers import Number\n>  import numpy as np\n> +import logging\n>  \n> +logger = logging.getLogger(__name__)\n>  \n>  class ALSCRaspberryPi(LSC):\n>      # Override the type name so that the parser can match the entry in the\n> @@ -35,7 +37,7 @@ class ALSCRaspberryPi(LSC):\n>  \n>      def validate_config(self, config: dict) -> bool:\n>          if self not in config:\n> -            utils.eprint(f'{self.type} not in config')\n> +            logger.error(f'{self.type} not in config')\n>              return False\n>  \n>          valid = True\n> @@ -46,14 +48,14 @@ class ALSCRaspberryPi(LSC):\n>          color_key = self.do_color.name\n>  \n>          if lum_key not in conf and self.luminance_strength.required:\n> -            utils.eprint(f'{lum_key} is not in config')\n> +            logger.error(f'{lum_key} is not in config')\n>              valid = False\n>  \n>          if lum_key in conf and (conf[lum_key] < 0 or conf[lum_key] > 1):\n> -            utils.eprint(f'Warning: {lum_key} is not in range [0, 1]; defaulting to 0.5')\n> +            logger.warning(f'{lum_key} is not in range [0, 1]; defaulting to 0.5')\n>  \n>          if color_key not in conf and self.do_color.required:\n> -            utils.eprint(f'{color_key} is not in config')\n> +            logger.error(f'{color_key} is not in config')\n>              valid = False\n>  \n>          return valid\n> @@ -235,7 +237,7 @@ class ALSCRaspberryPi(LSC):\n>          if count == 1:\n>              output['sigma'] = 0.005\n>              output['sigma_Cb'] = 0.005\n> -            utils.eprint('Warning: Only one alsc calibration found; standard sigmas used for adaptive algorithm.')\n> +            logger.warning('Only one alsc calibration found; standard sigmas used for adaptive algorithm.')\n>              return output\n>  \n>          # Obtain worst-case scenario residual sigmas\n> diff --git a/utils/tuning/libtuning/utils.py b/utils/tuning/libtuning/utils.py\n> index f099c0ed685c..872341407b7b 100644\n> --- a/utils/tuning/libtuning/utils.py\n> +++ b/utils/tuning/libtuning/utils.py\n> @@ -12,16 +12,15 @@ import os\n>  from pathlib import Path\n>  import re\n>  import sys\n> +import logging\n>  \n>  import libtuning as lt\n>  from libtuning.image import Image\n>  from libtuning.macbeth import locate_macbeth\n>  \n> -# Utility functions\n> -\n> +logger = logging.getLogger(__name__)\n>  \n> -def eprint(*args, **kwargs):\n> -    print(*args, file=sys.stderr, **kwargs)\n> +# Utility functions\n>  \n>  \n>  def get_module_by_type_name(modules, name):\n> @@ -45,7 +44,7 @@ def _list_image_files(directory):\n>  def _parse_image_filename(fn: Path):\n>      result = re.search(r'^(alsc_)?(\\d+)[kK]_(\\d+)?[lLuU]?.\\w{3,4}$', fn.name)\n>      if result is None:\n> -        eprint(f'The file name of {fn.name} is incorrectly formatted')\n> +        logger.error(f'The file name of {fn.name} is incorrectly formatted')\n>          return None, None, None\n>  \n>      color = int(result.group(2))\n> @@ -72,7 +71,7 @@ def _validate_images(images):\n>  def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) -> list:\n>      files = _list_image_files(input_dir)\n>      if len(files) == 0:\n> -        eprint(f'No images found in {input_dir}')\n> +        logger.error(f'No images found in {input_dir}')\n>          return None\n>  \n>      images = []\n> @@ -83,19 +82,19 @@ def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool)\n>  \n>          # Skip lsc image if we don't need it\n>          if lsc_only and not load_lsc:\n> -            eprint(f'Skipping {f.name} as this tuner has no LSC module')\n> +            logger.warning(f'Skipping {f.name} as this tuner has no LSC module')\n>              continue\n>  \n>          # Skip non-lsc image if we don't need it\n>          if not lsc_only and not load_nonlsc:\n> -            eprint(f'Skipping {f.name} as this tuner only has an LSC module')\n> +            logger.warning(f'Skipping {f.name} as this tuner only has an LSC module')\n>              continue\n>  \n>          # Load image\n>          try:\n>              image = Image(f)\n>          except Exception as e:\n> -            eprint(f'Failed to load image {f.name}: {e}')\n> +            logger.error(f'Failed to load image {f.name}: {e}')\n>              continue\n>  \n>          # Populate simple fields\n> diff --git a/utils/tuning/requirements.txt b/utils/tuning/requirements.txt\n> index d1dc589d0329..c3c20cee1263 100644\n> --- a/utils/tuning/requirements.txt\n> +++ b/utils/tuning/requirements.txt\n> @@ -1,3 +1,4 @@\n> +coloredlogs\n>  numpy\n>  opencv-python\n>  py3exiv2\n> diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py\n> index d0ce15d5ed7a..2606e07a93f7 100755\n> --- a/utils/tuning/rkisp1.py\n> +++ b/utils/tuning/rkisp1.py\n> @@ -5,6 +5,8 @@\n>  #\n>  # Tuning script for rkisp1\n>  \n> +import coloredlogs\n> +import logging\n>  import sys\n>  \n>  import libtuning as lt\n> @@ -13,6 +15,9 @@ from libtuning.generators import YamlOutput\n>  from libtuning.modules.lsc import LSCRkISP1\n>  from libtuning.modules.agc import AGCRkISP1\n>  \n> +\n> +coloredlogs.install(level=logging.INFO, fmt='%(name)s %(levelname)s %(message)s')\n> +\n>  tuner = lt.Tuner('RkISP1')\n>  tuner.add(LSCRkISP1(\n>            debug=[lt.Debug.Plot],\n> -- \n> 2.43.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 98006BEFBE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  4 Jul 2024 09:05:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4316B62E24;\n\tThu,  4 Jul 2024 11:05:33 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D159962C99\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  4 Jul 2024 11:05:31 +0200 (CEST)","from pyrite.rasen.tech (h175-177-049-156.catv02.itscom.jp\n\t[175.177.49.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 98D7463D;\n\tThu,  4 Jul 2024 11:05:01 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"P/nMy96T\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1720083903;\n\tbh=kxjC66yK5yRjux04HYYlTN3D8xKtK0VeC5E9GKxkXvg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=P/nMy96TTa04mWWUrQfb8EnNVs63qxauxtWf0hFYvZyg2aWdu9/x0IBB3igUGRUsG\n\tWR1frvdvk+3yPjRTXSPaNFWBJex4zOZBbOaBWzgUtjgyBOjZrUtDNiWZrJJ0CVz/p3\n\tSbdTEF4RKjjaDj76nRi/FyC33WYW+CkTm4NMM6aw=","Date":"Thu, 4 Jul 2024 18:05:23 +0900","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v3 07/23] libtuning: Migrate prints to python logging\n\tframework","Message-ID":"<ZoZl00F3rbHMRn2M@pyrite.rasen.tech>","References":"<20240703141726.252368-1-stefan.klug@ideasonboard.com>\n\t<20240703141726.252368-8-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20240703141726.252368-8-stefan.klug@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]