[5/6] utils: raspberrypi: ctt: Add option to convert between vc4/pisp targets
diff mbox series

Message ID 20240606101512.375178-6-david.plowman@raspberrypi.com
State Accepted
Headers show
Series
  • Raspberry Pi Camera Tuning Tool updates
Related show

Commit Message

David Plowman June 6, 2024, 10:15 a.m. UTC
From: Naushir Patuck <naush@raspberrypi.com>

This change adds functionality to the convert_tuning.py script to
convert between vc4 and pisp target tuning files. The conversion is
done on a best effort basis, and should provide functional tuning files.
However, a full tuning for the target platform is always preferred.

Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
---
 utils/raspberrypi/ctt/convert_tuning.py | 98 ++++++++++++++++++++++---
 1 file changed, 86 insertions(+), 12 deletions(-)

Comments

Kieran Bingham June 13, 2024, 8:24 a.m. UTC | #1
Quoting David Plowman (2024-06-06 11:15:11)
> From: Naushir Patuck <naush@raspberrypi.com>
> 
> This change adds functionality to the convert_tuning.py script to
> convert between vc4 and pisp target tuning files. The conversion is
> done on a best effort basis, and should provide functional tuning files.
> However, a full tuning for the target platform is always preferred.
> 
> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>

Hi David,

Can I assume you have supplied your SoB and RB tag to this one already?

--
Kieran


