[{"id":30131,"web_url":"https://patchwork.libcamera.org/comment/30131/","msgid":"<20240628224549.GE30900@pendragon.ideasonboard.com>","date":"2024-06-28T22:45:49","subject":"Re: [PATCH v2 07/25] libtuning: Migrate prints to python logging\n\tframework","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Stefan,\n\nThank you for the patch.\n\nOn Fri, Jun 28, 2024 at 12:47:00PM +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> ---\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                 |  2 ++\n>  utils/tuning/rkisp1.py                        |  5 ++++\n>  9 files changed, 63 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..2b6ed45c1cc0 100644\n> --- a/utils/tuning/requirements.txt\n> +++ b/utils/tuning/requirements.txt\n> @@ -1,5 +1,7 @@\n> +coloredlogs\n>  numpy\n>  opencv-python\n>  py3exiv2\n>  pyyaml\n>  rawpy\n> +\n\nYou can drop the empty line.\n\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\nShould this go go libtuning/__init__.py, or do you envision different\ntuning scripts configuring logging differently ?\n\n>  tuner = lt.Tuner('RkISP1')\n>  tuner.add(LSCRkISP1(\n>            debug=[lt.Debug.Plot],","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 CA42BBDB1D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 28 Jun 2024 22:46:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B74AF62C99;\n\tSat, 29 Jun 2024 00:46:11 +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 C87B0619E8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 29 Jun 2024 00:46:10 +0200 (CEST)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 91384471;\n\tSat, 29 Jun 2024 00:45:45 +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=\"H/U7ZANf\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1719614745;\n\tbh=imxX9Pn8+9Ri5+050vy1f4YzxWpLctcakhrRVfrFqRw=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=H/U7ZANfN6gjT+1hAoaWmg4oLfJm/Zp1uTtkSFT6VxOKyWrdDXfKGCIxhrE9GYhai\n\tfvyxy7hDSzTWm8XX9WGTkN0g0V4LWiDvIy78utgBKS03ifzO8IMjC9W8ebBhPG1XoU\n\tSGB71i/IAwW4eQoOdE5lu3BHFWnG79XMXx/x2NHw=","Date":"Sat, 29 Jun 2024 01:45:49 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v2 07/25] libtuning: Migrate prints to python logging\n\tframework","Message-ID":"<20240628224549.GE30900@pendragon.ideasonboard.com>","References":"<20240628104828.2928109-1-stefan.klug@ideasonboard.com>\n\t<20240628104828.2928109-8-stefan.klug@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240628104828.2928109-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>"}},{"id":30189,"web_url":"https://patchwork.libcamera.org/comment/30189/","msgid":"<c6tsxm5qxgait5kvyl5kncdxen6omdxrrrvtom5olm7vghrnyo@urgpa5hacjsg>","date":"2024-07-01T15:06:33","subject":"Re: [PATCH v2 07/25] libtuning: Migrate prints to python logging\n\tframework","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Laurent,\n\nOn Sat, Jun 29, 2024 at 01:45:49AM +0300, Laurent Pinchart wrote:\n> Hi Stefan,\n> \n> Thank you for the patch.\n> \n> On Fri, Jun 28, 2024 at 12:47:00PM +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> > ---\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                 |  2 ++\n> >  utils/tuning/rkisp1.py                        |  5 ++++\n> >  9 files changed, 63 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..2b6ed45c1cc0 100644\n> > --- a/utils/tuning/requirements.txt\n> > +++ b/utils/tuning/requirements.txt\n> > @@ -1,5 +1,7 @@\n> > +coloredlogs\n> >  numpy\n> >  opencv-python\n> >  py3exiv2\n> >  pyyaml\n> >  rawpy\n> > +\n> \n> You can drop the empty line.\n> \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> \n> Should this go go libtuning/__init__.py, or do you envision different\n> tuning scripts configuring logging differently ?\n\nThis depends on the scope of libtuning. To me it is a library/toolbox\nwith all the pieces needed to setup the tuning process. THe actual\napplication (utils/tuning/rkisp1.py) lives outside libtuning. Setting up\nthe logging is imho the job of the application. So I wouldn't move it\ninto libtuning.\n\nBest regards,\nStefan\n\n> \n> >  tuner = lt.Tuner('RkISP1')\n> >  tuner.add(LSCRkISP1(\n> >            debug=[lt.Debug.Plot],\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart","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 15042BD87C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  1 Jul 2024 15:06:40 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A701462E22;\n\tMon,  1 Jul 2024 17:06:38 +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 17877604C1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  1 Jul 2024 17:06:37 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:89b2:f6c7:b29b:4e5c])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 272A863D;\n\tMon,  1 Jul 2024 17:06:10 +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=\"V3fiWQKV\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1719846370;\n\tbh=4zy0jPeS5bcdbiPEeZbxWSKcvc+Tvb4qfYFufysO6qQ=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=V3fiWQKVlIEMGt1qRdJ8GIBXIylY+8TTdYaIfLVWYzvu9MYjaUVEIfIdxYSMmcaOk\n\tMfM/ovJSriM26a599zN4f2bLqB4k/4/2EMoWhphp0iRMVD3fNpT/sAloJuVjzJ5U8I\n\taMvxBwEhLmaUh+8aHfy0OX+TU/UuBOoWqlrAKjQo=","Date":"Mon, 1 Jul 2024 17:06:33 +0200","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v2 07/25] libtuning: Migrate prints to python logging\n\tframework","Message-ID":"<c6tsxm5qxgait5kvyl5kncdxen6omdxrrrvtom5olm7vghrnyo@urgpa5hacjsg>","References":"<20240628104828.2928109-1-stefan.klug@ideasonboard.com>\n\t<20240628104828.2928109-8-stefan.klug@ideasonboard.com>\n\t<20240628224549.GE30900@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240628224549.GE30900@pendragon.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>"}}]