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 15import os 16 17import its.caps 18import its.cv2image 19import its.device 20import its.image 21import its.objects 22import numpy as np 23 24NUM_TRYS = 2 25NUM_STEPS = 6 26SHARPNESS_TOL = 0.1 27POSITION_TOL = 0.1 28FRAME_TIME_TOL = 10 # ms 29VGA_WIDTH = 640 30VGA_HEIGHT = 480 31NAME = os.path.basename(__file__).split('.')[0] 32 33 34def test_lens_position(cam, props, fmt, sensitivity, exp, chart): 35 """Return fd, sharpness, lens state of the output images. 36 37 Args: 38 cam: An open device session. 39 props: Properties of cam 40 fmt: dict; capture format 41 sensitivity: Sensitivity for the 3A request as defined in 42 android.sensor.sensitivity 43 exp: Exposure time for the 3A request as defined in 44 android.sensor.exposureTime 45 chart: Object with chart properties 46 47 Returns: 48 Dictionary of results for different focal distance captures 49 with static lens positions and moving lens positions 50 d_static, d_moving 51 """ 52 53 # initialize variables and take data sets 54 data_static = {} 55 data_moving = {} 56 white_level = int(props['android.sensor.info.whiteLevel']) 57 min_fd = props['android.lens.info.minimumFocusDistance'] 58 hyperfocal = props['android.lens.info.hyperfocalDistance'] 59 fds_f = np.arange(hyperfocal, min_fd, (min_fd-hyperfocal)/(NUM_STEPS-1)) 60 fds_f = np.append(fds_f, min_fd) 61 fds_f = fds_f.tolist() 62 fds_b = list(reversed(fds_f)) 63 fds_fb = list(fds_f) 64 fds_fb.extend(fds_b) # forward and back 65 # take static data set 66 for i, fd in enumerate(fds_fb): 67 req = its.objects.manual_capture_request(sensitivity, exp) 68 req['android.lens.focusDistance'] = fd 69 cap = its.image.stationary_lens_cap(cam, req, fmt) 70 data = {'fd': fds_fb[i]} 71 data['loc'] = cap['metadata']['android.lens.focusDistance'] 72 print ' focus distance (diopters): %.3f' % data['fd'] 73 print ' current lens location (diopters): %.3f' % data['loc'] 74 y, _, _ = its.image.convert_capture_to_planes(cap, props) 75 chart.img = its.image.normalize_img(its.image.get_image_patch( 76 y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm)) 77 its.image.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (NAME, i)) 78 data['sharpness'] = white_level*its.image.compute_image_sharpness( 79 chart.img) 80 print 'Chart sharpness: %.1f\n' % data['sharpness'] 81 data_static[i] = data 82 # take moving data set 83 reqs = [] 84 for i, fd in enumerate(fds_f): 85 reqs.append(its.objects.manual_capture_request(sensitivity, exp)) 86 reqs[i]['android.lens.focusDistance'] = fd 87 caps = cam.do_capture(reqs, fmt) 88 for i, cap in enumerate(caps): 89 data = {'fd': fds_f[i]} 90 data['loc'] = cap['metadata']['android.lens.focusDistance'] 91 data['lens_moving'] = (cap['metadata']['android.lens.state'] 92 == 1) 93 timestamp = cap['metadata']['android.sensor.timestamp'] * 1E-6 94 if i == 0: 95 timestamp_init = timestamp 96 timestamp -= timestamp_init 97 data['timestamp'] = timestamp 98 print ' focus distance (diopters): %.3f' % data['fd'] 99 print ' current lens location (diopters): %.3f' % data['loc'] 100 y, _, _ = its.image.convert_capture_to_planes(cap, props) 101 y = its.image.rotate_img_per_argv(y) 102 chart.img = its.image.normalize_img(its.image.get_image_patch( 103 y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm)) 104 its.image.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (NAME, i)) 105 data['sharpness'] = white_level*its.image.compute_image_sharpness( 106 chart.img) 107 print 'Chart sharpness: %.1f\n' % data['sharpness'] 108 data_moving[i] = data 109 return data_static, data_moving 110 111 112def main(): 113 """Test if focus position is properly reported for moving lenses.""" 114 print '\nStarting test_lens_position.py' 115 # check skip conditions 116 with its.device.ItsSession() as cam: 117 props = cam.get_camera_properties() 118 its.caps.skip_unless(not its.caps.fixed_focus(props)) 119 its.caps.skip_unless(its.caps.read_3a(props) and 120 its.caps.lens_calibrated(props)) 121 # initialize chart class 122 chart = its.cv2image.Chart() 123 124 with its.device.ItsSession() as cam: 125 mono_camera = its.caps.mono_camera(props) 126 fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT} 127 128 # Get proper sensitivity and exposure time with 3A 129 s, e, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera) 130 131 # Get sharpness for each focal distance 132 d_stat, d_move = test_lens_position(cam, props, fmt, s, e, chart) 133 print 'Lens stationary' 134 for k in sorted(d_stat): 135 print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t' 136 'sharpness: %.1f' % (k, d_stat[k]['fd'], 137 d_stat[k]['loc'], 138 d_stat[k]['sharpness'])) 139 print 'Lens moving' 140 for k in sorted(d_move): 141 print ('i: %d\tfd: %.3f\tlens location (diopters): %.3f \t' 142 'sharpness: %.1f \tlens_moving: %r \t' 143 'timestamp: %.1fms' % (k, d_move[k]['fd'], 144 d_move[k]['loc'], 145 d_move[k]['sharpness'], 146 d_move[k]['lens_moving'], 147 d_move[k]['timestamp'])) 148 149 # assert static reported location/sharpness is close 150 print 'Asserting static lens locations/sharpness are similar' 151 for i in range(len(d_stat)/2): 152 j = 2 * NUM_STEPS - 1 - i 153 rw_msg = 'fd_write: %.3f, fd_read: %.3f, RTOL: %.2f' % ( 154 d_stat[i]['fd'], d_stat[i]['loc'], POSITION_TOL) 155 fr_msg = 'loc_fwd: %.3f, loc_rev: %.3f, RTOL: %.2f' % ( 156 d_stat[i]['loc'], d_stat[j]['loc'], POSITION_TOL) 157 s_msg = 'sharpness_fwd: %.3f, sharpness_rev: %.3f, RTOL: %.2f' % ( 158 d_stat[i]['sharpness'], d_stat[j]['sharpness'], 159 SHARPNESS_TOL) 160 assert np.isclose(d_stat[i]['loc'], d_stat[i]['fd'], 161 rtol=POSITION_TOL), rw_msg 162 assert np.isclose(d_stat[i]['loc'], d_stat[j]['loc'], 163 rtol=POSITION_TOL), fr_msg 164 assert np.isclose(d_stat[i]['sharpness'], d_stat[j]['sharpness'], 165 rtol=SHARPNESS_TOL), s_msg 166 # assert moving frames approximately consecutive with even distribution 167 print 'Asserting moving frames are consecutive' 168 times = [v['timestamp'] for v in d_move.itervalues()] 169 diffs = np.gradient(times) 170 assert np.isclose(np.amin(diffs), np.amax(diffs), atol=FRAME_TIME_TOL) 171 # assert reported location/sharpness is correct in moving frames 172 print 'Asserting moving lens locations/sharpness are similar' 173 for i in range(len(d_move)): 174 m_msg = 'static: %.3f, moving: %.3f, RTOL: %.2f' % ( 175 d_stat[i]['loc'], d_move[i]['loc'], POSITION_TOL) 176 assert np.isclose(d_stat[i]['loc'], d_move[i]['loc'], 177 rtol=POSITION_TOL), m_msg 178 if d_move[i]['lens_moving'] and i > 0: 179 if d_stat[i]['sharpness'] > d_stat[i-1]['sharpness']: 180 assert (d_stat[i]['sharpness']*(1.0+SHARPNESS_TOL) > 181 d_move[i]['sharpness'] > 182 d_stat[i-1]['sharpness']*(1.0-SHARPNESS_TOL)) 183 else: 184 assert (d_stat[i-1]['sharpness']*(1.0+SHARPNESS_TOL) > 185 d_move[i]['sharpness'] > 186 d_stat[i]['sharpness']*(1.0-SHARPNESS_TOL)) 187 elif not d_move[i]['lens_moving']: 188 assert np.isclose( 189 d_stat[i]['sharpness'], d_move[i]['sharpness'], 190 rtol=SHARPNESS_TOL) 191 else: 192 raise its.error.Error('Lens is moving at frame 0!') 193 194if __name__ == '__main__': 195 main() 196 197