> ---
>  utils/raspberrypi/ctt/convert_tuning.py | 98 ++++++++++++++++++++++---
>  1 file changed, 86 insertions(+), 12 deletions(-)
> 
> diff --git a/utils/raspberrypi/ctt/convert_tuning.py b/utils/raspberrypi/ctt/convert_tuning.py
> index f4504d45..83cf69d4 100755
> --- a/utils/raspberrypi/ctt/convert_tuning.py
> +++ b/utils/raspberrypi/ctt/convert_tuning.py
> @@ -8,30 +8,104 @@
>  
>  import argparse
>  import json
> +import numpy as np
>  import sys
>  
>  from ctt_pretty_print_json import pretty_print
> +from ctt_pisp import grid_size as grid_size_pisp
> +from ctt_pisp import json_template as json_template_pisp
> +from ctt_vc4 import grid_size as grid_size_vc4
> +from ctt_vc4 import json_template as json_template_vc4
>  
>  
> -def convert_v2(in_json: dict) -> str:
> +def interp_2d(in_ls, src_w, src_h, dst_w, dst_h):
>  
> -    if 'version' in in_json.keys() and in_json['version'] != 1.0:
> -        print(f'The JSON config reports version {in_json["version"]} that is incompatible with this tool.')
> -        sys.exit(-1)
> +    out_ls = np.zeros((dst_h, dst_w))
> +    for i in range(src_h):
> +        out_ls[i] = np.interp(np.linspace(0, dst_w - 1, dst_w),
> +                              np.linspace(0, dst_w - 1, src_w),
> +                              in_ls[i])
> +    for i in range(dst_w):
> +        out_ls[:,i] = np.interp(np.linspace(0, dst_h - 1, dst_h),
> +                                np.linspace(0, dst_h - 1, src_h),
> +                                out_ls[:src_h, i])
> +    return out_ls
>  
> -    converted = {
> -        'version': 2.0,
> -        'target': 'bcm2835',
> -        'algorithms': [{algo: config} for algo, config in in_json.items()]
> -    }
>  
> -    return pretty_print(converted)
> +def convert_target(in_json: dict, target: str):
> +
> +    src_w, src_h = grid_size_pisp if target == 'vc4' else grid_size_vc4
> +    dst_w, dst_h = grid_size_vc4 if target == 'vc4' else grid_size_pisp
> +    json_template = json_template_vc4 if target == 'vc4' else json_template_pisp
> +
> +    # ALSC grid sizes
> +    alsc = next(algo for algo in in_json['algorithms'] if 'rpi.alsc' in algo)['rpi.alsc']
> +    for colour in ['calibrations_Cr', 'calibrations_Cb']:
> +        if colour not in alsc:
> +            continue
> +        for temperature in alsc[colour]:
> +            in_ls = np.reshape(temperature['table'], (src_h, src_w))
> +            out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h)
> +            temperature['table'] = np.round(out_ls.flatten(), 3).tolist()
> +
> +    if 'luminance_lut' in alsc:
> +        in_ls = np.reshape(alsc['luminance_lut'], (src_h, src_w))
> +        out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h)
> +        alsc['luminance_lut'] = np.round(out_ls.flatten(), 3).tolist()
> +
> +    # Denoise blocks
> +    for i, algo in enumerate(in_json['algorithms']):
> +        if list(algo.keys())[0] == 'rpi.sdn':
> +            in_json['algorithms'][i] = {'rpi.denoise': json_template['rpi.sdn'] if target == 'vc4' else json_template['rpi.denoise']}
> +            break
> +
> +    # AGC mode weights
> +    agc = next(algo for algo in in_json['algorithms'] if 'rpi.agc' in algo)['rpi.agc']
> +    if 'channels' in agc:
> +        for i, channel in enumerate(agc['channels']):
> +            target_agc_metering = json_template['rpi.agc']['channels'][i]['metering_modes']
> +            for mode, v in channel['metering_modes'].items():
> +                v['weights'] = target_agc_metering[mode]['weights']
> +    else:
> +        for mode, v in agc["metering_modes"].items():
> +            target_agc_metering = json_template['rpi.agc']['channels'][0]['metering_modes']
> +            v['weights'] = target_agc_metering[mode]['weights']
> +
> +    # HDR
> +    if target == 'pisp':
> +        for i, algo in enumerate(in_json['algorithms']):
> +            if list(algo.keys())[0] == 'rpi.hdr':
> +                in_json['algorithms'][i] = {'rpi.hdr': json_template['rpi.hdr']}
> +
> +    return in_json
> +
> +
> +def convert_v2(in_json: dict, target: str) -> str:
> +
> +    if 'version' in in_json.keys() and in_json['version'] == 1.0:
> +        converted = {
> +            'version': 2.0,
> +            'target': target,
> +            'algorithms': [{algo: config} for algo, config in in_json.items()]
> +        }
> +    else:
> +        converted = in_json
> +
> +    # Convert between vc4 <-> pisp targets. This is a best effort thing.
> +    if converted['target'] != target:
> +        converted = convert_target(converted, target)
> +        converted['target'] = target
> +
> +    grid_size = grid_size_vc4[0] if target == 'vc4' else grid_size_pisp[0]
> +    return pretty_print(converted, custom_elems={'table': grid_size, 'luminance_lut': grid_size})
>  
>  
>  if __name__ == "__main__":
>      parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=
> -                    'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0.\n')
> +        'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0 and/or the vc4 <-> pisp targets.\n')
>      parser.add_argument('input', type=str, help='Input tuning file.')
> +    parser.add_argument('-t', '--target', type=str, help='Target platform.',
> +                        choices=['pisp', 'vc4'], default='vc4')
>      parser.add_argument('output', type=str, nargs='?',
>                          help='Output converted tuning file. If not provided, the input file will be updated in-place.',
>                          default=None)
> @@ -40,7 +114,7 @@ if __name__ == "__main__":
>      with open(args.input, 'r') as f:
>          in_json = json.load(f)
>  
> -    out_json = convert_v2(in_json)
> +    out_json = convert_v2(in_json, args.target)
>  
>      with open(args.output if args.output is not None else args.input, 'w') as f:
>          f.write(out_json)
> -- 
> 2.39.2
>

Patch
diff mbox series

diff --git a/utils/raspberrypi/ctt/convert_tuning.py b/utils/raspberrypi/ctt/convert_tuning.py
index f4504d45..83cf69d4 100755
--- a/utils/raspberrypi/ctt/convert_tuning.py
+++ b/utils/raspberrypi/ctt/convert_tuning.py
@@ -8,30 +8,104 @@ 
 
 import argparse
 import json
+import numpy as np
 import sys
 
 from ctt_pretty_print_json import pretty_print
+from ctt_pisp import grid_size as grid_size_pisp
+from ctt_pisp import json_template as json_template_pisp
+from ctt_vc4 import grid_size as grid_size_vc4
+from ctt_vc4 import json_template as json_template_vc4
 
 
-def convert_v2(in_json: dict) -> str:
+def interp_2d(in_ls, src_w, src_h, dst_w, dst_h):
 
-    if 'version' in in_json.keys() and in_json['version'] != 1.0:
-        print(f'The JSON config reports version {in_json["version"]} that is incompatible with this tool.')
-        sys.exit(-1)
+    out_ls = np.zeros((dst_h, dst_w))
+    for i in range(src_h):
+        out_ls[i] = np.interp(np.linspace(0, dst_w - 1, dst_w),
+                              np.linspace(0, dst_w - 1, src_w),
+                              in_ls[i])
+    for i in range(dst_w):
+        out_ls[:,i] = np.interp(np.linspace(0, dst_h - 1, dst_h),
+                                np.linspace(0, dst_h - 1, src_h),
+                                out_ls[:src_h, i])
+    return out_ls
 
