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