Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 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 |
Yin-Chia Yeh | f350657 | 2016-10-10 15:46:46 -0700 | [diff] [blame] | 16 | |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 17 | import its.caps |
Yin-Chia Yeh | b2a3865 | 2016-10-14 16:41:06 -0700 | [diff] [blame] | 18 | import its.cv2image |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 19 | import its.device |
| 20 | import its.image |
| 21 | import its.objects |
| 22 | import numpy as np |
| 23 | |
| 24 | NUM_TRYS = 2 |
| 25 | NUM_STEPS = 6 |
| 26 | SHARPNESS_TOL = 10 # percentage |
| 27 | POSITION_TOL = 10 # percentage |
| 28 | FRAME_TIME_TOL = 10 # ms |
| 29 | VGA_WIDTH = 640 |
| 30 | VGA_HEIGHT = 480 |
| 31 | NAME = os.path.basename(__file__).split('.')[0] |
Clemenz Portmann | 1475df3 | 2016-10-06 07:59:11 -0700 | [diff] [blame] | 32 | CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules', 'its', |
| 33 | 'test_images', 'ISO12233.png') |
| 34 | CHART_HEIGHT = 13.5 # cm |
| 35 | CHART_DISTANCE = 30.0 # cm |
| 36 | CHART_SCALE_START = 0.65 |
| 37 | CHART_SCALE_STOP = 1.35 |
| 38 | CHART_SCALE_STEP = 0.025 |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 39 | |
| 40 | |
| 41 | def 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 | |
Clemenz Portmann | 1475df3 | 2016-10-06 07:59:11 -0700 | [diff] [blame] | 61 | # initialize chart class |
Yin-Chia Yeh | b2a3865 | 2016-10-14 16:41:06 -0700 | [diff] [blame] | 62 | chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE, |
| 63 | CHART_SCALE_START, CHART_SCALE_STOP, |
| 64 | CHART_SCALE_STEP) |
Clemenz Portmann | 1475df3 | 2016-10-06 07:59:11 -0700 | [diff] [blame] | 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 |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 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 |
Yin-Chia Yeh | f350657 | 2016-10-10 15:46:46 -0700 | [diff] [blame] | 104 | caps = cam.do_capture(reqs, fmt) |
| 105 | for i, cap in enumerate(caps): |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 106 | data = {'fd': fds_f[i]} |
Yin-Chia Yeh | f350657 | 2016-10-10 15:46:46 -0700 | [diff] [blame] | 107 | data['loc'] = cap['metadata']['android.lens.focusDistance'] |
| 108 | data['lens_moving'] = (cap['metadata']['android.lens.state'] |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 109 | == 1) |
Yin-Chia Yeh | f350657 | 2016-10-10 15:46:46 -0700 | [diff] [blame] | 110 | timestamp = cap['metadata']['android.sensor.timestamp'] * 1E-6 |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 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'] |
Yin-Chia Yeh | f350657 | 2016-10-10 15:46:46 -0700 | [diff] [blame] | 117 | y, _, _ = its.image.convert_capture_to_planes(cap, props) |
| 118 | y = its.image.flip_mirror_img_per_argv(y) |
Clemenz Portmann | 812236f | 2016-07-19 17:51:44 -0700 | [diff] [blame] | 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 | |
| 129 | def 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 | |
| 198 | if __name__ == '__main__': |
| 199 | main() |
| 200 | |