-    converted = {
-        'version': 2.0,
-        'target': 'bcm2835',
-        'algorithms': [{algo: config} for algo, config in in_json.items()]
-    }
 
-    return pretty_print(converted)
+def convert_target(in_json: dict, target: str):
+
+    src_w, src_h = grid_size_pisp if target == 'vc4' else grid_size_vc4
+    dst_w, dst_h = grid_size_vc4 if target == 'vc4' else grid_size_pisp
+    json_template = json_template_vc4 if target == 'vc4' else json_template_pisp
+
+    # ALSC grid sizes
+    alsc = next(algo for algo in in_json['algorithms'] if 'rpi.alsc' in algo)['rpi.alsc']
+    for colour in ['calibrations_Cr', 'calibrations_Cb']:
+        if colour not in alsc:
+            continue
+        for temperature in alsc[colour]:
+            in_ls = np.reshape(temperature['table'], (src_h, src_w))
+            out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h)
+            temperature['table'] = np.round(out_ls.flatten(), 3).tolist()
+
+    if 'luminance_lut' in alsc:
+        in_ls = np.reshape(alsc['luminance_lut'], (src_h, src_w))
+        out_ls = interp_2d(in_ls, src_w, src_h, dst_w, dst_h)
+        alsc['luminance_lut'] = np.round(out_ls.flatten(), 3).tolist()
+
+    # Denoise blocks
+    for i, algo in enumerate(in_json['algorithms']):
+        if list(algo.keys())[0] == 'rpi.sdn':
+            in_json['algorithms'][i] = {'rpi.denoise': json_template['rpi.sdn'] if target == 'vc4' else json_template['rpi.denoise']}
+            break
+
+    # AGC mode weights
+    agc = next(algo for algo in in_json['algorithms'] if 'rpi.agc' in algo)['rpi.agc']
+    if 'channels' in agc:
+        for i, channel in enumerate(agc['channels']):
+            target_agc_metering = json_template['rpi.agc']['channels'][i]['metering_modes']
+            for mode, v in channel['metering_modes'].items():
+                v['weights'] = target_agc_metering[mode]['weights']
+    else:
+        for mode, v in agc["metering_modes"].items():
+            target_agc_metering = json_template['rpi.agc']['channels'][0]['metering_modes']
+            v['weights'] = target_agc_metering[mode]['weights']
+
+    # HDR
+    if target == 'pisp':
+        for i, algo in enumerate(in_json['algorithms']):
+            if list(algo.keys())[0] == 'rpi.hdr':
+                in_json['algorithms'][i] = {'rpi.hdr': json_template['rpi.hdr']}
+
+    return in_json
+
+
+def convert_v2(in_json: dict, target: str) -> str:
+
+    if 'version' in in_json.keys() and in_json['version'] == 1.0:
+        converted = {
+            'version': 2.0,
+            'target': target,
+            'algorithms': [{algo: config} for algo, config in in_json.items()]
+        }
+    else:
+        converted = in_json
+
+    # Convert between vc4 <-> pisp targets. This is a best effort thing.
+    if converted['target'] != target:
+        converted = convert_target(converted, target)
+        converted['target'] = target
+
+    grid_size = grid_size_vc4[0] if target == 'vc4' else grid_size_pisp[0]
+    return pretty_print(converted, custom_elems={'table': grid_size, 'luminance_lut': grid_size})
 
 
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=
-                    'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0.\n')
+        'Convert the format of the Raspberry Pi camera tuning file from v1.0 to v2.0 and/or the vc4 <-> pisp targets.\n')
     parser.add_argument('input', type=str, help='Input tuning file.')
+    parser.add_argument('-t', '--target', type=str, help='Target platform.',
+                        choices=['pisp', 'vc4'], default='vc4')
     parser.add_argument('output', type=str, nargs='?',
                         help='Output converted tuning file. If not provided, the input file will be updated in-place.',
                         default=None)
@@ -40,7 +114,7 @@  if __name__ == "__main__":
     with open(args.input, 'r') as f:
         in_json = json.load(f)
 
-    out_json = convert_v2(in_json)
+    out_json = convert_v2(in_json, args.target)
 
     with open(args.output if args.output is not None else args.input, 'w') as f:
         f.write(out_json)