[6/7] utils/tuning: Add LSC scripts
diff mbox series

Message ID 20260126104256.119697-7-rick.w.ten.wolde@gmail.com
State New
Headers show
Series
  • LSC for SoftISP simple pipeline
Related show

Commit Message

Rick ten Wolde Jan. 26, 2026, 10:42 a.m. UTC
From: Derek Gielen <derekgielen@outlook.com>

Add script for the conversion of the tuning file to a format usable by
the lens shading correction shader.

Add script for visualizing the lens shading correction using pyplot.

Co-authored-by: Aron Dosti <aron-dosti@hotmail.com>
Signed-off-by: Aron Dosti <aron-dosti@hotmail.com>
Signed-off-by: Derek Gielen <derekgielen@outlook.com>
---
 utils/tuning/exportTuningToLscShader.py | 120 ++++++++++++++++++++++++
 utils/tuning/generate_lsc_map_plot.py   |  76 +++++++++++++++
 2 files changed, 196 insertions(+)
 create mode 100644 utils/tuning/exportTuningToLscShader.py
 create mode 100644 utils/tuning/generate_lsc_map_plot.py

Patch
diff mbox series

diff --git a/utils/tuning/exportTuningToLscShader.py b/utils/tuning/exportTuningToLscShader.py
new file mode 100644
index 00000000..4f07aa1e
--- /dev/null
+++ b/utils/tuning/exportTuningToLscShader.py
@@ -0,0 +1,120 @@ 
+import yaml
+import numpy as np
+import argparse
+import sys
+
+# --- Configuration ---
+GRID_W = 16
+GRID_H = 16
+
+# Formula constants
+BLACK_LEVEL = 1024.0
+SCALE_FACTOR = 3071  # Divisor to map the range to 0-255
+
+def load_and_process_yaml(input_filename, target_ct):
+    # 1. Load the YAML file
+    try:
+        with open(input_filename, 'r') as f:
+            data = yaml.safe_load(f)
+    except FileNotFoundError:
+        print(f"Error: Input file '{input_filename}' not found.")
+        return None, None, None
+
+    # 2. Find the LensShadingCorrection block
+    lsc_data = None
+    for algo in data['algorithms']:
+        if 'LensShadingCorrection' in algo:
+            lsc_data = algo['LensShadingCorrection']
+            break
+
+    if not lsc_data:
+        print("Error: LensShadingCorrection block not found.")
+        return None, None, None
+
+    # 3. Extract the set for the specific Color Temperature (CT) provided in arguments
+    sets = lsc_data['sets']
+    target_set = next((item for item in sets if item['ct'] == target_ct), None)
+
+    if not target_set:
+        print(f"Error: Set for Color Temperature {target_ct} not found in '{input_filename}'.")
+        return None, None, None
+
+    print(f"Found data for CT {target_ct}. Applying formula: (x - {int(BLACK_LEVEL)}) / {SCALE_FACTOR} ...")
+
+    # 4. Get Raw Data
+    r_raw = np.array(target_set['r'])
+    b_raw = np.array(target_set['b'])
+    gr_raw = np.array(target_set['gr'])
+    gb_raw = np.array(target_set['gb'])
+
+    # Calculate Green Channel (Average of GR and GB)
+    g_raw = (gr_raw + gb_raw) / 2.0
+
+    # 5. Define the calculation logic
+    def apply_formula(data_array):
+        """
+        Applies the specific user formula:
+        1. Subtract Black Level (1024)
+        2. Divide by the Scale Factor (3071)
+        3. Multiply by 255 and convert to integer
+        """
+        result = ((data_array - BLACK_LEVEL) / SCALE_FACTOR) * 255
+        return result.astype(int)
+
+    # 6. Apply calculation to all channels
+    r_final = apply_formula(r_raw)
+    g_final = apply_formula(g_raw)
+    b_final = apply_formula(b_raw)
+
+    return r_final, g_final, b_final
+
+def save_custom_grid_yaml(output_filename, r, g, b, target_ct):
+
+    # Helper function to format the array as a visual grid string
+    def format_array_as_grid_string(arr):
+        lines = []
+        # Loop through the array in chunks of 16 (GRID_W)
+        for i in range(0, len(arr), GRID_W):
+            row = arr[i:i+GRID_W]
+            # Join numbers with commas
+            row_str = ", ".join(map(str, row))
+            lines.append(f"    {row_str}")
+        # Wrap in brackets to form a valid YAML list, but visually formatted
+        return "[\n" + ",\n".join(lines) + "\n  ]"
+
+    # Write the file manually to ensure specific formatting
+    with open(output_filename, 'w') as f:
+        f.write(f"description: 'LSC Fixed Formula ((x-{int(BLACK_LEVEL)})/{SCALE_FACTOR})'\n")
+        f.write(f"source_ct: {target_ct}\n")
+        f.write(f"grid_size: [{GRID_W}, {GRID_H}]\n")
+        f.write(f"formula_used: '(RawValue - {int(BLACK_LEVEL)}) / {SCALE_FACTOR} -> [0..255]'\n")
+        f.write(f"channels:\n")
+
+        f.write("  red: " + format_array_as_grid_string(r) + "\n")
+        f.write("  green: " + format_array_as_grid_string(g) + "\n")
+        f.write("  blue: " + format_array_as_grid_string(b) + "\n")
+
+    print(f"Success! Saved formatted grid to '{output_filename}'")
+
+# --- Main Execution ---
+if __name__ == "__main__":
+    # 1. Setup Argument Parser
+    parser = argparse.ArgumentParser(description="Convert LSC YAML data to shader grid format.")
+    parser.add_argument("ct", type=int, help="The Color Temperature to process (e.g. 2700, 5000, 6500)")
+
+    # 2. Parse arguments
+    args = parser.parse_args()
+    ct_val = args.ct
+
+    # 3. Construct filenames based on the CT value
+    # Assumes input file is named 'tuning_XXXX.yaml'
+    input_file = f'tuning{ct_val}.yaml'
+    output_file = f'lsc_shader_16x16_{ct_val}_fixed.yaml'
+
+    print(f"--- Processing for Color Temp: {ct_val} ---")
+
+    # 4. Run Process
+    r, g, b = load_and_process_yaml(input_file, ct_val)
+
+    if r is not None:
+        save_custom_grid_yaml(output_file, r, g, b, ct_val)
diff --git a/utils/tuning/generate_lsc_map_plot.py b/utils/tuning/generate_lsc_map_plot.py
new file mode 100644
index 00000000..67b3a041
--- /dev/null
+++ b/utils/tuning/generate_lsc_map_plot.py
@@ -0,0 +1,76 @@ 
+import yaml
+import numpy as np
+import matplotlib.pyplot as plt
+
+# 1. Load the data
+try:
+    with open('tuning2700.yaml', 'r') as f:
+        data = yaml.safe_load(f)
+except FileNotFoundError:
+    print("Error: 'tuning.yaml' not found. Please ensure the file exists.")
+    exit()
+
+# 2. Find LensShadingCorrection safely
+lsc_data = None
+for algo in data['algorithms']:
+    if 'LensShadingCorrection' in algo:
+        lsc_data = algo['LensShadingCorrection']
+        break
+
+if not lsc_data:
+    print("Error: LensShadingCorrection block not found in YAML.")
+    exit()
+
+# 3. Extract the set for disirable Kelvin
+kelvin = 2700
+sets = lsc_data['sets']
+target_set = next((item for item in sets if item['ct'] == kelvin), None)
+
+if not target_set:
+    print("Error: CT 6500 not found in sets.")
+    exit()
+
+# 4. Get lists and normalize (1024 = 1.0 gain)
+r_list = np.array(target_set['r'])
+gr_list = np.array(target_set['gr'])
+gb_list = np.array(target_set['gb'])
+b_list = np.array(target_set['b'])
+
+r_norm = r_list / 1024.0
+b_norm = b_list / 1024.0
+# Average the two greens for the shader
+g_norm = (gr_list + gb_list) / 2.0 / 1024.0
+
+# 5. Reshape into 17x17 Grids
+grid_size = (17, 17)
+r_grid = r_norm.reshape(grid_size)
+g_grid = g_norm.reshape(grid_size)
+b_grid = b_norm.reshape(grid_size)
+
+# 6. Visualization
+# We create 3 separate plots to see the data distribution correctly
+fig, axs = plt.subplots(1, 3, figsize=(15, 5))
+
+# Plot Red
+im1 = axs[0].imshow(r_grid, cmap='viridis')
+axs[0].set_title('Red Gain Map')
+fig.colorbar(im1, ax=axs[0])
+
+# Plot Green
+im2 = axs[1].imshow(g_grid, cmap='viridis')
+axs[1].set_title('Green Gain Map')
+fig.colorbar(im2, ax=axs[1])
+
+# Plot Blue
+im3 = axs[2].imshow(b_grid, cmap='viridis')
+axs[2].set_title('Blue Gain Map')
+fig.colorbar(im3, ax=axs[2])
+
+plt.suptitle(f"LSC Gain Maps (Center ~1.0, Corners > 1.0) for collor temprature {kelvin}")
+plt.show()
+
+# 7. Prepare Texture for Export (Optional)
+# Stack them for your shader: (17, 17, 3)
+lsc_texture = np.dstack((r_grid, g_grid, b_grid))
+print(f"Final Texture Shape: {lsc_texture.shape}")
+print(f"Sample Blue Value at Corner: {b_grid[0,0]}")