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