1*b7c941bbSAndroid Build Coastguard Worker# Copyright 2013 The Android Open Source Project 2*b7c941bbSAndroid Build Coastguard Worker# 3*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*b7c941bbSAndroid Build Coastguard Worker# 7*b7c941bbSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*b7c941bbSAndroid Build Coastguard Worker# 9*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*b7c941bbSAndroid Build Coastguard Worker# limitations under the License. 14*b7c941bbSAndroid Build Coastguard Worker"""Utility functions to calculate targeted exposures based on camera properties. 15*b7c941bbSAndroid Build Coastguard Worker""" 16*b7c941bbSAndroid Build Coastguard Worker 17*b7c941bbSAndroid Build Coastguard Workerimport json 18*b7c941bbSAndroid Build Coastguard Workerimport logging 19*b7c941bbSAndroid Build Coastguard Workerimport os 20*b7c941bbSAndroid Build Coastguard Workerimport sys 21*b7c941bbSAndroid Build Coastguard Worker 22*b7c941bbSAndroid Build Coastguard Workerimport capture_request_utils 23*b7c941bbSAndroid Build Coastguard Workerimport image_processing_utils 24*b7c941bbSAndroid Build Coastguard Workerimport its_session_utils 25*b7c941bbSAndroid Build Coastguard Worker 26*b7c941bbSAndroid Build Coastguard Worker_CACHE_FILENAME = 'its.target.cfg' 27*b7c941bbSAndroid Build Coastguard Worker_REGION_3A = [[0.45, 0.45, 0.1, 0.1, 1]] 28*b7c941bbSAndroid Build Coastguard Worker 29*b7c941bbSAndroid Build Coastguard Worker 30*b7c941bbSAndroid Build Coastguard Workerdef get_target_exposure_combos(output_path, its_session=None): 31*b7c941bbSAndroid Build Coastguard Worker """Get a set of legal combinations of target (exposure time, sensitivity). 32*b7c941bbSAndroid Build Coastguard Worker 33*b7c941bbSAndroid Build Coastguard Worker Gets the target exposure value, which is a product of sensitivity (ISO) and 34*b7c941bbSAndroid Build Coastguard Worker exposure time, and returns equivalent tuples of (exposure time,sensitivity) 35*b7c941bbSAndroid Build Coastguard Worker that are all legal and that correspond to the four extrema in this 2D param 36*b7c941bbSAndroid Build Coastguard Worker space, as well as to two "middle" points. 37*b7c941bbSAndroid Build Coastguard Worker 38*b7c941bbSAndroid Build Coastguard Worker Will open a device session if its_session is None. 39*b7c941bbSAndroid Build Coastguard Worker 40*b7c941bbSAndroid Build Coastguard Worker Args: 41*b7c941bbSAndroid Build Coastguard Worker output_path: String, path where the target.cfg file will be saved. 42*b7c941bbSAndroid Build Coastguard Worker its_session: Optional, holding an open device session. 43*b7c941bbSAndroid Build Coastguard Worker 44*b7c941bbSAndroid Build Coastguard Worker Returns: 45*b7c941bbSAndroid Build Coastguard Worker Object containing six legal (exposure time, sensitivity) tuples, keyed 46*b7c941bbSAndroid Build Coastguard Worker by the following strings: 47*b7c941bbSAndroid Build Coastguard Worker 'minExposureTime' 48*b7c941bbSAndroid Build Coastguard Worker 'midExposureTime' 49*b7c941bbSAndroid Build Coastguard Worker 'maxExposureTime' 50*b7c941bbSAndroid Build Coastguard Worker 'minSensitivity' 51*b7c941bbSAndroid Build Coastguard Worker 'midSensitivity' 52*b7c941bbSAndroid Build Coastguard Worker 'maxSensitivity' 53*b7c941bbSAndroid Build Coastguard Worker """ 54*b7c941bbSAndroid Build Coastguard Worker target_config_filename = os.path.join(output_path, _CACHE_FILENAME) 55*b7c941bbSAndroid Build Coastguard Worker 56*b7c941bbSAndroid Build Coastguard Worker if its_session is None: 57*b7c941bbSAndroid Build Coastguard Worker with its_session_utils.ItsSession() as cam: 58*b7c941bbSAndroid Build Coastguard Worker exp_sens_prod = get_target_exposure(target_config_filename, cam) 59*b7c941bbSAndroid Build Coastguard Worker props = cam.get_camera_properties() 60*b7c941bbSAndroid Build Coastguard Worker props = cam.override_with_hidden_physical_camera_props(props) 61*b7c941bbSAndroid Build Coastguard Worker else: 62*b7c941bbSAndroid Build Coastguard Worker exp_sens_prod = get_target_exposure(target_config_filename, its_session) 63*b7c941bbSAndroid Build Coastguard Worker props = its_session.get_camera_properties() 64*b7c941bbSAndroid Build Coastguard Worker props = its_session.override_with_hidden_physical_camera_props(props) 65*b7c941bbSAndroid Build Coastguard Worker 66*b7c941bbSAndroid Build Coastguard Worker sens_range = props['android.sensor.info.sensitivityRange'] 67*b7c941bbSAndroid Build Coastguard Worker exp_time_range = props['android.sensor.info.exposureTimeRange'] 68*b7c941bbSAndroid Build Coastguard Worker logging.debug('Target exposure*sensitivity: %d', exp_sens_prod) 69*b7c941bbSAndroid Build Coastguard Worker logging.debug('sensor exp time range: %s', str(exp_time_range)) 70*b7c941bbSAndroid Build Coastguard Worker logging.debug('sensor sensitivity range: %s', str(sens_range)) 71*b7c941bbSAndroid Build Coastguard Worker 72*b7c941bbSAndroid Build Coastguard Worker # Combo 1: smallest legal exposure time. 73*b7c941bbSAndroid Build Coastguard Worker e1_expt = exp_time_range[0] 74*b7c941bbSAndroid Build Coastguard Worker e1_sens = int(exp_sens_prod / e1_expt) 75*b7c941bbSAndroid Build Coastguard Worker if e1_sens > sens_range[1]: 76*b7c941bbSAndroid Build Coastguard Worker e1_sens = sens_range[1] 77*b7c941bbSAndroid Build Coastguard Worker e1_expt = int(exp_sens_prod / e1_sens) 78*b7c941bbSAndroid Build Coastguard Worker logging.debug('minExposureTime e: %d, s: %d', e1_expt, e1_sens) 79*b7c941bbSAndroid Build Coastguard Worker 80*b7c941bbSAndroid Build Coastguard Worker # Combo 2: largest legal exposure time. 81*b7c941bbSAndroid Build Coastguard Worker e2_expt = exp_time_range[1] 82*b7c941bbSAndroid Build Coastguard Worker e2_sens = int(exp_sens_prod / e2_expt) 83*b7c941bbSAndroid Build Coastguard Worker if e2_sens < sens_range[0]: 84*b7c941bbSAndroid Build Coastguard Worker e2_sens = sens_range[0] 85*b7c941bbSAndroid Build Coastguard Worker e2_expt = int(exp_sens_prod / e2_sens) 86*b7c941bbSAndroid Build Coastguard Worker logging.debug('maxExposureTime e: %d, s: %d', e2_expt, e2_sens) 87*b7c941bbSAndroid Build Coastguard Worker 88*b7c941bbSAndroid Build Coastguard Worker # Combo 3: smallest legal sensitivity. 89*b7c941bbSAndroid Build Coastguard Worker e3_sens = sens_range[0] 90*b7c941bbSAndroid Build Coastguard Worker e3_expt = int(exp_sens_prod / e3_sens) 91*b7c941bbSAndroid Build Coastguard Worker if e3_expt > exp_time_range[1]: 92*b7c941bbSAndroid Build Coastguard Worker e3_expt = exp_time_range[1] 93*b7c941bbSAndroid Build Coastguard Worker e3_sens = int(exp_sens_prod / e3_expt) 94*b7c941bbSAndroid Build Coastguard Worker logging.debug('minSensitivity e: %d, s: %d', e3_expt, e3_sens) 95*b7c941bbSAndroid Build Coastguard Worker 96*b7c941bbSAndroid Build Coastguard Worker # Combo 4: largest legal sensitivity. 97*b7c941bbSAndroid Build Coastguard Worker e4_sens = sens_range[1] 98*b7c941bbSAndroid Build Coastguard Worker e4_expt = int(exp_sens_prod / e4_sens) 99*b7c941bbSAndroid Build Coastguard Worker if e4_expt < exp_time_range[0]: 100*b7c941bbSAndroid Build Coastguard Worker e4_expt = exp_time_range[0] 101*b7c941bbSAndroid Build Coastguard Worker e4_sens = int(exp_sens_prod / e4_expt) 102*b7c941bbSAndroid Build Coastguard Worker logging.debug('maxSensitivity e: %d, s: %d', e4_expt, e4_sens) 103*b7c941bbSAndroid Build Coastguard Worker 104*b7c941bbSAndroid Build Coastguard Worker # Combo 5: middle exposure time. 105*b7c941bbSAndroid Build Coastguard Worker e5_expt = int((exp_time_range[0] + exp_time_range[1]) / 2.0) 106*b7c941bbSAndroid Build Coastguard Worker e5_sens = int(exp_sens_prod / e5_expt) 107*b7c941bbSAndroid Build Coastguard Worker if e5_sens > sens_range[1]: 108*b7c941bbSAndroid Build Coastguard Worker e5_sens = sens_range[1] 109*b7c941bbSAndroid Build Coastguard Worker e5_expt = int(exp_sens_prod / e5_sens) 110*b7c941bbSAndroid Build Coastguard Worker if e5_sens < sens_range[0]: 111*b7c941bbSAndroid Build Coastguard Worker e5_sens = sens_range[0] 112*b7c941bbSAndroid Build Coastguard Worker e5_expt = int(exp_sens_prod / e5_sens) 113*b7c941bbSAndroid Build Coastguard Worker logging.debug('midExposureTime e: %d, s: %d', e5_expt, e5_sens) 114*b7c941bbSAndroid Build Coastguard Worker 115*b7c941bbSAndroid Build Coastguard Worker # Combo 6: middle sensitivity. 116*b7c941bbSAndroid Build Coastguard Worker e6_sens = int((sens_range[0] + sens_range[1]) / 2.0) 117*b7c941bbSAndroid Build Coastguard Worker e6_expt = int(exp_sens_prod / e6_sens) 118*b7c941bbSAndroid Build Coastguard Worker if e6_expt > exp_time_range[1]: 119*b7c941bbSAndroid Build Coastguard Worker e6_expt = exp_time_range[1] 120*b7c941bbSAndroid Build Coastguard Worker e6_sens = int(exp_sens_prod / e6_expt) 121*b7c941bbSAndroid Build Coastguard Worker if e6_expt < exp_time_range[0]: 122*b7c941bbSAndroid Build Coastguard Worker e6_expt = exp_time_range[0] 123*b7c941bbSAndroid Build Coastguard Worker e6_sens = int(exp_sens_prod / e6_expt) 124*b7c941bbSAndroid Build Coastguard Worker logging.debug('midSensitivity e: %d, s: %d', e6_expt, e6_sens) 125*b7c941bbSAndroid Build Coastguard Worker 126*b7c941bbSAndroid Build Coastguard Worker return { 127*b7c941bbSAndroid Build Coastguard Worker 'minExposureTime': (e1_expt, e1_sens), 128*b7c941bbSAndroid Build Coastguard Worker 'maxExposureTime': (e2_expt, e2_sens), 129*b7c941bbSAndroid Build Coastguard Worker 'minSensitivity': (e3_expt, e3_sens), 130*b7c941bbSAndroid Build Coastguard Worker 'maxSensitivity': (e4_expt, e4_sens), 131*b7c941bbSAndroid Build Coastguard Worker 'midExposureTime': (e5_expt, e5_sens), 132*b7c941bbSAndroid Build Coastguard Worker 'midSensitivity': (e6_expt, e6_sens) 133*b7c941bbSAndroid Build Coastguard Worker } 134*b7c941bbSAndroid Build Coastguard Worker 135*b7c941bbSAndroid Build Coastguard Worker 136*b7c941bbSAndroid Build Coastguard Workerdef get_target_exposure(target_config_filename, its_session=None): 137*b7c941bbSAndroid Build Coastguard Worker """Get the target exposure to use. 138*b7c941bbSAndroid Build Coastguard Worker 139*b7c941bbSAndroid Build Coastguard Worker If there is a cached value and if the "target" command line parameter is 140*b7c941bbSAndroid Build Coastguard Worker present, then return the cached value. Otherwise, measure a new value from 141*b7c941bbSAndroid Build Coastguard Worker the scene, cache it, then return it. 142*b7c941bbSAndroid Build Coastguard Worker 143*b7c941bbSAndroid Build Coastguard Worker Args: 144*b7c941bbSAndroid Build Coastguard Worker target_config_filename: String, target config file name. 145*b7c941bbSAndroid Build Coastguard Worker its_session: Optional, holding an open device session. 146*b7c941bbSAndroid Build Coastguard Worker 147*b7c941bbSAndroid Build Coastguard Worker Returns: 148*b7c941bbSAndroid Build Coastguard Worker The target exposure*sensitivity value. 149*b7c941bbSAndroid Build Coastguard Worker """ 150*b7c941bbSAndroid Build Coastguard Worker cached_exposure = None 151*b7c941bbSAndroid Build Coastguard Worker for s in sys.argv[1:]: 152*b7c941bbSAndroid Build Coastguard Worker if s == 'target': 153*b7c941bbSAndroid Build Coastguard Worker cached_exposure = get_cached_target_exposure(target_config_filename) 154*b7c941bbSAndroid Build Coastguard Worker if cached_exposure is not None: 155*b7c941bbSAndroid Build Coastguard Worker logging.debug('Using cached target exposure') 156*b7c941bbSAndroid Build Coastguard Worker return cached_exposure 157*b7c941bbSAndroid Build Coastguard Worker if its_session is None: 158*b7c941bbSAndroid Build Coastguard Worker with its_session_utils.ItsSession() as cam: 159*b7c941bbSAndroid Build Coastguard Worker measured_exposure = do_target_exposure_measurement(cam) 160*b7c941bbSAndroid Build Coastguard Worker else: 161*b7c941bbSAndroid Build Coastguard Worker measured_exposure = do_target_exposure_measurement(its_session) 162*b7c941bbSAndroid Build Coastguard Worker set_cached_target_exposure(target_config_filename, measured_exposure) 163*b7c941bbSAndroid Build Coastguard Worker return measured_exposure 164*b7c941bbSAndroid Build Coastguard Worker 165*b7c941bbSAndroid Build Coastguard Worker 166*b7c941bbSAndroid Build Coastguard Workerdef set_cached_target_exposure(target_config_filename, exposure): 167*b7c941bbSAndroid Build Coastguard Worker """Saves the given exposure value to a cached location. 168*b7c941bbSAndroid Build Coastguard Worker 169*b7c941bbSAndroid Build Coastguard Worker Once a value is cached, a call to get_cached_target_exposure will return 170*b7c941bbSAndroid Build Coastguard Worker the value, even from a subsequent test/script run. That is, the value is 171*b7c941bbSAndroid Build Coastguard Worker persisted. 172*b7c941bbSAndroid Build Coastguard Worker 173*b7c941bbSAndroid Build Coastguard Worker The value is persisted in a JSON file in the current directory (from which 174*b7c941bbSAndroid Build Coastguard Worker the script calling this function is run). 175*b7c941bbSAndroid Build Coastguard Worker 176*b7c941bbSAndroid Build Coastguard Worker Args: 177*b7c941bbSAndroid Build Coastguard Worker target_config_filename: String, target config file name. 178*b7c941bbSAndroid Build Coastguard Worker exposure: The value to cache. 179*b7c941bbSAndroid Build Coastguard Worker """ 180*b7c941bbSAndroid Build Coastguard Worker logging.debug('Setting cached target exposure') 181*b7c941bbSAndroid Build Coastguard Worker with open(target_config_filename, 'w') as f: 182*b7c941bbSAndroid Build Coastguard Worker f.write(json.dumps({'exposure': exposure})) 183*b7c941bbSAndroid Build Coastguard Worker 184*b7c941bbSAndroid Build Coastguard Worker 185*b7c941bbSAndroid Build Coastguard Workerdef get_cached_target_exposure(target_config_filename): 186*b7c941bbSAndroid Build Coastguard Worker """Get the cached exposure value. 187*b7c941bbSAndroid Build Coastguard Worker 188*b7c941bbSAndroid Build Coastguard Worker Args: 189*b7c941bbSAndroid Build Coastguard Worker target_config_filename: String, target config file name. 190*b7c941bbSAndroid Build Coastguard Worker 191*b7c941bbSAndroid Build Coastguard Worker Returns: 192*b7c941bbSAndroid Build Coastguard Worker The cached exposure value, or None if there is no valid cached value. 193*b7c941bbSAndroid Build Coastguard Worker """ 194*b7c941bbSAndroid Build Coastguard Worker try: 195*b7c941bbSAndroid Build Coastguard Worker with open(target_config_filename, 'r') as f: 196*b7c941bbSAndroid Build Coastguard Worker o = json.load(f) 197*b7c941bbSAndroid Build Coastguard Worker return o['exposure'] 198*b7c941bbSAndroid Build Coastguard Worker except IOError: 199*b7c941bbSAndroid Build Coastguard Worker return None 200*b7c941bbSAndroid Build Coastguard Worker 201*b7c941bbSAndroid Build Coastguard Worker 202*b7c941bbSAndroid Build Coastguard Workerdef do_target_exposure_measurement(its_session): 203*b7c941bbSAndroid Build Coastguard Worker """Use device 3A and captured shots to determine scene exposure. 204*b7c941bbSAndroid Build Coastguard Worker 205*b7c941bbSAndroid Build Coastguard Worker Creates a new ITS device session (so this function should not be called 206*b7c941bbSAndroid Build Coastguard Worker while another session to the device is open). 207*b7c941bbSAndroid Build Coastguard Worker 208*b7c941bbSAndroid Build Coastguard Worker Assumes that the camera is pointed at a scene that is reasonably uniform 209*b7c941bbSAndroid Build Coastguard Worker and reasonably lit -- that is, an appropriate target for running the ITS 210*b7c941bbSAndroid Build Coastguard Worker tests that assume such uniformity. 211*b7c941bbSAndroid Build Coastguard Worker 212*b7c941bbSAndroid Build Coastguard Worker Measures the scene using device 3A and then by taking a shot to hone in on 213*b7c941bbSAndroid Build Coastguard Worker the exact exposure level that will result in a center 10% by 10% patch of 214*b7c941bbSAndroid Build Coastguard Worker the scene having a intensity level of 0.5 (in the pixel range of [0,1]) 215*b7c941bbSAndroid Build Coastguard Worker when a linear tonemap is used. That is, the pixels coming off the sensor 216*b7c941bbSAndroid Build Coastguard Worker should be at approximately 50% intensity (however note that it's actually 217*b7c941bbSAndroid Build Coastguard Worker the luma value in the YUV image that is being targeted to 50%). 218*b7c941bbSAndroid Build Coastguard Worker 219*b7c941bbSAndroid Build Coastguard Worker The computed exposure value is the product of the sensitivity (ISO) and 220*b7c941bbSAndroid Build Coastguard Worker exposure time (ns) to achieve that sensor exposure level. 221*b7c941bbSAndroid Build Coastguard Worker 222*b7c941bbSAndroid Build Coastguard Worker Args: 223*b7c941bbSAndroid Build Coastguard Worker its_session: Holds an open device session. 224*b7c941bbSAndroid Build Coastguard Worker 225*b7c941bbSAndroid Build Coastguard Worker Returns: 226*b7c941bbSAndroid Build Coastguard Worker The measured product of sensitivity and exposure time that results in 227*b7c941bbSAndroid Build Coastguard Worker the luma channel of captured shots having an intensity of 0.5. 228*b7c941bbSAndroid Build Coastguard Worker """ 229*b7c941bbSAndroid Build Coastguard Worker logging.debug('Measuring target exposure') 230*b7c941bbSAndroid Build Coastguard Worker 231*b7c941bbSAndroid Build Coastguard Worker # Get AE+AWB lock first, so the auto values in the capture result are 232*b7c941bbSAndroid Build Coastguard Worker # populated properly. 233*b7c941bbSAndroid Build Coastguard Worker sens, exp_time, gains, xform, _ = its_session.do_3a( 234*b7c941bbSAndroid Build Coastguard Worker _REGION_3A, _REGION_3A, _REGION_3A, do_af=False, get_results=True) 235*b7c941bbSAndroid Build Coastguard Worker 236*b7c941bbSAndroid Build Coastguard Worker # Convert the transform to rational. 237*b7c941bbSAndroid Build Coastguard Worker xform_rat = [{'numerator': int(100 * x), 'denominator': 100} for x in xform] 238*b7c941bbSAndroid Build Coastguard Worker 239*b7c941bbSAndroid Build Coastguard Worker # Linear tonemap 240*b7c941bbSAndroid Build Coastguard Worker tmap = sum([[i / 63.0, i / 63.0] for i in range(64)], []) 241*b7c941bbSAndroid Build Coastguard Worker 242*b7c941bbSAndroid Build Coastguard Worker # Capture a manual shot with this exposure, using a linear tonemap. 243*b7c941bbSAndroid Build Coastguard Worker # Use the gains+transform returned by the AWB pass. 244*b7c941bbSAndroid Build Coastguard Worker req = capture_request_utils.manual_capture_request(sens, exp_time) 245*b7c941bbSAndroid Build Coastguard Worker req['android.tonemap.mode'] = 0 246*b7c941bbSAndroid Build Coastguard Worker req['android.tonemap.curve'] = {'red': tmap, 'green': tmap, 'blue': tmap} 247*b7c941bbSAndroid Build Coastguard Worker req['android.colorCorrection.transform'] = xform_rat 248*b7c941bbSAndroid Build Coastguard Worker req['android.colorCorrection.gains'] = gains 249*b7c941bbSAndroid Build Coastguard Worker cap = its_session.do_capture(req) 250*b7c941bbSAndroid Build Coastguard Worker 251*b7c941bbSAndroid Build Coastguard Worker # Compute the mean luma of a center patch. 252*b7c941bbSAndroid Build Coastguard Worker yimg, _, _ = image_processing_utils.convert_capture_to_planes( 253*b7c941bbSAndroid Build Coastguard Worker cap) 254*b7c941bbSAndroid Build Coastguard Worker tile = image_processing_utils.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1) 255*b7c941bbSAndroid Build Coastguard Worker luma_mean = image_processing_utils.compute_image_means(tile) 256*b7c941bbSAndroid Build Coastguard Worker logging.debug('Target exposure cap luma: %.4f', luma_mean[0]) 257*b7c941bbSAndroid Build Coastguard Worker 258*b7c941bbSAndroid Build Coastguard Worker # Compute the exposure value that would result in a luma of 0.5. 259*b7c941bbSAndroid Build Coastguard Worker return sens * exp_time * 0.5 / luma_mean[0] 260*b7c941bbSAndroid Build Coastguard Worker 261