1 # Copyright 2013 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 15 import its.device 16 import its.image 17 import its.objects 18 import os 19 import os.path 20 import sys 21 import json 22 import unittest 23 import json 24 25 CACHE_FILENAME = "its.target.cfg" 26 27 def __do_target_exposure_measurement(its_session): 28 """Use device 3A and captured shots to determine scene exposure. 29 30 Creates a new ITS device session (so this function should not be called 31 while another session to the device is open). 32 33 Assumes that the camera is pointed at a scene that is reasonably uniform 34 and reasonably lit -- that is, an appropriate target for running the ITS 35 tests that assume such uniformity. 36 37 Measures the scene using device 3A and then by taking a shot to hone in on 38 the exact exposure level that will result in a center 10% by 10% patch of 39 the scene having a intensity level of 0.5 (in the pixel range of [0,1]) 40 when a linear tonemap is used. That is, the pixels coming off the sensor 41 should be at approximately 50% intensity (however note that it's actually 42 the luma value in the YUV image that is being targeted to 50%). 43 44 The computed exposure value is the product of the sensitivity (ISO) and 45 exposure time (ns) to achieve that sensor exposure level. 46 47 Args: 48 its_session: Holds an open device session. 49 50 Returns: 51 The measured product of sensitivity and exposure time that results in 52 the luma channel of captured shots having an intensity of 0.5. 53 """ 54 print "Measuring target exposure" 55 56 # Get AE+AWB lock first, so the auto values in the capture result are 57 # populated properly. 58 r = [[0.45, 0.45, 0.1, 0.1, 1]] 59 sens, exp_time, gains, xform, _ \ 60 = its_session.do_3a(r,r,r,do_af=False,get_results=True) 61 62 # Convert the transform to rational. 63 xform_rat = [{"numerator":int(100*x),"denominator":100} for x in xform] 64 65 # Linear tonemap 66 tmap = sum([[i/63.0,i/63.0] for i in range(64)], []) 67 68 # Capture a manual shot with this exposure, using a linear tonemap. 69 # Use the gains+transform returned by the AWB pass. 70 req = its.objects.manual_capture_request(sens, exp_time) 71 req["android.tonemap.mode"] = 0 72 req["android.tonemap.curveRed"] = tmap 73 req["android.tonemap.curveGreen"] = tmap 74 req["android.tonemap.curveBlue"] = tmap 75 req["android.colorCorrection.transform"] = xform_rat 76 req["android.colorCorrection.gains"] = gains 77 cap = its_session.do_capture(req) 78 79 # Compute the mean luma of a center patch. 80 yimg,uimg,vimg = its.image.convert_capture_to_planes(cap) 81 tile = its.image.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1) 82 luma_mean = its.image.compute_image_means(tile) 83 84 # Compute the exposure value that would result in a luma of 0.5. 85 return sens * exp_time * 0.5 / luma_mean[0] 86 87 def __set_cached_target_exposure(exposure): 88 """Saves the given exposure value to a cached location. 89 90 Once a value is cached, a call to __get_cached_target_exposure will return 91 the value, even from a subsequent test/script run. That is, the value is 92 persisted. 93 94 The value is persisted in a JSON file in the current directory (from which 95 the script calling this function is run). 96 97 Args: 98 exposure: The value to cache. 99 """ 100 print "Setting cached target exposure" 101 with open(CACHE_FILENAME, "w") as f: 102 f.write(json.dumps({"exposure":exposure})) 103 104 def __get_cached_target_exposure(): 105 """Get the cached exposure value. 106 107 Returns: 108 The cached exposure value, or None if there is no valid cached value. 109 """ 110 try: 111 with open(CACHE_FILENAME, "r") as f: 112 o = json.load(f) 113 return o["exposure"] 114 except: 115 return None 116 117 def clear_cached_target_exposure(): 118 """If there is a cached exposure value, clear it. 119 """ 120 if os.path.isfile(CACHE_FILENAME): 121 os.remove(CACHE_FILENAME) 122 123 def set_hardcoded_exposure(exposure): 124 """Set a hard-coded exposure value, rather than relying on measurements. 125 126 The exposure value is the product of sensitivity (ISO) and eposure time 127 (ns) that will result in a center-patch luma value of 0.5 (using a linear 128 tonemap) for the scene that the camera is pointing at. 129 130 If bringing up a new HAL implementation and the ability use the device to 131 measure the scene isn't there yet (e.g. device 3A doesn't work), then a 132 cache file of the appropriate name can be manually created and populated 133 with a hard-coded value using this function. 134 135 Args: 136 exposure: The hard-coded exposure value to set. 137 """ 138 __set_cached_target_exposure(exposure) 139 140 def get_target_exposure(its_session=None): 141 """Get the target exposure to use. 142 143 If there is a cached value and if the "target" command line parameter is 144 present, then return the cached value. Otherwise, measure a new value from 145 the scene, cache it, then return it. 146 147 Args: 148 its_session: Optional, holding an open device session. 149 150 Returns: 151 The target exposure value. 152 """ 153 cached_exposure = None 154 for s in sys.argv[1:]: 155 if s == "target": 156 cached_exposure = __get_cached_target_exposure() 157 if cached_exposure is not None: 158 print "Using cached target exposure" 159 return cached_exposure 160 if its_session is None: 161 with its.device.ItsSession() as cam: 162 measured_exposure = __do_target_exposure_measurement(cam) 163 else: 164 measured_exposure = __do_target_exposure_measurement(its_session) 165 __set_cached_target_exposure(measured_exposure) 166 return measured_exposure 167 168 def get_target_exposure_combos(its_session=None): 169 """Get a set of legal combinations of target (exposure time, sensitivity). 170 171 Gets the target exposure value, which is a product of sensitivity (ISO) and 172 exposure time, and returns equivalent tuples of (exposure time,sensitivity) 173 that are all legal and that correspond to the four extrema in this 2D param 174 space, as well as to two "middle" points. 175 176 Will open a device session if its_session is None. 177 178 Args: 179 its_session: Optional, holding an open device session. 180 181 Returns: 182 Object containing six legal (exposure time, sensitivity) tuples, keyed 183 by the following strings: 184 "minExposureTime" 185 "midExposureTime" 186 "maxExposureTime" 187 "minSensitivity" 188 "midSensitivity" 189 "maxSensitivity 190 """ 191 if its_session is None: 192 with its.device.ItsSession() as cam: 193 exposure = get_target_exposure(cam) 194 props = cam.get_camera_properties() 195 else: 196 exposure = get_target_exposure(its_session) 197 props = its_session.get_camera_properties() 198 199 sens_range = props['android.sensor.info.sensitivityRange'] 200 exp_time_range = props['android.sensor.info.exposureTimeRange'] 201 202 # Combo 1: smallest legal exposure time. 203 e1_expt = exp_time_range[0] 204 e1_sens = exposure / e1_expt 205 if e1_sens > sens_range[1]: 206 e1_sens = sens_range[1] 207 e1_expt = exposure / e1_sens 208 209 # Combo 2: largest legal exposure time. 210 e2_expt = exp_time_range[1] 211 e2_sens = exposure / e2_expt 212 if e2_sens < sens_range[0]: 213 e2_sens = sens_range[0] 214 e2_expt = exposure / e2_sens 215 216 # Combo 3: smallest legal sensitivity. 217 e3_sens = sens_range[0] 218 e3_expt = exposure / e3_sens 219 if e3_expt > exp_time_range[1]: 220 e3_expt = exp_time_range[1] 221 e3_sens = exposure / e3_expt 222 223 # Combo 4: largest legal sensitivity. 224 e4_sens = sens_range[1] 225 e4_expt = exposure / e4_sens 226 if e4_expt < exp_time_range[0]: 227 e4_expt = exp_time_range[0] 228 e4_sens = exposure / e4_expt 229 230 # Combo 5: middle exposure time. 231 e5_expt = (exp_time_range[0] + exp_time_range[1]) / 2.0 232 e5_sens = exposure / e5_expt 233 if e5_sens > sens_range[1]: 234 e5_sens = sens_range[1] 235 e5_expt = exposure / e5_sens 236 if e5_sens < sens_range[0]: 237 e5_sens = sens_range[0] 238 e5_expt = exposure / e5_sens 239 240 # Combo 6: middle sensitivity. 241 e6_sens = (sens_range[0] + sens_range[1]) / 2.0 242 e6_expt = exposure / e6_sens 243 if e6_expt > exp_time_range[1]: 244 e6_expt = exp_time_range[1] 245 e6_sens = exposure / e6_expt 246 if e6_expt < exp_time_range[0]: 247 e6_expt = exp_time_range[0] 248 e6_sens = exposure / e6_expt 249 250 return { 251 "minExposureTime" : (int(e1_expt), int(e1_sens)), 252 "maxExposureTime" : (int(e2_expt), int(e2_sens)), 253 "minSensitivity" : (int(e3_expt), int(e3_sens)), 254 "maxSensitivity" : (int(e4_expt), int(e4_sens)), 255 "midExposureTime" : (int(e5_expt), int(e5_sens)), 256 "midSensitivity" : (int(e6_expt), int(e6_sens)) 257 } 258 259 class __UnitTest(unittest.TestCase): 260 """Run a suite of unit tests on this module. 261 """ 262 # TODO: Add some unit tests. 263 264 if __name__ == '__main__': 265 unittest.main() 266 267