1 # Copyright 2016 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 16 17 import its.caps 18 import its.cv2image 19 import its.device 20 import its.image 21 import its.objects 22 import numpy as np 23 24 NUM_IMGS = 12 25 FRAME_TIME_TOL = 10 # ms 26 SHARPNESS_TOL = 0.10 # percentage 27 POSITION_TOL = 0.10 # percentage 28 VGA_WIDTH = 640 29 VGA_HEIGHT = 480 30 NAME = os.path.basename(__file__).split('.')[0] 31 32 33 def test_lens_movement_reporting(cam, props, fmt, gain, exp, af_fd, chart): 34 """Return fd, sharpness, lens state of the output images. 35 36 Args: 37 cam: An open device session. 38 props: Properties of cam 39 fmt: dict; capture format 40 gain: Sensitivity for the 3A request as defined in 41 android.sensor.sensitivity 42 exp: Exposure time for the 3A request as defined in 43 android.sensor.exposureTime 44 af_fd: Focus distance for the 3A request as defined in 45 android.lens.focusDistance 46 chart: Object that contains chart information 47 48 Returns: 49 Object containing reported sharpness of the output image, keyed by 50 the following string: 51 'sharpness' 52 """ 53 54 # initialize variables and take data sets 55 data_set = {} 56 white_level = int(props['android.sensor.info.whiteLevel']) 57 min_fd = props['android.lens.info.minimumFocusDistance'] 58 fds = [af_fd, min_fd] 59 fds = sorted(fds * NUM_IMGS) 60 reqs = [] 61 for i, fd in enumerate(fds): 62 reqs.append(its.objects.manual_capture_request(gain, exp)) 63 reqs[i]['android.lens.focusDistance'] = fd 64 caps = cam.do_capture(reqs, fmt) 65 for i, cap in enumerate(caps): 66 data = {'fd': fds[i]} 67 data['loc'] = cap['metadata']['android.lens.focusDistance'] 68 data['lens_moving'] = (cap['metadata']['android.lens.state'] 69 == 1) 70 timestamp = cap['metadata']['android.sensor.timestamp'] 71 if i == 0: 72 timestamp_init = timestamp 73 timestamp -= timestamp_init 74 timestamp *= 1E-6 75 data['timestamp'] = timestamp 76 print ' focus distance (diopters): %.3f' % data['fd'] 77 print ' current lens location (diopters): %.3f' % data['loc'] 78 print ' lens moving %r' % data['lens_moving'] 79 y, _, _ = its.image.convert_capture_to_planes(cap, props) 80 y = its.image.rotate_img_per_argv(y) 81 chart.img = its.image.normalize_img(its.image.get_image_patch( 82 y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm)) 83 its.image.write_image(chart.img, '%s_i=%d_chart.jpg' % (NAME, i)) 84 data['sharpness'] = white_level*its.image.compute_image_sharpness( 85 chart.img) 86 print 'Chart sharpness: %.1f\n' % data['sharpness'] 87 data_set[i] = data 88 return data_set 89 90 91 def main(): 92 """Test if focus distance is properly reported. 93 94 Capture images at a variety of focus locations. 95 """ 96 97 print '\nStarting test_lens_movement_reporting.py' 98 # check skip conditions 99 with its.device.ItsSession() as cam: 100 props = cam.get_camera_properties() 101 its.caps.skip_unless(not its.caps.fixed_focus(props)) 102 its.caps.skip_unless(its.caps.read_3a(props) and 103 its.caps.lens_approx_calibrated(props)) 104 # initialize chart class 105 chart = its.cv2image.Chart() 106 107 with its.device.ItsSession() as cam: 108 mono_camera = its.caps.mono_camera(props) 109 min_fd = props['android.lens.info.minimumFocusDistance'] 110 fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT} 111 112 # Get proper sensitivity, exposure time, and focus distance with 3A. 113 s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera) 114 115 # Get sharpness for each focal distance 116 d = test_lens_movement_reporting(cam, props, fmt, s, e, fd, chart) 117 for k in sorted(d): 118 print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t' 119 'sharpness: %.1f \tlens_moving: %r \t' 120 'timestamp: %.1fms' % (k, d[k]['fd'], d[k]['loc'], 121 d[k]['sharpness'], 122 d[k]['lens_moving'], 123 d[k]['timestamp'])) 124 125 # assert frames are consecutive 126 print 'Asserting frames are consecutive' 127 times = [v['timestamp'] for v in d.itervalues()] 128 diffs = np.gradient(times) 129 assert np.isclose(np.amax(diffs)-np.amax(diffs), 0, atol=FRAME_TIME_TOL) 130 131 # remove data when lens is moving 132 for k in sorted(d): 133 if d[k]['lens_moving']: 134 del d[k] 135 136 # split data into min_fd and af data for processing 137 d_min_fd = {} 138 d_af_fd = {} 139 for k in sorted(d): 140 if d[k]['fd'] == min_fd: 141 d_min_fd[k] = d[k] 142 if d[k]['fd'] == fd: 143 d_af_fd[k] = d[k] 144 145 # assert reported locations are close at af_fd 146 print 'Asserting lens location of af_fd data' 147 min_loc = min([v['loc'] for v in d_af_fd.itervalues()]) 148 max_loc = max([v['loc'] for v in d_af_fd.itervalues()]) 149 assert np.isclose(min_loc, max_loc, rtol=POSITION_TOL) 150 # assert reported sharpness is close at af_fd 151 print 'Asserting sharpness of af_fd data' 152 min_sharp = min([v['sharpness'] for v in d_af_fd.itervalues()]) 153 max_sharp = max([v['sharpness'] for v in d_af_fd.itervalues()]) 154 assert np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_TOL) 155 # assert reported location is close to assign location for af_fd 156 print 'Asserting lens location close to assigned fd for af_fd data' 157 first_key = min(d_af_fd.keys()) # finds 1st non-moving frame 158 assert np.isclose(d_af_fd[first_key]['loc'], d_af_fd[first_key]['fd'], 159 rtol=POSITION_TOL) 160 161 # assert reported location is close for min_fd captures 162 print 'Asserting lens location similar min_fd data' 163 min_loc = min([v['loc'] for v in d_min_fd.itervalues()]) 164 max_loc = max([v['loc'] for v in d_min_fd.itervalues()]) 165 assert np.isclose(min_loc, max_loc, rtol=POSITION_TOL) 166 # assert reported sharpness is close at min_fd 167 print 'Asserting sharpness of min_fd data' 168 min_sharp = min([v['sharpness'] for v in d_min_fd.itervalues()]) 169 max_sharp = max([v['sharpness'] for v in d_min_fd.itervalues()]) 170 assert np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_TOL) 171 # assert reported location is close to assign location for min_fd 172 print 'Asserting lens location close to assigned fd for min_fd data' 173 assert np.isclose(d_min_fd[NUM_IMGS*2-1]['loc'], 174 d_min_fd[NUM_IMGS*2-1]['fd'], rtol=POSITION_TOL) 175 176 177 if __name__ == '__main__': 178 main() 179