Patch Detail
Show a patch.
GET /api/1.1/patches/17654/?format=api
{ "id": 17654, "url": "https://patchwork.libcamera.org/api/1.1/patches/17654/?format=api", "web_url": "https://patchwork.libcamera.org/patch/17654/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20221022062310.2545463-4-paul.elder@ideasonboard.com>", "date": "2022-10-22T06:23:02", "name": "[libcamera-devel,v2,03/11] utils: libtuning: modules: alsc: Add raspberrypi ALSC module", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "1c1952186c423c82fd2d6fbeb5747c79f3961d18", "submitter": { "id": 17, "url": "https://patchwork.libcamera.org/api/1.1/people/17/?format=api", "name": "Paul Elder", "email": "paul.elder@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/17654/mbox/", "series": [ { "id": 3573, "url": "https://patchwork.libcamera.org/api/1.1/series/3573/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3573", "date": "2022-10-22T06:22:59", "name": "utils: tuning: Add a new tuning infrastructure", "version": 2, "mbox": "https://patchwork.libcamera.org/series/3573/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/17654/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/17654/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 3D2F4C327C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 22 Oct 2022 06:23:30 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A5D4F62ED1;\n\tSat, 22 Oct 2022 08:23:29 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6532B62EC5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 22 Oct 2022 08:23:26 +0200 (CEST)", "from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp\n\t[175.177.42.159])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 286FE891;\n\tSat, 22 Oct 2022 08:23:24 +0200 (CEST)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1666419809;\n\tbh=QwIIKLKs60P8RlVm7Uxw8cwAWj1D3bY4a7SjBVzL3yQ=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=Xa/QJBXl4sJdSKP87xcYyJOQ+D7kBcBFzCKJgknfAdLM0kZlBrIVb+CulPZ8RfFiD\n\tJoj7eEalGp6joNmQXIbsNuKnkdAMslh5wloYMl2SOkdpLelxMC7rrHzYzkvoTgYGGv\n\tSchE+KKQ4+eB8LLgECBs+wJ77npZz4b1x0HEBmZo+Af7stfdCOqNvNwcPVZeVfXrBA\n\tbBwRGcepQMYKgfghvXHqL4/wtDXqj7qBVjS+wds3IQI6cGSDZiikxnYln43xtcUGNX\n\tRHMcR8YC20KX+6Z+3jL+T20qHQ2jG4k07sGl3YZrmvByAyqCU0qWK0nBl9ib7vtgpk\n\tFBvOhArn7VikA==", "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1666419806;\n\tbh=QwIIKLKs60P8RlVm7Uxw8cwAWj1D3bY4a7SjBVzL3yQ=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=k10sPJsaQgZUMH1gj/Pg9jQ0NlbxQdgV58wk39K0024fhpV9Vm8cFyuGDbbI5mMiK\n\t8YAGbPFkG9GjEHhsX4lNVIFy4QyF/iVVOwJvkXqr/EnIqCfhJpVOtNmoXUl55YREuO\n\tX/qPiWM5QORJw+95nSYrmX/fh3+pAuApCFQCjaXY=" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"k10sPJsa\"; dkim-atps=neutral", "To": "libcamera-devel@lists.libcamera.org", "Date": "Sat, 22 Oct 2022 15:23:02 +0900", "Message-Id": "<20221022062310.2545463-4-paul.elder@ideasonboard.com>", "X-Mailer": "git-send-email 2.30.2", "In-Reply-To": "<20221022062310.2545463-1-paul.elder@ideasonboard.com>", "References": "<20221022062310.2545463-1-paul.elder@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v2 03/11] utils: libtuning: modules: alsc:\n\tAdd raspberrypi ALSC module", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "From": "Paul Elder via libcamera-devel <libcamera-devel@lists.libcamera.org>", "Reply-To": "Paul Elder <paul.elder@ideasonboard.com>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "Add an ALSC module for Raspberry Pi.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n\n---\nNew in v2\n---\n .../tuning/libtuning/modules/alsc/__init__.py | 1 +\n .../libtuning/modules/alsc/raspberrypi.py | 246 ++++++++++++++++++\n 2 files changed, 247 insertions(+)\n create mode 100644 utils/tuning/libtuning/modules/alsc/raspberrypi.py", "diff": "diff --git a/utils/tuning/libtuning/modules/alsc/__init__.py b/utils/tuning/libtuning/modules/alsc/__init__.py\nindex a8f28923..ef6300c2 100644\n--- a/utils/tuning/libtuning/modules/alsc/__init__.py\n+++ b/utils/tuning/libtuning/modules/alsc/__init__.py\n@@ -3,3 +3,4 @@\n # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>\n \n from libtuning.modules.alsc.alsc import ALSC\n+from libtuning.modules.alsc.raspberrypi import ALSCRaspberryPi\ndiff --git a/utils/tuning/libtuning/modules/alsc/raspberrypi.py b/utils/tuning/libtuning/modules/alsc/raspberrypi.py\nnew file mode 100644\nindex 00000000..bea1a9a9\n--- /dev/null\n+++ b/utils/tuning/libtuning/modules/alsc/raspberrypi.py\n@@ -0,0 +1,246 @@\n+# SPDX-License-Identifier: BSD-2-Clause\n+#\n+# Copyright (C) 2019, Raspberry Pi Ltd\n+# Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com>\n+\n+from .alsc import ALSC\n+\n+import libtuning as lt\n+import libtuning.utils as utils\n+\n+from numbers import Number\n+import numpy as np\n+\n+\n+class ALSCRaspberryPi(ALSC):\n+ hr_name = 'ALSC (Raspberry Pi)'\n+ out_name = 'rpi.alsc'\n+ # todo Do something with the compatible list\n+ compatible = ['raspberrypi']\n+\n+ def __init__(self, *,\n+ do_color: lt.Param,\n+ luminance_strength: lt.Param,\n+ **kwargs):\n+ super().__init__(**kwargs)\n+\n+ self.do_color = do_color\n+ self.luminance_strength = luminance_strength\n+\n+ self.output_range = (0, 3.999)\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+ return False\n+\n+ valid = True\n+\n+ conf = config[self]\n+\n+ lum_key = self.luminance_strength.name\n+ color_key = self.do_color.name\n+\n+ if lum_key not in conf and self.luminance_strength.is_required():\n+ utils.eprint(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+\n+ if color_key not in conf and self.do_color.is_required():\n+ utils.eprint(f'{color_key} is not in config')\n+ valid = False\n+\n+ return valid\n+\n+ # @return Image color temperature, flattened array of red calibration table\n+ # (containing {sector size} elements), flattened array of blue\n+ # calibration table, flattened array of green calibration\n+ # table\n+\n+ def _do_single_alsc(self, image: lt.Image, do_alsc_colour: bool):\n+ average_green = np.mean((image.channels[lt.Color.GR:lt.Color.GB + 1]), axis=0)\n+\n+ cg, g = self._lsc_single_channel(average_green, image)\n+\n+ if not do_alsc_colour:\n+ return image.color, None, None, cg.flatten()\n+\n+ cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, g)\n+ cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, g)\n+\n+ # todo implement debug\n+\n+ return image.color, cr.flatten(), cb.flatten(), cg.flatten()\n+\n+ # @return Red shading table, Blue shading table, Green shading table,\n+ # number of images processed\n+\n+ def _do_all_alsc(self, images: list, do_alsc_colour: bool, general_conf: dict) -> (list, list, list, Number, int):\n+ # List of colour temperatures\n+ list_col = []\n+ # Associated calibration tables\n+ list_cr = []\n+ list_cb = []\n+ list_cg = []\n+ count = 0\n+ for image in self._enumerate_alsc_images(images):\n+ col, cr, cb, cg = self._do_single_alsc(image, do_alsc_colour)\n+ list_col.append(col)\n+ list_cr.append(cr)\n+ list_cb.append(cb)\n+ list_cg.append(cg)\n+ count += 1\n+\n+ # Convert to numpy array for data manipulation\n+ list_col = np.array(list_col)\n+ list_cr = np.array(list_cr)\n+ list_cb = np.array(list_cb)\n+ list_cg = np.array(list_cg)\n+\n+ cal_cr_list = []\n+ cal_cb_list = []\n+\n+ # Note: Calculation of average corners and center of the shading tables\n+ # has been removed (which ctt had, as it was being unused)\n+\n+ # todo Handle the second green table\n+\n+ # Average all values for luminance shading and return one table for all temperatures\n+ lum_lut = list(utils.round_with_sigfigs(np.mean(list_cg, axis=0), self.output_range))\n+\n+ if not do_alsc_colour:\n+ return None, None, lum_lut, count\n+\n+ for ct in sorted(set(list_col)):\n+ # Average tables for the same colour temperature\n+ indices = np.where(list_col == ct)\n+ ct = int(ct)\n+ t_r = utils.round_with_sigfigs(np.mean(list_cr[indices], axis=0),\n+ self.output_range)\n+ t_b = utils.round_with_sigfigs(np.mean(list_cb[indices], axis=0),\n+ self.output_range)\n+\n+ cr_dict = {\n+ 'ct': ct,\n+ 'table': list(t_r)\n+ }\n+ cb_dict = {\n+ 'ct': ct,\n+ 'table': list(t_b)\n+ }\n+ cal_cr_list.append(cr_dict)\n+ cal_cb_list.append(cb_dict)\n+\n+ return cal_cr_list, cal_cb_list, lum_lut, count\n+\n+ # @brief Calculate sigma from two adjacent gain tables\n+ def _calcSigma(self, g1, g2):\n+ g1 = np.reshape(g1, self.sector_shape[::-1])\n+ g2 = np.reshape(g2, self.sector_shape[::-1])\n+\n+ # Apply gains to gain table\n+ gg = g1 / g2\n+ if np.mean(gg) < 1:\n+ gg = 1 / gg\n+\n+ # For each internal patch, compute average difference between it and\n+ # its 4 neighbours, then append to list\n+ diffs = []\n+ for i in range(self.sector_shape[1] - 2):\n+ for j in range(self.sector_shape[0] - 2):\n+ # Indexing is incremented by 1 since all patches on borders are\n+ # not counted\n+ diff = np.abs(gg[i + 1][j + 1] - gg[i][j + 1])\n+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 2][j + 1])\n+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j])\n+ diff += np.abs(gg[i + 1][j + 1] - gg[i + 1][j + 2])\n+ diffs.append(diff / 4)\n+\n+ mean_diff = np.mean(diffs)\n+ return(np.round(mean_diff, 5))\n+\n+ # @brief Obtains sigmas for red and blue, effectively a measure of the\n+ # 'error'\n+ def _get_sigma(self, cal_cr_list, cal_cb_list):\n+ # Provided colour alsc tables were generated for two different colour\n+ # temperatures sigma is calculated by comparing two calibration temperatures\n+ # adjacent in colour space\n+\n+ color_temps = [cal['ct'] for cal in cal_cr_list]\n+\n+ # Calculate sigmas for each adjacent color_temps and return worst one\n+ sigma_rs = []\n+ sigma_bs = []\n+ for i in range(len(color_temps) - 1):\n+ sigma_rs.append(self._calcSigma(cal_cr_list[i]['table'], cal_cr_list[i + 1]['table']))\n+ sigma_bs.append(self._calcSigma(cal_cb_list[i]['table'], cal_cb_list[i + 1]['table']))\n+\n+ # Return maximum sigmas, not necessarily from the same colour\n+ # temperature interval\n+ sigma_r = max(sigma_rs) if sigma_rs else 0.005\n+ sigma_b = max(sigma_bs) if sigma_bs else 0.005\n+\n+ return sigma_r, sigma_b\n+\n+ def _process(self, args, config: dict, images: list, outputs: dict) -> dict:\n+ output = {\n+ 'omega': 1.3,\n+ 'n_iter': 100,\n+ 'luminance_strength': 0.7\n+ }\n+\n+ conf = config[self]\n+ general_conf = config['general']\n+\n+ do_alsc_colour = self.do_color.get_value(conf)\n+\n+ # todo I have no idea where this input parameter is used\n+ luminance_strength = self.luminance_strength.get_value(conf)\n+ if luminance_strength < 0 or luminance_strength > 1:\n+ luminance_strength = 0.5\n+\n+ output['luminance_strength'] = luminance_strength\n+\n+ # todo Validate images from greyscale camera and force grescale mode\n+ # todo Debug functionality\n+\n+ alsc_out = self._do_all_alsc(images, do_alsc_colour, general_conf)\n+ # todo Handle the second green lut\n+ cal_cr_list, cal_cb_list, luminance_lut, count = alsc_out\n+\n+ if not do_alsc_colour:\n+ output['luminance_lut'] = luminance_lut\n+ output['n_iter'] = 0\n+ return output\n+\n+ output['calibrations_Cr'] = cal_cr_list\n+ output['calibrations_Cb'] = cal_cb_list\n+ output['luminance_lut'] = luminance_lut\n+\n+ # The sigmas determine the strength of the adaptive algorithm, that\n+ # cleans up any lens shading that has slipped through the alsc. These\n+ # are determined by measuring a 'worst-case' difference between two\n+ # alsc tables that are adjacent in colour space. If, however, only one\n+ # colour temperature has been provided, then this difference can not be\n+ # computed as only one table is available.\n+ # To determine the sigmas you would have to estimate the error of an\n+ # alsc table with only the image it was taken on as a check. To avoid\n+ # circularity, dfault exaggerated sigmas are used, which can result in\n+ # too much alsc and is therefore not advised.\n+ # In general, just take another alsc picture at another colour\n+ # temperature!\n+\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+ return output\n+\n+ # Obtain worst-case scenario residual sigmas\n+ sigma_r, sigma_b = self._get_sigma(cal_cr_list, cal_cb_list)\n+ output['sigma'] = np.round(sigma_r, 5)\n+ output['sigma_Cb'] = np.round(sigma_b, 5)\n+\n+ return output\n", "prefixes": [ "libcamera-devel", "v2", "03/11" ] }