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 os.path 16 17 import its.caps 18 import its.device 19 import its.image 20 import its.objects 21 import its.target 22 import matplotlib 23 from matplotlib import pylab 24 import numpy 25 26 IMG_STATS_GRID = 9 # find used to find the center 11.11% 27 NAME = os.path.basename(__file__).split('.')[0] 28 THRESHOLD_MAX_OUTLIER_DIFF = 0.1 29 THRESHOLD_MIN_LEVEL = 0.1 30 THRESHOLD_MAX_LEVEL = 0.9 31 THRESHOLD_MAX_LEVEL_DIFF = 0.045 32 THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06 33 THRESHOLD_ROUND_DOWN_GAIN = 0.1 34 THRESHOLD_ROUND_DOWN_EXP = 0.05 35 36 37 def get_raw_active_array_size(props): 38 """Return the active array w, h from props.""" 39 aaw = (props['android.sensor.info.activeArraySize']['right'] - 40 props['android.sensor.info.activeArraySize']['left']) 41 aah = (props['android.sensor.info.activeArraySize']['bottom'] - 42 props['android.sensor.info.activeArraySize']['top']) 43 return aaw, aah 44 45 46 def main(): 47 """Test that a constant exposure is seen as ISO and exposure time vary. 48 49 Take a series of shots that have ISO and exposure time chosen to balance 50 each other; result should be the same brightness, but over the sequence 51 the images should get noisier. 52 """ 53 mults = [] 54 r_means = [] 55 g_means = [] 56 b_means = [] 57 raw_r_means = [] 58 raw_gr_means = [] 59 raw_gb_means = [] 60 raw_b_means = [] 61 threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF 62 63 with its.device.ItsSession() as cam: 64 props = cam.get_camera_properties() 65 its.caps.skip_unless(its.caps.compute_target_exposure(props) and 66 its.caps.per_frame_control(props)) 67 68 process_raw = (its.caps.compute_target_exposure(props) and 69 its.caps.per_frame_control(props) and 70 its.caps.raw16(props) and 71 its.caps.manual_sensor(props)) 72 73 debug = its.caps.debug_mode() 74 largest_yuv = its.objects.get_largest_yuv_format(props) 75 if debug: 76 fmt = largest_yuv 77 else: 78 match_ar = (largest_yuv['width'], largest_yuv['height']) 79 fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar) 80 81 e, s = its.target.get_target_exposure_combos(cam)['minSensitivity'] 82 s_e_product = s*e 83 expt_range = props['android.sensor.info.exposureTimeRange'] 84 sens_range = props['android.sensor.info.sensitivityRange'] 85 86 m = 1.0 87 while s*m < sens_range[1] and e/m > expt_range[0]: 88 mults.append(m) 89 s_test = round(s*m) 90 e_test = s_e_product / s_test 91 print 'Testing s:', s_test, 'e:', e_test 92 req = its.objects.manual_capture_request( 93 s_test, e_test, 0.0, True, props) 94 cap = cam.do_capture(req, fmt) 95 s_res = cap['metadata']['android.sensor.sensitivity'] 96 e_res = cap['metadata']['android.sensor.exposureTime'] 97 assert 0 <= s_test - s_res < s_test * THRESHOLD_ROUND_DOWN_GAIN 98 assert 0 <= e_test - e_res < e_test * THRESHOLD_ROUND_DOWN_EXP 99 s_e_product_res = s_res * e_res 100 request_result_ratio = s_e_product / s_e_product_res 101 print 'Capture result s:', s_test, 'e:', e_test 102 img = its.image.convert_capture_to_rgb_image(cap) 103 its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m)) 104 tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1) 105 rgb_means = its.image.compute_image_means(tile) 106 # Adjust for the difference between request and result 107 r_means.append(rgb_means[0] * request_result_ratio) 108 g_means.append(rgb_means[1] * request_result_ratio) 109 b_means.append(rgb_means[2] * request_result_ratio) 110 # do same in RAW space if possible 111 if process_raw and debug: 112 aaw, aah = get_raw_active_array_size(props) 113 raw_cap = cam.do_capture(req, 114 {'format': 'rawStats', 115 'gridWidth': aaw/IMG_STATS_GRID, 116 'gridHeight': aah/IMG_STATS_GRID}) 117 r, gr, gb, b = its.image.convert_capture_to_planes(raw_cap, 118 props) 119 raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2] 120 * request_result_ratio) 121 raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2] 122 * request_result_ratio) 123 raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2] 124 * request_result_ratio) 125 raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2] 126 * request_result_ratio) 127 # Test 3 steps per 2x gain 128 m *= pow(2, 1.0 / 3) 129 130 # Allow more threshold for devices with wider exposure range 131 if m >= 64.0: 132 threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE 133 134 # Draw plots 135 pylab.figure('rgb data') 136 pylab.plot(mults, r_means, 'ro-') 137 pylab.plot(mults, g_means, 'go-') 138 pylab.plot(mults, b_means, 'bo-') 139 pylab.title(NAME + 'RGB Data') 140 pylab.xlabel('Gain Multiplier') 141 pylab.ylabel('Normalized RGB Plane Avg') 142 pylab.ylim([0, 1]) 143 matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME)) 144 145 if process_raw and debug: 146 pylab.figure('raw data') 147 pylab.plot(mults, raw_r_means, 'ro-', label='R') 148 pylab.plot(mults, raw_gr_means, 'go-', label='GR') 149 pylab.plot(mults, raw_gb_means, 'ko-', label='GB') 150 pylab.plot(mults, raw_b_means, 'bo-', label='B') 151 pylab.title(NAME + 'RAW Data') 152 pylab.xlabel('Gain Multiplier') 153 pylab.ylabel('Normalized RAW Plane Avg') 154 pylab.ylim([0, 1]) 155 pylab.legend(numpoints=1) 156 matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME)) 157 158 # Check for linearity. Verify sample pixel mean values are close to each 159 # other. Also ensure that the images aren't clamped to 0 or 1 160 # (which would make them look like flat lines). 161 for chan in xrange(3): 162 values = [r_means, g_means, b_means][chan] 163 m, b = numpy.polyfit(mults, values, 1).tolist() 164 max_val = max(values) 165 min_val = min(values) 166 max_diff = max_val - min_val 167 print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b) 168 print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff) 169 assert max_diff < threshold_max_level_diff 170 assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL 171 for v in values: 172 assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL 173 assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF 174 if process_raw and debug: 175 for chan in xrange(4): 176 values = [raw_r_means, raw_gr_means, raw_gb_means, 177 raw_b_means][chan] 178 m, b = numpy.polyfit(mults, values, 1).tolist() 179 max_val = max(values) 180 min_val = min(values) 181 max_diff = max_val - min_val 182 print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, 183 m, b) 184 print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff) 185 assert max_diff < threshold_max_level_diff 186 assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL 187 for v in values: 188 assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL 189 assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF 190 191 if __name__ == '__main__': 192 main() 193