You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
9.5 KiB
233 lines
9.5 KiB
# Copyright 2016 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Verifies android.lens.focusDistance for lens moving and stationary."""
|
|
|
|
|
|
import logging
|
|
import os
|
|
from mobly import test_runner
|
|
import numpy as np
|
|
|
|
import its_base_test
|
|
import camera_properties_utils
|
|
import capture_request_utils
|
|
import error_util
|
|
import image_processing_utils
|
|
import its_session_utils
|
|
import opencv_processing_utils
|
|
|
|
FRAME_ATOL_MS = 10 # ms
|
|
LENS_MOVING_STATE = 1
|
|
NAME = os.path.splitext(os.path.basename(__file__))[0]
|
|
NSEC_TO_MSEC = 1.0E-6
|
|
NUM_TRYS = 2
|
|
NUM_STEPS = 6
|
|
POSITION_RTOL = 0.1
|
|
SHARPNESS_RTOL = 0.1
|
|
VGA_W, VGA_H = 640, 480
|
|
|
|
|
|
def assert_static_frames_behavior(d_stat):
|
|
"""Assert locations/sharpness are correct in static frames."""
|
|
logging.debug('Asserting static lens locations/sharpness are similar')
|
|
for i in range(len(d_stat) // 2):
|
|
j = 2 * NUM_STEPS - 1 - i
|
|
rw_msg = 'fd_write: %.3f, fd_read: %.3f, RTOL: %.2f' % (
|
|
d_stat[i]['fd'], d_stat[i]['loc'], POSITION_RTOL)
|
|
fr_msg = 'loc_fwd[%d]: %.3f, loc_rev[%d]: %.3f, RTOL: %.2f' % (
|
|
i, d_stat[i]['loc'], j, d_stat[j]['loc'], POSITION_RTOL)
|
|
s_msg = 'sharpness_fwd: %.3f, sharpness_rev: %.3f, RTOL: %.2f' % (
|
|
d_stat[i]['sharpness'], d_stat[j]['sharpness'], SHARPNESS_RTOL)
|
|
assert np.isclose(d_stat[i]['loc'], d_stat[i]['fd'],
|
|
rtol=POSITION_RTOL), rw_msg
|
|
assert np.isclose(d_stat[i]['loc'], d_stat[j]['loc'],
|
|
rtol=POSITION_RTOL), fr_msg
|
|
assert np.isclose(d_stat[i]['sharpness'], d_stat[j]['sharpness'],
|
|
rtol=SHARPNESS_RTOL), s_msg
|
|
|
|
|
|
def assert_moving_frames_behavior(d_move, d_stat):
|
|
"""Assert locations/sharpness are correct for consecutive moving frames."""
|
|
logging.debug('Asserting moving frames are consecutive')
|
|
times = [v['timestamp'] for v in d_move.values()]
|
|
diffs = np.gradient(times)
|
|
assert np.isclose(np.amin(diffs), np.amax(diffs),
|
|
atol=FRAME_ATOL_MS), 'ATOL(ms): %.1f' % FRAME_ATOL_MS
|
|
|
|
logging.debug('Asserting moving lens locations/sharpness are similar')
|
|
for i in range(len(d_move)):
|
|
e_msg = 'static: %.3f, moving: %.3f, RTOL: %.2f' % (
|
|
d_stat[i]['loc'], d_move[i]['loc'], POSITION_RTOL)
|
|
assert np.isclose(d_stat[i]['loc'], d_move[i]['loc'],
|
|
rtol=POSITION_RTOL), e_msg
|
|
if d_move[i]['lens_moving'] and i > 0:
|
|
e_msg = '%d sharpness[stat]: %.2f ' % (i-1, d_stat[i-1]['sharpness'])
|
|
e_msg += '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
|
|
i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
|
|
if d_stat[i]['sharpness'] > d_stat[i-1]['sharpness']:
|
|
assert (d_stat[i]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
|
|
d_move[i]['sharpness'] > d_stat[i-1]['sharpness'] *
|
|
(1.0 - SHARPNESS_RTOL)), e_msg
|
|
else:
|
|
assert (d_stat[i-1]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
|
|
d_move[i]['sharpness'] > d_stat[i]['sharpness'] *
|
|
(1.0 - SHARPNESS_RTOL)), e_msg
|
|
elif not d_move[i]['lens_moving']:
|
|
e_msg = '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
|
|
i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
|
|
assert np.isclose(d_stat[i]['sharpness'], d_move[i]['sharpness'],
|
|
rtol=SHARPNESS_RTOL), e_msg
|
|
else:
|
|
raise error_util.Error('Lens is moving at frame 0!')
|
|
|
|
|
|
def take_caps_and_return_data(cam, props, fmt, sens, exp, chart, log_path):
|
|
"""Return fd, sharpness, lens state of the output images.
|
|
|
|
Args:
|
|
cam: An open device session
|
|
props: Properties of cam
|
|
fmt: Dict for capture format
|
|
sens: Sensitivity for 3A request as defined in android.sensor.sensitivity
|
|
exp: Exposure time for 3A request as defined in android.sensor.exposureTime
|
|
chart: Object with chart properties
|
|
log_path: Location to save images
|
|
|
|
Returns:
|
|
Dictionary of results for different focal distance captures with static
|
|
lens positions and moving lens positions: d_static, d_moving
|
|
"""
|
|
|
|
# initialize variables and take data sets
|
|
data_static = {}
|
|
data_moving = {}
|
|
white_level = int(props['android.sensor.info.whiteLevel'])
|
|
min_fd = props['android.lens.info.minimumFocusDistance']
|
|
hyperfocal = props['android.lens.info.hyperfocalDistance']
|
|
# create forward + back list of focal distances
|
|
fds_f = np.arange(hyperfocal, min_fd, (min_fd-hyperfocal)/(NUM_STEPS-1))
|
|
fds_f = np.append(fds_f, min_fd)
|
|
fds_fb = list(fds_f) + list(reversed(fds_f))
|
|
|
|
# take static data set
|
|
for i, fd in enumerate(fds_fb):
|
|
req = capture_request_utils.manual_capture_request(sens, exp)
|
|
req['android.lens.focusDistance'] = fd
|
|
cap = image_processing_utils.stationary_lens_cap(cam, req, fmt)
|
|
data = {'fd': fds_fb[i]}
|
|
data['loc'] = cap['metadata']['android.lens.focusDistance']
|
|
y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
|
|
chart.img = image_processing_utils.normalize_img(
|
|
image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
|
|
chart.wnorm, chart.hnorm))
|
|
image_processing_utils.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (
|
|
os.path.join(log_path, NAME), i))
|
|
data['sharpness'] = white_level*image_processing_utils.compute_image_sharpness(
|
|
chart.img)
|
|
data_static[i] = data
|
|
|
|
# take moving data set
|
|
reqs = []
|
|
for i, fd in enumerate(fds_f):
|
|
reqs.append(capture_request_utils.manual_capture_request(sens, exp))
|
|
reqs[i]['android.lens.focusDistance'] = fd
|
|
caps = cam.do_capture(reqs, fmt)
|
|
for i, cap in enumerate(caps):
|
|
data = {'fd': fds_f[i]}
|
|
data['loc'] = cap['metadata']['android.lens.focusDistance']
|
|
data['lens_moving'] = (
|
|
cap['metadata']['android.lens.state'] == LENS_MOVING_STATE)
|
|
timestamp = cap['metadata']['android.sensor.timestamp'] * NSEC_TO_MSEC
|
|
if i == 0:
|
|
timestamp_init = timestamp
|
|
timestamp -= timestamp_init
|
|
data['timestamp'] = timestamp
|
|
y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
|
|
y = image_processing_utils.rotate_img_per_argv(y)
|
|
chart.img = image_processing_utils.normalize_img(
|
|
image_processing_utils.get_image_patch(
|
|
y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
|
|
image_processing_utils.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (
|
|
os.path.join(log_path, NAME), i))
|
|
data['sharpness'] = (
|
|
white_level * image_processing_utils.compute_image_sharpness(chart.img))
|
|
data_moving[i] = data
|
|
return data_static, data_moving
|
|
|
|
|
|
class LensPositionReportingTest(its_base_test.ItsBaseTest):
|
|
"""Test if focus position is properly reported for moving lenses."""
|
|
|
|
def test_lens_position_reporting(self):
|
|
logging.debug('Starting %s', NAME)
|
|
with its_session_utils.ItsSession(
|
|
device_id=self.dut.serial,
|
|
camera_id=self.camera_id,
|
|
hidden_physical_id=self.hidden_physical_id) as cam:
|
|
chart_loc_arg = self.chart_loc_arg
|
|
props = cam.get_camera_properties()
|
|
props = cam.override_with_hidden_physical_camera_props(props)
|
|
log_path = self.log_path
|
|
|
|
# Check skip conditions
|
|
camera_properties_utils.skip_unless(
|
|
not camera_properties_utils.fixed_focus(props) and
|
|
camera_properties_utils.read_3a(props) and
|
|
camera_properties_utils.lens_calibrated(props))
|
|
|
|
# Calculate camera_fov and load scaled image on tablet.
|
|
its_session_utils.load_scene(cam, props, self.scene, self.tablet,
|
|
self.chart_distance)
|
|
|
|
# Initialize chart class and locate chart in scene
|
|
chart = opencv_processing_utils.Chart(
|
|
cam, props, self.log_path, chart_loc=chart_loc_arg)
|
|
|
|
# Initialize capture format
|
|
fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
|
|
|
|
# Get proper sensitivity and exposure time with 3A
|
|
mono_camera = camera_properties_utils.mono_camera(props)
|
|
s, e, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
|
|
|
|
# Take caps and get sharpness for each focal distance
|
|
d_stat, d_move = take_caps_and_return_data(
|
|
cam, props, fmt, s, e, chart, log_path)
|
|
|
|
# Summarize info for log file and easier debug
|
|
logging.debug('Lens stationary')
|
|
for k in sorted(d_stat):
|
|
logging.debug(
|
|
'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
|
|
'sharpness: %.1f', k, d_stat[k]['fd'], d_stat[k]['loc'],
|
|
d_stat[k]['sharpness'])
|
|
logging.debug('Lens moving')
|
|
for k in sorted(d_move):
|
|
logging.debug(
|
|
'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
|
|
'sharpness: %.1f \tlens_moving: %r \t'
|
|
'timestamp: %.1fms', k, d_move[k]['fd'], d_move[k]['loc'],
|
|
d_move[k]['sharpness'], d_move[k]['lens_moving'],
|
|
d_move[k]['timestamp'])
|
|
|
|
# assert reported location/sharpness is correct in static frames
|
|
assert_static_frames_behavior(d_stat)
|
|
|
|
# assert reported location/sharpness is correct in moving frames
|
|
assert_moving_frames_behavior(d_move, d_stat)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_runner.main()
|