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 15 import its.device 16 import its.caps 17 import its.objects 18 import its.image 19 import os.path 20 import numpy as np 21 from matplotlib import pylab 22 import matplotlib.pyplot 23 24 IMG_STATS_GRID = 9 # find used to find the center 11.11% 25 NAME = os.path.basename(__file__).split(".")[0] 26 NUM_ISO_STEPS = 5 27 SATURATION_TOL = 0.01 28 BLK_LVL_TOL = 0.1 29 # Test 3 steps per 2x exposure 30 EXP_MULT = pow(2, 1.0/3) 31 INCREASING_THR = 0.99 32 # slice captures into burst of SLICE_LEN requests 33 SLICE_LEN = 10 34 35 def 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 160 if __name__ == "__main__": 161 main() 162