• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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