1# Copyright 2018 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 os.path 16 17import its.caps 18import its.device 19import its.image 20import its.objects 21 22import matplotlib 23from matplotlib import pylab 24import numpy as np 25 26AE_STATE_CONVERGED = 2 27CONTROL_AE_STATE_FLASH_REQUIRED = 4 28NAME = os.path.basename(__file__).split('.')[0] 29NUM_CAPTURE = 30 30VALID_STABLE_LUMA_MIN = 0.1 31VALID_STABLE_LUMA_MAX = 0.9 32 33 34def is_awb_af_stable(prev_cap, cap): 35 awb_gains_0 = prev_cap['metadata']['android.colorCorrection.gains'] 36 awb_gains_1 = cap['metadata']['android.colorCorrection.gains'] 37 ccm_0 = prev_cap['metadata']['android.colorCorrection.transform'] 38 ccm_1 = cap['metadata']['android.colorCorrection.transform'] 39 focus_distance_0 = prev_cap['metadata']['android.lens.focusDistance'] 40 focus_distance_1 = cap['metadata']['android.lens.focusDistance'] 41 42 return (np.allclose(awb_gains_0, awb_gains_1, rtol=0.01) and 43 ccm_0 == ccm_1 and 44 np.isclose(focus_distance_0, focus_distance_1, rtol=0.01)) 45 46 47def main(): 48 """Tests PER_FRAME_CONTROL properties for auto capture requests. 49 50 If debug is required, MANUAL_POSTPROCESSING capability is implied 51 since its.caps.read_3a is valid for test. Debug can performed with 52 a defined tonemap curve: 53 req['android.tonemap.mode'] = 0 54 gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[]) 55 req['android.tonemap.curve'] = { 56 'red': gamma, 'green': gamma, 'blue': gamma} 57 """ 58 59 with its.device.ItsSession() as cam: 60 props = cam.get_camera_properties() 61 its.caps.skip_unless(its.caps.per_frame_control(props) and 62 its.caps.read_3a(props)) 63 64 debug = its.caps.debug_mode() 65 largest_yuv = its.objects.get_largest_yuv_format(props) 66 if debug: 67 fmt = largest_yuv 68 else: 69 match_ar = (largest_yuv['width'], largest_yuv['height']) 70 fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar) 71 72 req = its.objects.auto_capture_request() 73 caps = cam.do_capture([req]*NUM_CAPTURE, fmt) 74 75 total_gains = [] 76 lumas = [] 77 ae_states = [] 78 for i, cap in enumerate(caps): 79 print '=========== frame %d ==========' % i 80 y = its.image.convert_capture_to_planes(cap)[0] 81 tile = its.image.get_image_patch(y, 0.45, 0.45, 0.1, 0.1) 82 luma = its.image.compute_image_means(tile)[0] 83 84 ae_state = cap['metadata']['android.control.aeState'] 85 iso = cap['metadata']['android.sensor.sensitivity'] 86 isp_gain = cap['metadata']['android.control.postRawSensitivityBoost'] 87 exp_time = cap['metadata']['android.sensor.exposureTime'] 88 total_gain = iso*isp_gain/100.0*exp_time/1000000.0 89 awb_state = cap['metadata']['android.control.awbState'] 90 awb_gains = cap['metadata']['android.colorCorrection.gains'] 91 ccm = cap['metadata']['android.colorCorrection.transform'] 92 focus_distance = cap['metadata']['android.lens.focusDistance'] 93 94 # Convert CCM from rational to float, as numpy arrays. 95 awb_ccm = np.array(its.objects.rational_to_float(ccm)).reshape(3, 3) 96 97 print 'AE: %d ISO: %d ISP_sen: %d exp(ms): %d tot_gain: %f' % ( 98 ae_state, iso, isp_gain, exp_time, total_gain), 99 print 'luma: %f' % luma 100 print 'fd: %f' % focus_distance 101 print 'AWB: %d, AWB gains: %s\n AWB matrix: %s' % ( 102 awb_state, str(awb_gains), str(awb_ccm)) 103 print 'Tonemap curve:', cap['metadata']['android.tonemap.curve'] 104 105 lumas.append(luma) 106 total_gains.append(total_gain) 107 ae_states.append(ae_state) 108 img = its.image.convert_capture_to_rgb_image(cap) 109 its.image.write_image(img, '%s_frame_%d.jpg'% (NAME, i)) 110 111 norm_gains = [x / max(total_gains) * max(lumas) for x in total_gains] 112 pylab.plot(range(len(lumas)), lumas, '-g.', 113 label='Center patch brightness') 114 pylab.plot(range(len(norm_gains)), norm_gains, '-r.', 115 label='Metadata AE setting product') 116 pylab.title(NAME) 117 pylab.xlabel('frame index') 118 pylab.legend() 119 matplotlib.pyplot.savefig('%s_plot.png' % (NAME)) 120 121 for i in range(1, len(caps)): 122 if is_awb_af_stable(caps[i-1], caps[i]): 123 prev_total_gain = total_gains[i-1] 124 total_gain = total_gains[i] 125 delta_gain = total_gain - prev_total_gain 126 prev_luma = lumas[i-1] 127 luma = lumas[i] 128 delta_luma = luma - prev_luma 129 # luma and total_gain should change in same direction 130 msg = 'Frame %d to frame %d:' % (i-1, i) 131 msg += ' metadata gain %f->%f (%s), luma %f->%f (%s)' % ( 132 prev_total_gain, total_gain, 133 'increasing' if delta_gain > 0.0 else 'decreasing', 134 prev_luma, luma, 135 'increasing' if delta_luma > 0.0 else 'decreasing') 136 assert delta_gain * delta_luma >= 0.0, msg 137 else: 138 print 'Frame %d->%d AWB/AF changed' % (i-1, i) 139 140 for i in range(len(lumas)): 141 luma = lumas[i] 142 ae_state = ae_states[i] 143 if (ae_state == AE_STATE_CONVERGED or 144 ae_state == CONTROL_AE_STATE_FLASH_REQUIRED): 145 msg = 'Frame %d AE converged luma %f. valid range: (%f, %f)' % ( 146 i, luma, VALID_STABLE_LUMA_MIN, VALID_STABLE_LUMA_MAX) 147 assert VALID_STABLE_LUMA_MIN < luma < VALID_STABLE_LUMA_MAX, msg 148 149if __name__ == '__main__': 150 main() 151