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 its.device 16import its.caps 17import its.objects 18import its.image 19import os.path 20import numpy as np 21from matplotlib import pylab 22import matplotlib.pyplot 23 24IMG_STATS_GRID = 9 # find used to find the center 11.11% 25NAME = os.path.basename(__file__).split(".")[0] 26NUM_ISO_STEPS = 5 27SATURATION_TOL = 0.01 28BLK_LVL_TOL = 0.1 29# Test 3 steps per 2x exposure 30EXP_MULT = pow(2, 1.0/3) 31INCREASING_THR = 0.99 32# slice captures into burst of SLICE_LEN requests 33SLICE_LEN = 10 34 35def main(): 36 """Capture a set of raw images with increasing exposure time and measure the pixel values. 37 """ 38 39 with its.device.ItsSession() as cam: 40 41 props = cam.get_camera_properties() 42 its.caps.skip_unless(its.caps.raw16(props) and 43 its.caps.manual_sensor(props) and 44 its.caps.read_3a(props) and 45 its.caps.per_frame_control(props) and 46 not its.caps.mono_camera(props)) 47 debug = its.caps.debug_mode() 48 49 # Expose for the scene with min sensitivity 50 exp_min, exp_max = props["android.sensor.info.exposureTimeRange"] 51 sens_min, _ = props["android.sensor.info.sensitivityRange"] 52 # Digital gains might not be visible on RAW data 53 sens_max = props["android.sensor.maxAnalogSensitivity"] 54 sens_step = (sens_max - sens_min) / NUM_ISO_STEPS 55 white_level = float(props["android.sensor.info.whiteLevel"]) 56 black_levels = [its.image.get_black_level(i,props) for i in range(4)] 57 # Get the active array width and height. 58 aax = props["android.sensor.info.activeArraySize"]["left"] 59 aay = props["android.sensor.info.activeArraySize"]["top"] 60 aaw = props["android.sensor.info.activeArraySize"]["right"]-aax 61 aah = props["android.sensor.info.activeArraySize"]["bottom"]-aay 62 raw_stat_fmt = {"format": "rawStats", 63 "gridWidth": aaw/IMG_STATS_GRID, 64 "gridHeight": aah/IMG_STATS_GRID} 65 66 e_test = [] 67 mult = 1.0 68 while exp_min*mult < exp_max: 69 e_test.append(int(exp_min*mult)) 70 mult *= EXP_MULT 71 if e_test[-1] < exp_max * INCREASING_THR: 72 e_test.append(int(exp_max)) 73 e_test_ms = [e / 1000000.0 for e in e_test] 74 75 for s in range(sens_min, sens_max, sens_step): 76 means = [] 77 means.append(black_levels) 78 reqs = [its.objects.manual_capture_request(s, e, 0) for e in e_test] 79 # Capture raw in debug mode, rawStats otherwise 80 caps = [] 81 for i in range(len(reqs) / SLICE_LEN): 82 if debug: 83 caps += cam.do_capture(reqs[i*SLICE_LEN:(i+1)*SLICE_LEN], cam.CAP_RAW) 84 else: 85 caps += cam.do_capture(reqs[i*SLICE_LEN:(i+1)*SLICE_LEN], raw_stat_fmt) 86 last_n = len(reqs) % SLICE_LEN 87 if last_n == 1: 88 if debug: 89 caps += [cam.do_capture(reqs[-last_n:], cam.CAP_RAW)] 90 else: 91 caps += [cam.do_capture(reqs[-last_n:], raw_stat_fmt)] 92 elif last_n > 0: 93 if debug: 94 caps += cam.do_capture(reqs[-last_n:], cam.CAP_RAW) 95 else: 96 caps += cam.do_capture(reqs[-last_n:], raw_stat_fmt) 97 98 # Measure the mean of each channel. 99 # Each shot should be brighter (except underexposed/overexposed scene) 100 for i,cap in enumerate(caps): 101 if debug: 102 planes = its.image.convert_capture_to_planes(cap, props) 103 tiles = [its.image.get_image_patch(p, 0.445, 0.445, 0.11, 0.11) for p in planes] 104 mean = [m * white_level for tile in tiles 105 for m in its.image.compute_image_means(tile)] 106 img = its.image.convert_capture_to_rgb_image(cap, props=props) 107 its.image.write_image(img, "%s_s=%d_e=%05d.jpg" % (NAME, s, e_test)) 108 else: 109 mean_image, _ = its.image.unpack_rawstats_capture(cap) 110 mean = mean_image[IMG_STATS_GRID/2, IMG_STATS_GRID/2] 111 112 print "ISO=%d, exposure time=%.3fms, mean=%s" % ( 113 s, e_test[i] / 1000000.0, str(mean)) 114 means.append(mean) 115 116 117 # means[0] is black level value 118 r = [m[0] for m in means[1:]] 119 gr = [m[1] for m in means[1:]] 120 gb = [m[2] for m in means[1:]] 121 b = [m[3] for m in means[1:]] 122 123 pylab.plot(e_test_ms, r, "r.-") 124 pylab.plot(e_test_ms, b, "b.-") 125 pylab.plot(e_test_ms, gr, "g.-") 126 pylab.plot(e_test_ms, gb, "k.-") 127 pylab.xscale('log') 128 pylab.yscale('log') 129 pylab.title("%s ISO=%d" % (NAME, s)) 130 pylab.xlabel("Exposure time (ms)") 131 pylab.ylabel("Center patch pixel mean") 132 matplotlib.pyplot.savefig("%s_s=%d.png" % (NAME, s)) 133 pylab.clf() 134 135 allow_under_saturated = True 136 for i in xrange(1, len(means)): 137 prev_mean = means[i-1] 138 mean = means[i] 139 140 if np.isclose(max(mean), white_level, rtol=SATURATION_TOL): 141 print "Saturated: white_level %f, max_mean %f"% (white_level, max(mean)) 142 break; 143 144 if allow_under_saturated and np.allclose(mean, black_levels, rtol=BLK_LVL_TOL): 145 # All channel means are close to black level 146 continue 147 148 allow_under_saturated = False 149 # Check pixel means are increasing (with small tolerance) 150 channels = ["Red", "Gr", "Gb", "Blue"] 151 for chan in range(4): 152 err_msg = "ISO=%d, %s, exptime %3fms mean: %.2f, %s mean: %.2f, TOL=%.f%%" % ( 153 s, channels[chan], 154 e_test_ms[i-1], mean[chan], 155 "black level" if i == 1 else "exptime %3fms"%e_test_ms[i-2], 156 prev_mean[chan], 157 INCREASING_THR*100) 158 assert mean[chan] > prev_mean[chan] * INCREASING_THR, err_msg 159 160if __name__ == "__main__": 161 main() 162