#!/usr/bin/env python3 # # SPDX-License-Identifier: BSD-2-Clause # # Copyright (C) 2019, Raspberry Pi Ltd # # ctt.py - camera tuning tool import os import sys from ctt_image_load import * from ctt_ccm import * from ctt_awb import * from ctt_alsc import * from ctt_lux import * from ctt_noise import * from ctt_geq import * from ctt_pretty_print_json import pretty_print import random import json import re """ This file houses the camera object, which is used to perform the calibrations. The camera object houses all the calibration images as attributes in two lists: - imgs (macbeth charts) - imgs_alsc (alsc correction images) Various calibrations are methods of the camera object, and the output is stored in a dictionary called self.json. Once all the caibration has been completed, the Camera.json is written into a json file. The camera object initialises its json dictionary by reading from a pre-written blank json file. This has been done to avoid reproducing the entire json file in the code here, thereby avoiding unecessary clutter. """ """ Get the colour and lux values from the strings of each inidvidual image """ def get_col_lux(string): """ Extract colour and lux values from filename """ col = re.search(r'([0-9]+)[kK](\.(jpg|jpeg|brcm|dng)|_.*\.(jpg|jpeg|brcm|dng))$', string) lux = re.search(r'([0-9]+)[lL](\.(jpg|jpeg|brcm|dng)|_.*\.(jpg|jpeg|brcm|dng))$', string) try: col = col.group(1) except AttributeError: """ Catch error if images labelled incorrectly and pass reasonable defaults """ return None, None try: lux = lux.group(1) except AttributeError: """ Catch error if images labelled incorrectly and pass reasonable defaults Still returns colour if that has been found. """ return col, None return int(col), int(lux) """ Camera object that is the backbone of the tuning tool. Input is the desired path of the output json. """ class Camera: def __init__(self, jfile): self.path = os.path.dirname(os.path.expanduser(__file__)) + '/' if self.path == '/': self.path = '' self.imgs = [] self.imgs_alsc = [] self.log = 'Log created : ' + time.asctime(time.localtime(time.time())) self.log_separator = '\n'+'-'*70+'\n' self.jf = jfile """ initial json dict populated by uncalibrated values """ self.json = { "rpi.black_level": { "black_level": 4096 }, "rpi.dpc": { }, "rpi.lux": { "reference_shutter_speed": 10000, "reference_gain": 1, "reference_aperture": 1.0 }, "rpi.noise": { }, "rpi.geq": { }, "rpi.sdn": { }, "rpi.awb": { "priors": [ {"lux": 0, "prior": [2000, 1.0, 3000, 0.0, 13000, 0.0]}, {"lux": 800, "prior": [2000, 0.0, 6000, 2.0, 13000, 2.0]}, {"lux": 1500, "prior": [2000, 0.0, 4000, 1.0, 6000, 6.0, 6500, 7.0, 7000, 1.0, 13000, 1.0]} ], "modes": { "auto": {"lo": 2500, "hi": 8000}, "incandescent": {"lo": 2500, "hi": 3000}, "tungsten": {"lo": 3000, "hi": 3500}, "fluorescent": {"lo": 4000, "hi": 4700}, "indoor": {"lo": 3000, "hi": 5000}, "daylight": {"lo": 5500, "hi": 6500}, "cloudy": {"lo": 7000, "hi": 8600} }, "bayes": 1 }, "rpi.agc": { "metering_modes": { "centre-weighted": { "weights": [3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0] }, "spot": { "weights": [2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, "matrix": { "weights": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] } }, "exposure_modes": { "normal": { "shutter": [100, 10000, 30000, 60000, 120000], "gain": [1.0, 2.0, 4.0, 6.0, 6.0] }, "short": { "shutter": [100, 5000, 10000, 20000, 120000], "gain": [1.0, 2.0, 4.0, 6.0, 6.0] } }, "constraint_modes": { "normal": [ {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]} ], "highlight": [ {"bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.5, 1000, 0.5]}, {"bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target": [0, 0.8, 1000, 0.8]} ] }, "y_target": [0, 0.16, 1000, 0.165, 10000, 0.17] }, "rpi.alsc": { 'omega': 1.3, 'n_iter': 100, 'luminance_strength': 0.7, }, "rpi.contrast": { "ce_enable": 1, "gamma_curve": [ 0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193, 8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168, 16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796, 32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476, 65535, 65535 ] }, "rpi.ccm": { }, "rpi.sharpen": { } } """ Perform colour correction calibrations by comparing macbeth patch colours to standard macbeth chart colours. """ def ccm_cal(self, do_alsc_colour): if 'rpi.ccm' in self.disable: return 1 print('\nStarting CCM calibration') self.log_new_sec('CCM') """ if image is greyscale then CCm makes no sense """ if self.grey: print('\nERROR: Can\'t do CCM on greyscale image!') self.log += '\nERROR: Cannot perform CCM calibration ' self.log += 'on greyscale image!\nCCM aborted!' del self.json['rpi.ccm'] return 0 a = time.time() """ Check if alsc tables have been generated, if not then do ccm without alsc """ if ("rpi.alsc" not in self.disable) and do_alsc_colour: """ case where ALSC colour has been done, so no errors should be expected... """ try: cal_cr_list = self.json['rpi.alsc']['calibrations_Cr'] cal_cb_list = self.json['rpi.alsc']['calibrations_Cb'] self.log += '\nALSC tables found successfully' except KeyError: cal_cr_list, cal_cb_list = None, None print('WARNING! No ALSC tables found for CCM!') print('Performing CCM calibrations without ALSC correction...') self.log += '\nWARNING: No ALSC tables found.\nCCM calibration ' self.log += 'performed without ALSC correction...' else: """ case where config options result in CCM done without ALSC colour tables """ cal_cr_list, cal_cb_list = None, None self.log += '\nWARNING: No ALSC tables found.\nCCM calibration ' self.log += 'performed without ALSC correction...' """ Do CCM calibration """ try: ccms = ccm(self, cal_cr_list, cal_cb_list) except ArithmeticError: print('ERROR: Matrix is singular!\nTake new pictures and try again...') self.log += '\nERROR: Singular matrix encountered during fit!' self.log += '\nCCM aborted!' return 1 """ Write output to json """ self.