1# Copyright 2014 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import numpy 16import numpy.linalg 17import unittest 18 19# Illuminant IDs 20A = 0 21D65 = 1 22 23def compute_cm_fm(illuminant, gains, ccm, cal): 24 """Compute the ColorMatrix (CM) and ForwardMatrix (FM). 25 26 Given a captured shot of a grey chart illuminated by either a D65 or a 27 standard A illuminant, the HAL will produce the WB gains and transform, 28 in the android.colorCorrection.gains and android.colorCorrection.transform 29 tags respectively. These values have both golden module and per-unit 30 calibration baked in. 31 32 This function is used to take the per-unit gains, ccm, and calibration 33 matrix, and compute the values that the DNG ColorMatrix and ForwardMatrix 34 for the specified illuminant should be. These CM and FM values should be 35 the same for all DNG files captured by all units of the same model (e.g. 36 all Nexus 5 units). The calibration matrix should be the same for all DNGs 37 saved by the same unit, but will differ unit-to-unit. 38 39 Args: 40 illuminant: 0 (A) or 1 (D65). 41 gains: White balance gains, as a list of 4 floats. 42 ccm: White balance transform matrix, as a list of 9 floats. 43 cal: Per-unit calibration matrix, as a list of 9 floats. 44 45 Returns: 46 CM: The 3x3 ColorMatrix for the specified illuminant, as a numpy array 47 FM: The 3x3 ForwardMatrix for the specified illuminant, as a numpy array 48 """ 49 50 ########################################################################### 51 # Standard matrices. 52 53 # W is the matrix that maps sRGB to XYZ. 54 # See: http://www.brucelindbloom.com/ 55 W = numpy.array([ 56 [ 0.4124564, 0.3575761, 0.1804375], 57 [ 0.2126729, 0.7151522, 0.0721750], 58 [ 0.0193339, 0.1191920, 0.9503041]]) 59 60 # HH is the chromatic adaptation matrix from D65 (since sRGB's ref white is 61 # D65) to D50 (since CIE XYZ's ref white is D50). 62 HH = numpy.array([ 63 [ 1.0478112, 0.0228866, -0.0501270], 64 [ 0.0295424, 0.9904844, -0.0170491], 65 [-0.0092345, 0.0150436, 0.7521316]]) 66 67 # H is a chromatic adaptation matrix from D65 (because sRGB's reference 68 # white is D65) to the calibration illuminant (which is a standard matrix 69 # depending on the illuminant). For a D65 illuminant, the matrix is the 70 # identity. For the A illuminant, the matrix uses the linear Bradford 71 # adaptation method to map from D65 to A. 72 # See: http://www.brucelindbloom.com/ 73 H_D65 = numpy.array([ 74 [ 1.0, 0.0, 0.0], 75 [ 0.0, 1.0, 0.0], 76 [ 0.0, 0.0, 1.0]]) 77 H_A = numpy.array([ 78 [ 1.2164557, 0.1109905, -0.1549325], 79 [ 0.1533326, 0.9152313, -0.0559953], 80 [-0.0239469, 0.0358984, 0.3147529]]) 81 H = [H_A, H_D65][illuminant] 82 83 ########################################################################### 84 # Per-model matrices (that should be the same for all units of a particular 85 # phone/camera. These are statics in the HAL camera properties. 86 87 # G is formed by taking the r,g,b gains and putting them into a 88 # diagonal matrix. 89 G = numpy.array([[gains[0],0,0], [0,gains[1],0], [0,0,gains[3]]]) 90 91 # S is just the CCM. 92 S = numpy.array([ccm[0:3], ccm[3:6], ccm[6:9]]) 93 94 ########################################################################### 95 # Per-unit matrices. 96 97 # The per-unit calibration matrix for the given illuminant. 98 CC = numpy.array([cal[0:3],cal[3:6],cal[6:9]]) 99 100 ########################################################################### 101 # Derived matrices. These should match up with DNG-related matrices 102 # provided by the HAL. 103 104 # The color matrix and forward matrix are computed as follows: 105 # CM = inv(H * W * S * G * CC) 106 # FM = HH * W * S 107 CM = numpy.linalg.inv( 108 numpy.dot(numpy.dot(numpy.dot(numpy.dot(H, W), S), G), CC)) 109 FM = numpy.dot(numpy.dot(HH, W), S) 110 111 # The color matrix is normalized so that it maps the D50 (PCS) white 112 # point to a maximum component value of 1. 113 CM = CM / max(numpy.dot(CM, (0.9642957, 1.0, 0.8251046))) 114 115 return CM, FM 116 117def compute_asn(illuminant, cal, CM): 118 """Compute the AsShotNeutral DNG value. 119 120 This value is the only dynamic DNG value; the ForwardMatrix, ColorMatrix, 121 and CalibrationMatrix values should be the same for every DNG saved by 122 a given unit. The AsShotNeutral depends on the scene white balance 123 estimate. 124 125 This function computes what the DNG AsShotNeutral values should be, for 126 a given ColorMatrix (which is computed from the WB gains and CCM for a 127 shot taken of a grey chart under either A or D65 illuminants) and the 128 per-unit calibration matrix. 129 130 Args: 131 illuminant: 0 (A) or 1 (D65). 132 cal: Per-unit calibration matrix, as a list of 9 floats. 133 CM: The computed 3x3 ColorMatrix for the illuminant, as a numpy array. 134 135 Returns: 136 ASN: The AsShotNeutral value, as a length-3 numpy array. 137 """ 138 139 ########################################################################### 140 # Standard matrices. 141 142 # XYZCAL is the XYZ coordinate of calibration illuminant (so A or D65). 143 # See: Wyszecki & Stiles, "Color Science", second edition. 144 XYZCAL_A = numpy.array([1.098675, 1.0, 0.355916]) 145 XYZCAL_D65 = numpy.array([0.950456, 1.0, 1.089058]) 146 XYZCAL = [XYZCAL_A, XYZCAL_D65][illuminant] 147 148 ########################################################################### 149 # Per-unit matrices. 150 151 # The per-unit calibration matrix for the given illuminant. 152 CC = numpy.array([cal[0:3],cal[3:6],cal[6:9]]) 153 154 ########################################################################### 155 # Derived matrices. 156 157 # The AsShotNeutral value is then the product of this final color matrix 158 # with the XYZ coordinate of calibration illuminant. 159 # ASN = CC * CM * XYZCAL 160 ASN = numpy.dot(numpy.dot(CC, CM), XYZCAL) 161 162 # Normalize so the max vector element is 1.0. 163 ASN = ASN / max(ASN) 164 165 return ASN 166 167class __UnitTest(unittest.TestCase): 168 """Run a suite of unit tests on this module. 169 """ 170 # TODO: Add more unit tests. 171 172if __name__ == '__main__': 173 unittest.main() 174 175