Message ID | 20250304231254.10588-2-dan.scally@ideasonboard.com |
---|---|
State | New |
Headers | show |
Series |
|
Related | show |
Quoting Daniel Scally (2025-03-04 23:12:52) > Add tuning scripts for the mali-c55 IPA. At present the only module > that is available is for lens shading correction. > As a new module - I don't think this can cause regressions - so as long as it runs ... Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > .../tuning/libtuning/modules/lsc/__init__.py | 1 + > .../tuning/libtuning/modules/lsc/mali_c55.py | 173 ++++++++++++++++++ > utils/tuning/mali-c55.py | 40 ++++ > 3 files changed, 214 insertions(+) > create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py > create mode 100755 utils/tuning/mali-c55.py > > diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py > index 0ba4411b..edd5ce7f 100644 > --- a/utils/tuning/libtuning/modules/lsc/__init__.py > +++ b/utils/tuning/libtuning/modules/lsc/__init__.py > @@ -3,5 +3,6 @@ > # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> > > from libtuning.modules.lsc.lsc import LSC > +from libtuning.modules.lsc.mali_c55 import LSCMaliC55 > from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi > from libtuning.modules.lsc.rkisp1 import LSCRkISP1 > diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py > new file mode 100644 > index 00000000..7d85aee9 > --- /dev/null > +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py > @@ -0,0 +1,173 @@ > +# SPDX-License-Identifier: BSD-2-Clause > +# > +# Copyright (C) 2019, Raspberry Pi Ltd > +# Copyright (C) 2024, Ideas on Board oy > +# > +# mali-c55.py - LSC module for tuning mali-c55 > + > +from .lsc import LSC > + > +import libtuning as lt > + > +import numpy as np > + > +class LSCMaliC55(LSC): > + out_name = 'Lsc' > + name = "LSCMaliC55" > + > + def __init__(self, *args, **kwargs): > + super().__init__(**kwargs) > + > + def validate_config(self, config: dict) -> bool: > + # There's nothing we need in config > + return True > + > + def _do_single_lsc(self, image: lt.Image): > + """ > + Generate lens shading tables of gain values for a single image > + > + This function generates a set of lens shading tables - one for > + each colour channel. The table values are gain values, expressed > + generically (I.E. not in the IPA module's format) > + > + Returns a tuple of colour temperature, array of red gain values, > + array of blue gain values, array of red's green gain values and > + array of blue's green gain values. > + """ > + 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() > + ) > + > + def _do_all_lsc(self, images: list) -> dict: > + """ > + Generate lens shading tables in the IPA's format > + > + This function generates lens shading tables from the list of > + images. A set (one per colour channel) of tables is generated > + for each image, and then the sets from all images which were > + generated from the same colour temperature are averaged together > + and transformed into the format required by the IPA module. > + > + Returns a list of dictionaries containing colour temperature, > + red lens shading table, green lens shading table and blue lens > + shading table. > + """ > + > + output_map_func = lt.gradient.Linear().map > + > + list_ct = [] > + > + list_cr = [] > + list_cb = [] > + list_cgr = [] > + list_cgb = [] > + > + for image in images: > + ct, cr, cg, cgr, cgb = self._do_single_lsc(image) > + > + list_ct.append(ct) > + list_cr.append(cr) > + list_cb.append(cg) > + list_cgr.append(cgr) > + list_cgb.append(cgb) > + > + list_ct = np.array(list_ct) > + list_cr = np.array(list_cr) > + list_cb = np.array(list_cb) > + list_cgr = np.array(list_cgr) > + list_cgb = np.array(list_cgb) > + list_cg = (list_cgr + list_cgb) / 2 > + > + # We need to map the gains into the IPA-specific values to pass > + # to the ISP. For the mali-c55 the values are always in the > + # range [0..255] but the min/max that those values represent > + # depend on the mesh scale parameter, so we'll need to choose > + # what that should be and use the gain-range it represents as > + # the domain for output_map_func(). > + # > + # For convenient reference, the possible mesh scale values are > + # as follows (taken from include/linux/mali-c55-config.h) > + # > + # - 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 > + # > + # We want to use the scale with the smallest range that still > + # covers the minimum and maximum value we want to set...but this > + # process at present hard-codes a minimum gain of 1.0, so the > + # first 4 scales are out right away. We'll just consider the > + # minimum as 1.0 for now and if we ever need more than 9.0 gain > + # we'll have to fix this - shout about that if so. > + > + max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb]) > + if (max_gain > 9.0): > + print("WARNING: Maximum gain restricted artificially to 9.0") > + > + mesh_scales = { > + 4: (1.0, 2.0), > + 5: (1.0, 3.0), > + 6: (1.0, 5.0), > + 7: (1.0, 9.0) > + } > + > + for i in mesh_scales.keys(): > + if max_gain <= mesh_scales[i][1]: > + break > + > + mesh_scale = i > + > + output_list = [] > + for ct in sorted(set(list_ct)): > + indices = np.where(list_ct == ct) > + ct = int(ct) > + > + tables = [] > + for lists in [list_cr, list_cg, list_cb]: > + table = np.mean(lists[indices], axis=0) > + > + table = output_map_func( > + ( > + mesh_scales[mesh_scale][0], > + mesh_scales[mesh_scale][1] - 0.001 > + ), > + (0, 255), > + table > + ) > + table = np.round(table).astype('uint8').tolist() > + tables.append(table) > + > + entry = { > + 'ct': ct, > + 'r': tables[0], > + 'g': tables[1], > + 'b': tables[2], > + } > + > + output_list.append(entry) > + > + return { > + 'meshScale': mesh_scale, > + 'sets': output_list > + } > + > + def process(self, config: dict, images: list, outputs: dict) -> dict: > + return self._do_all_lsc(images) > + > diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py > new file mode 100755 > index 00000000..01535366 > --- /dev/null > +++ b/utils/tuning/mali-c55.py > @@ -0,0 +1,40 @@ > +#!/usr/bin/env python3 > +# SPDX-License-Identifier: GPL-2.0-or-later > +# > +# Copyright (C) 2024, Ideas on Board oy > +# > +# mali-c55.py - Tuning script for mali-c55 > + > +import sys > + > +import libtuning as lt > +from libtuning.parsers import YamlParser > +from libtuning.generators import YamlOutput > +from libtuning.modules.lsc import LSCMaliC55 > + > +tuner = lt.Tuner('MaliC55') > +tuner.add(LSCMaliC55( > + 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=(32, 32), > + > + 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.set_input_parser(YamlParser()) > +tuner.set_output_formatter(YamlOutput()) > +tuner.set_output_order([LSCMaliC55]) > + > +if __name__ == '__main__': > + sys.exit(tuner.run(sys.argv)) > -- > 2.34.1 >
Quoting Daniel Scally (2025-03-05 08:12:52) > Add tuning scripts for the mali-c55 IPA. At present the only module > that is available is for lens shading correction. > > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > .../tuning/libtuning/modules/lsc/__init__.py | 1 + > .../tuning/libtuning/modules/lsc/mali_c55.py | 173 ++++++++++++++++++ > utils/tuning/mali-c55.py | 40 ++++ > 3 files changed, 214 insertions(+) > create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py > create mode 100755 utils/tuning/mali-c55.py > > diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py > index 0ba4411b..edd5ce7f 100644 > --- a/utils/tuning/libtuning/modules/lsc/__init__.py > +++ b/utils/tuning/libtuning/modules/lsc/__init__.py > @@ -3,5 +3,6 @@ > # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> > > from libtuning.modules.lsc.lsc import LSC > +from libtuning.modules.lsc.mali_c55 import LSCMaliC55 > from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi > from libtuning.modules.lsc.rkisp1 import LSCRkISP1 > diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py > new file mode 100644 > index 00000000..7d85aee9 > --- /dev/null > +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py > @@ -0,0 +1,173 @@ > +# SPDX-License-Identifier: BSD-2-Clause > +# > +# Copyright (C) 2019, Raspberry Pi Ltd > +# Copyright (C) 2024, Ideas on Board oy > +# > +# mali-c55.py - LSC module for tuning mali-c55 > + > +from .lsc import LSC > + > +import libtuning as lt > + > +import numpy as np > + > +class LSCMaliC55(LSC): > + out_name = 'Lsc' > + name = "LSCMaliC55" > + > + def __init__(self, *args, **kwargs): > + super().__init__(**kwargs) > + > + def validate_config(self, config: dict) -> bool: > + # There's nothing we need in config > + return True > + > + def _do_single_lsc(self, image: lt.Image): > + """ > + Generate lens shading tables of gain values for a single image > + > + This function generates a set of lens shading tables - one for > + each colour channel. The table values are gain values, expressed > + generically (I.E. not in the IPA module's format) > + > + Returns a tuple of colour temperature, array of red gain values, > + array of blue gain values, array of red's green gain values and > + array of blue's green gain values. > + """ > + 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() > + ) This looks exactly the same as rkisp1's... I wonder if it's worth pulling this out into the LSC class. At least _lsc_single_channel is deduplicated :) In any case, out of scope for this series; just me mumbling to myself. > + > + def _do_all_lsc(self, images: list) -> dict: > + """ > + Generate lens shading tables in the IPA's format > + > + This function generates lens shading tables from the list of > + images. A set (one per colour channel) of tables is generated > + for each image, and then the sets from all images which were > + generated from the same colour temperature are averaged together > + and transformed into the format required by the IPA module. > + > + Returns a list of dictionaries containing colour temperature, > + red lens shading table, green lens shading table and blue lens > + shading table. > + """ > + > + output_map_func = lt.gradient.Linear().map > + > + list_ct = [] > + > + list_cr = [] > + list_cb = [] > + list_cgr = [] > + list_cgb = [] > + > + for image in images: > + ct, cr, cg, cgr, cgb = self._do_single_lsc(image) > + > + list_ct.append(ct) > + list_cr.append(cr) > + list_cb.append(cg) > + list_cgr.append(cgr) > + list_cgb.append(cgb) > + > + list_ct = np.array(list_ct) > + list_cr = np.array(list_cr) > + list_cb = np.array(list_cb) > + list_cgr = np.array(list_cgr) > + list_cgb = np.array(list_cgb) > + list_cg = (list_cgr + list_cgb) / 2 > + > + # We need to map the gains into the IPA-specific values to pass > + # to the ISP. For the mali-c55 the values are always in the > + # range [0..255] but the min/max that those values represent > + # depend on the mesh scale parameter, so we'll need to choose > + # what that should be and use the gain-range it represents as > + # the domain for output_map_func(). > + # > + # For convenient reference, the possible mesh scale values are > + # as follows (taken from include/linux/mali-c55-config.h) > + # > + # - 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 > + # > + # We want to use the scale with the smallest range that still > + # covers the minimum and maximum value we want to set...but this > + # process at present hard-codes a minimum gain of 1.0, so the > + # first 4 scales are out right away. We'll just consider the > + # minimum as 1.0 for now and if we ever need more than 9.0 gain > + # we'll have to fix this - shout about that if so. > + > + max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb]) > + if (max_gain > 9.0): > + print("WARNING: Maximum gain restricted artificially to 9.0") > + > + mesh_scales = { > + 4: (1.0, 2.0), > + 5: (1.0, 3.0), > + 6: (1.0, 5.0), > + 7: (1.0, 9.0) > + } > + > + for i in mesh_scales.keys(): > + if max_gain <= mesh_scales[i][1]: > + break > + > + mesh_scale = i > + > + output_list = [] > + for ct in sorted(set(list_ct)): > + indices = np.where(list_ct == ct) > + ct = int(ct) > + > + tables = [] > + for lists in [list_cr, list_cg, list_cb]: > + table = np.mean(lists[indices], axis=0) > + > + table = output_map_func( > + ( > + mesh_scales[mesh_scale][0], > + mesh_scales[mesh_scale][1] - 0.001 > + ), > + (0, 255), > + table > + ) > + table = np.round(table).astype('uint8').tolist() > + tables.append(table) > + > + entry = { > + 'ct': ct, > + 'r': tables[0], > + 'g': tables[1], > + 'b': tables[2], > + } > + > + output_list.append(entry) > + > + return { > + 'meshScale': mesh_scale, > + 'sets': output_list > + } > + > + def process(self, config: dict, images: list, outputs: dict) -> dict: > + return self._do_all_lsc(images) > + > diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py > new file mode 100755 > index 00000000..01535366 > --- /dev/null > +++ b/utils/tuning/mali-c55.py > @@ -0,0 +1,40 @@ > +#!/usr/bin/env python3 > +# SPDX-License-Identifier: GPL-2.0-or-later > +# > +# Copyright (C) 2024, Ideas on Board oy > +# > +# mali-c55.py - Tuning script for mali-c55 > + > +import sys > + > +import libtuning as lt > +from libtuning.parsers import YamlParser > +from libtuning.generators import YamlOutput > +from libtuning.modules.lsc import LSCMaliC55 > + > +tuner = lt.Tuner('MaliC55') > +tuner.add(LSCMaliC55( > + 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. rkisp1 doesn't look relevant to mali-c55 :) With that removed (or changed to fit), looks good to me. Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > + sector_shape=(32, 32), > + > + 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.set_input_parser(YamlParser()) > +tuner.set_output_formatter(YamlOutput()) > +tuner.set_output_order([LSCMaliC55]) > + > +if __name__ == '__main__': > + sys.exit(tuner.run(sys.argv)) > -- > 2.34.1 >
Hi Paul On 22/07/2025 11:16, Paul Elder wrote: > Quoting Daniel Scally (2025-03-05 08:12:52) >> Add tuning scripts for the mali-c55 IPA. At present the only module >> that is available is for lens shading correction. >> >> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> >> --- >> .../tuning/libtuning/modules/lsc/__init__.py | 1 + >> .../tuning/libtuning/modules/lsc/mali_c55.py | 173 ++++++++++++++++++ >> utils/tuning/mali-c55.py | 40 ++++ >> 3 files changed, 214 insertions(+) >> create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py >> create mode 100755 utils/tuning/mali-c55.py >> >> diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py >> index 0ba4411b..edd5ce7f 100644 >> --- a/utils/tuning/libtuning/modules/lsc/__init__.py >> +++ b/utils/tuning/libtuning/modules/lsc/__init__.py >> @@ -3,5 +3,6 @@ >> # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> >> >> from libtuning.modules.lsc.lsc import LSC >> +from libtuning.modules.lsc.mali_c55 import LSCMaliC55 >> from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi >> from libtuning.modules.lsc.rkisp1 import LSCRkISP1 >> diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py >> new file mode 100644 >> index 00000000..7d85aee9 >> --- /dev/null >> +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py >> @@ -0,0 +1,173 @@ >> +# SPDX-License-Identifier: BSD-2-Clause >> +# >> +# Copyright (C) 2019, Raspberry Pi Ltd >> +# Copyright (C) 2024, Ideas on Board oy >> +# >> +# mali-c55.py - LSC module for tuning mali-c55 >> + >> +from .lsc import LSC >> + >> +import libtuning as lt >> + >> +import numpy as np >> + >> +class LSCMaliC55(LSC): >> + out_name = 'Lsc' >> + name = "LSCMaliC55" >> + >> + def __init__(self, *args, **kwargs): >> + super().__init__(**kwargs) >> + >> + def validate_config(self, config: dict) -> bool: >> + # There's nothing we need in config >> + return True >> + >> + def _do_single_lsc(self, image: lt.Image): >> + """ >> + Generate lens shading tables of gain values for a single image >> + >> + This function generates a set of lens shading tables - one for >> + each colour channel. The table values are gain values, expressed >> + generically (I.E. not in the IPA module's format) >> + >> + Returns a tuple of colour temperature, array of red gain values, >> + array of blue gain values, array of red's green gain values and >> + array of blue's green gain values. >> + """ >> + 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() >> + ) > This looks exactly the same as rkisp1's... I wonder if it's worth pulling this > out into the LSC class. At least _lsc_single_channel is deduplicated :) > > In any case, out of scope for this series; just me mumbling to myself. > >> + >> + def _do_all_lsc(self, images: list) -> dict: >> + """ >> + Generate lens shading tables in the IPA's format >> + >> + This function generates lens shading tables from the list of >> + images. A set (one per colour channel) of tables is generated >> + for each image, and then the sets from all images which were >> + generated from the same colour temperature are averaged together >> + and transformed into the format required by the IPA module. >> + >> + Returns a list of dictionaries containing colour temperature, >> + red lens shading table, green lens shading table and blue lens >> + shading table. >> + """ >> + >> + output_map_func = lt.gradient.Linear().map >> + >> + list_ct = [] >> + >> + list_cr = [] >> + list_cb = [] >> + list_cgr = [] >> + list_cgb = [] >> + >> + for image in images: >> + ct, cr, cg, cgr, cgb = self._do_single_lsc(image) >> + >> + list_ct.append(ct) >> + list_cr.append(cr) >> + list_cb.append(cg) >> + list_cgr.append(cgr) >> + list_cgb.append(cgb) >> + >> + list_ct = np.array(list_ct) >> + list_cr = np.array(list_cr) >> + list_cb = np.array(list_cb) >> + list_cgr = np.array(list_cgr) >> + list_cgb = np.array(list_cgb) >> + list_cg = (list_cgr + list_cgb) / 2 >> + >> + # We need to map the gains into the IPA-specific values to pass >> + # to the ISP. For the mali-c55 the values are always in the >> + # range [0..255] but the min/max that those values represent >> + # depend on the mesh scale parameter, so we'll need to choose >> + # what that should be and use the gain-range it represents as >> + # the domain for output_map_func(). >> + # >> + # For convenient reference, the possible mesh scale values are >> + # as follows (taken from include/linux/mali-c55-config.h) >> + # >> + # - 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 >> + # >> + # We want to use the scale with the smallest range that still >> + # covers the minimum and maximum value we want to set...but this >> + # process at present hard-codes a minimum gain of 1.0, so the >> + # first 4 scales are out right away. We'll just consider the >> + # minimum as 1.0 for now and if we ever need more than 9.0 gain >> + # we'll have to fix this - shout about that if so. >> + >> + max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb]) >> + if (max_gain > 9.0): >> + print("WARNING: Maximum gain restricted artificially to 9.0") >> + >> + mesh_scales = { >> + 4: (1.0, 2.0), >> + 5: (1.0, 3.0), >> + 6: (1.0, 5.0), >> + 7: (1.0, 9.0) >> + } >> + >> + for i in mesh_scales.keys(): >> + if max_gain <= mesh_scales[i][1]: >> + break >> + >> + mesh_scale = i >> + >> + output_list = [] >> + for ct in sorted(set(list_ct)): >> + indices = np.where(list_ct == ct) >> + ct = int(ct) >> + >> + tables = [] >> + for lists in [list_cr, list_cg, list_cb]: >> + table = np.mean(lists[indices], axis=0) >> + >> + table = output_map_func( >> + ( >> + mesh_scales[mesh_scale][0], >> + mesh_scales[mesh_scale][1] - 0.001 >> + ), >> + (0, 255), >> + table >> + ) >> + table = np.round(table).astype('uint8').tolist() >> + tables.append(table) >> + >> + entry = { >> + 'ct': ct, >> + 'r': tables[0], >> + 'g': tables[1], >> + 'b': tables[2], >> + } >> + >> + output_list.append(entry) >> + >> + return { >> + 'meshScale': mesh_scale, >> + 'sets': output_list >> + } >> + >> + def process(self, config: dict, images: list, outputs: dict) -> dict: >> + return self._do_all_lsc(images) >> + >> diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py >> new file mode 100755 >> index 00000000..01535366 >> --- /dev/null >> +++ b/utils/tuning/mali-c55.py >> @@ -0,0 +1,40 @@ >> +#!/usr/bin/env python3 >> +# SPDX-License-Identifier: GPL-2.0-or-later >> +# >> +# Copyright (C) 2024, Ideas on Board oy >> +# >> +# mali-c55.py - Tuning script for mali-c55 >> + >> +import sys >> + >> +import libtuning as lt >> +from libtuning.parsers import YamlParser >> +from libtuning.generators import YamlOutput >> +from libtuning.modules.lsc import LSCMaliC55 >> + >> +tuner = lt.Tuner('MaliC55') >> +tuner.add(LSCMaliC55( >> + 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. > rkisp1 doesn't look relevant to mali-c55 :) Oops, I'll fix that > > > With that removed (or changed to fit), looks good to me. > > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Thanks! > >> + sector_shape=(32, 32), >> + >> + 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.set_input_parser(YamlParser()) >> +tuner.set_output_formatter(YamlOutput()) >> +tuner.set_output_order([LSCMaliC55]) >> + >> +if __name__ == '__main__': >> + sys.exit(tuner.run(sys.argv)) >> -- >> 2.34.1 >>
diff --git a/utils/tuning/libtuning/modules/lsc/__init__.py b/utils/tuning/libtuning/modules/lsc/__init__.py index 0ba4411b..edd5ce7f 100644 --- a/utils/tuning/libtuning/modules/lsc/__init__.py +++ b/utils/tuning/libtuning/modules/lsc/__init__.py @@ -3,5 +3,6 @@ # Copyright (C) 2022, Paul Elder <paul.elder@ideasonboard.com> from libtuning.modules.lsc.lsc import LSC +from libtuning.modules.lsc.mali_c55 import LSCMaliC55 from libtuning.modules.lsc.raspberrypi import ALSCRaspberryPi from libtuning.modules.lsc.rkisp1 import LSCRkISP1 diff --git a/utils/tuning/libtuning/modules/lsc/mali_c55.py b/utils/tuning/libtuning/modules/lsc/mali_c55.py new file mode 100644 index 00000000..7d85aee9 --- /dev/null +++ b/utils/tuning/libtuning/modules/lsc/mali_c55.py @@ -0,0 +1,173 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2024, Ideas on Board oy +# +# mali-c55.py - LSC module for tuning mali-c55 + +from .lsc import LSC + +import libtuning as lt + +import numpy as np + +class LSCMaliC55(LSC): + out_name = 'Lsc' + name = "LSCMaliC55" + + def __init__(self, *args, **kwargs): + super().__init__(**kwargs) + + def validate_config(self, config: dict) -> bool: + # There's nothing we need in config + return True + + def _do_single_lsc(self, image: lt.Image): + """ + Generate lens shading tables of gain values for a single image + + This function generates a set of lens shading tables - one for + each colour channel. The table values are gain values, expressed + generically (I.E. not in the IPA module's format) + + Returns a tuple of colour temperature, array of red gain values, + array of blue gain values, array of red's green gain values and + array of blue's green gain values. + """ + 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() + ) + + def _do_all_lsc(self, images: list) -> dict: + """ + Generate lens shading tables in the IPA's format + + This function generates lens shading tables from the list of + images. A set (one per colour channel) of tables is generated + for each image, and then the sets from all images which were + generated from the same colour temperature are averaged together + and transformed into the format required by the IPA module. + + Returns a list of dictionaries containing colour temperature, + red lens shading table, green lens shading table and blue lens + shading table. + """ + + output_map_func = lt.gradient.Linear().map + + list_ct = [] + + list_cr = [] + list_cb = [] + list_cgr = [] + list_cgb = [] + + for image in images: + ct, cr, cg, cgr, cgb = self._do_single_lsc(image) + + list_ct.append(ct) + list_cr.append(cr) + list_cb.append(cg) + list_cgr.append(cgr) + list_cgb.append(cgb) + + list_ct = np.array(list_ct) + list_cr = np.array(list_cr) + list_cb = np.array(list_cb) + list_cgr = np.array(list_cgr) + list_cgb = np.array(list_cgb) + list_cg = (list_cgr + list_cgb) / 2 + + # We need to map the gains into the IPA-specific values to pass + # to the ISP. For the mali-c55 the values are always in the + # range [0..255] but the min/max that those values represent + # depend on the mesh scale parameter, so we'll need to choose + # what that should be and use the gain-range it represents as + # the domain for output_map_func(). + # + # For convenient reference, the possible mesh scale values are + # as follows (taken from include/linux/mali-c55-config.h) + # + # - 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 + # + # We want to use the scale with the smallest range that still + # covers the minimum and maximum value we want to set...but this + # process at present hard-codes a minimum gain of 1.0, so the + # first 4 scales are out right away. We'll just consider the + # minimum as 1.0 for now and if we ever need more than 9.0 gain + # we'll have to fix this - shout about that if so. + + max_gain = np.max([list_cr, list_cgr, list_cgb, list_cb]) + if (max_gain > 9.0): + print("WARNING: Maximum gain restricted artificially to 9.0") + + mesh_scales = { + 4: (1.0, 2.0), + 5: (1.0, 3.0), + 6: (1.0, 5.0), + 7: (1.0, 9.0) + } + + for i in mesh_scales.keys(): + if max_gain <= mesh_scales[i][1]: + break + + mesh_scale = i + + output_list = [] + for ct in sorted(set(list_ct)): + indices = np.where(list_ct == ct) + ct = int(ct) + + tables = [] + for lists in [list_cr, list_cg, list_cb]: + table = np.mean(lists[indices], axis=0) + + table = output_map_func( + ( + mesh_scales[mesh_scale][0], + mesh_scales[mesh_scale][1] - 0.001 + ), + (0, 255), + table + ) + table = np.round(table).astype('uint8').tolist() + tables.append(table) + + entry = { + 'ct': ct, + 'r': tables[0], + 'g': tables[1], + 'b': tables[2], + } + + output_list.append(entry) + + return { + 'meshScale': mesh_scale, + 'sets': output_list + } + + def process(self, config: dict, images: list, outputs: dict) -> dict: + return self._do_all_lsc(images) + diff --git a/utils/tuning/mali-c55.py b/utils/tuning/mali-c55.py new file mode 100755 index 00000000..01535366 --- /dev/null +++ b/utils/tuning/mali-c55.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas on Board oy +# +# mali-c55.py - Tuning script for mali-c55 + +import sys + +import libtuning as lt +from libtuning.parsers import YamlParser +from libtuning.generators import YamlOutput +from libtuning.modules.lsc import LSCMaliC55 + +tuner = lt.Tuner('MaliC55') +tuner.add(LSCMaliC55( + 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=(32, 32), + + 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.set_input_parser(YamlParser()) +tuner.set_output_formatter(YamlOutput()) +tuner.set_output_order([LSCMaliC55]) + +if __name__ == '__main__': + sys.exit(tuner.run(sys.argv))
Add tuning scripts for the mali-c55 IPA. At present the only module that is available is for lens shading correction. Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> --- .../tuning/libtuning/modules/lsc/__init__.py | 1 + .../tuning/libtuning/modules/lsc/mali_c55.py | 173 ++++++++++++++++++ utils/tuning/mali-c55.py | 40 ++++ 3 files changed, 214 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lsc/mali_c55.py create mode 100755 utils/tuning/mali-c55.py