blob: 930817ced828cbfabe5d8b9b6ba26ee9a0c40a3f [file] [log] [blame]
# 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.state when lens is moving."""
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 image_processing_utils
import its_session_utils
import opencv_processing_utils
FRAME_ATOL_MS = 10
MIN_AF_FD_TOL = 1.2 # AF value must < 1.2*min
NAME = os.path.splitext(os.path.basename(__file__))[0]
NUM_FRAMES_PER_FD = 12
POSITION_RTOL = 0.10 # 10%
SHARPNESS_RTOL = 0.10 # 10%
START_FRAME = 1 # start on second frame
VGA_WIDTH, VGA_HEIGHT = 640, 480
def take_caps_and_determine_sharpness(
cam, props, fmt, gain, exp, af_fd, chart, log_path):
"""Return fd, sharpness, lens state of the output images.
Args:
cam: An open device session.
props: Properties of cam
fmt: dict; capture format
gain: Sensitivity for the request as defined in android.sensor.sensitivity
exp: Exposure time for the request as defined in
android.sensor.exposureTime
af_fd: Focus distance for the request as defined in
android.lens.focusDistance
chart: Object that contains chart information
log_path: log_path to save the captured image
Returns:
Object containing reported sharpness of the output image, keyed by
the following string:
'sharpness'
"""
# initialize variables and take data sets
data_set = {}
white_level = int(props['android.sensor.info.whiteLevel'])
min_fd = props['android.lens.info.minimumFocusDistance']
fds = [af_fd] * NUM_FRAMES_PER_FD + [min_fd] * NUM_FRAMES_PER_FD
reqs = []
for i, fd in enumerate(fds):
reqs.append(capture_request_utils.manual_capture_request(gain, exp))
reqs[i]['android.lens.focusDistance'] = fd
caps = cam.do_capture(reqs, fmt)
caps = caps[START_FRAME:]
for i, cap in enumerate(caps):
data = {'fd': fds[i+START_FRAME]}
data['loc'] = cap['metadata']['android.lens.focusDistance']
data['lens_moving'] = (cap['metadata']['android.lens.state']
== 1)
timestamp = cap['metadata']['android.sensor.timestamp'] * 1E-6
if i == 0:
timestamp_init = timestamp
timestamp -= timestamp_init
data['timestamp'] = timestamp
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))
img_name = '%s_i=%d.jpg' % (os.path.join(log_path, NAME), i)
image_processing_utils.write_image(chart.img, img_name)
data['sharpness'] = (
white_level * image_processing_utils.compute_image_sharpness(chart.img))
data_set[i] = data
return data_set
class LensMovementReportingTest(its_base_test.ItsBaseTest):
"""Test if focus distance is properly reported.
Do unit step of focus distance and check sharpness correlates.
"""
def test_lens_movement_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:
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
# 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_approx_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)
# Get proper sensitivity, exposure time, and focus distance with 3A.
mono_camera = camera_properties_utils.mono_camera(props)
s, e, _, _, af_fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
# Get sharpness for each focal distance
fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
d = take_caps_and_determine_sharpness(
cam, props, fmt, s, e, af_fd, chart, self.log_path)
for k in sorted(d):
logging.debug(
'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
'sharpness: %.1f \tlens_moving: %r \t'
'timestamp: %.1fms', k, d[k]['fd'], d[k]['loc'], d[k]['sharpness'],
d[k]['lens_moving'], d[k]['timestamp'])
# Assert frames are consecutive
frame_diffs = np.gradient([v['timestamp'] for v in d.values()])
delta_diffs = np.amax(frame_diffs) - np.amin(frame_diffs)
if not np.isclose(delta_diffs, 0, atol=FRAME_ATOL_MS):
raise AssertionError(f'Timestamp gradient(ms): {delta_diffs:.1f}, '
f'ATOL: {FRAME_ATOL_MS}')
# Remove data when lens is moving
for k in sorted(d):
if d[k]['lens_moving']:
del d[k]
# Split data into min_fd and af data for processing
d_min_fd = {}
d_af_fd = {}
for k in sorted(d):
if d[k]['fd'] == props['android.lens.info.minimumFocusDistance']:
d_min_fd[k] = d[k]
if d[k]['fd'] == af_fd:
d_af_fd[k] = d[k]
logging.debug('Assert reported locs are close for af_fd captures')
min_loc = min([v['loc'] for v in d_af_fd.values()])
max_loc = max([v['loc'] for v in d_af_fd.values()])
if not np.isclose(min_loc, max_loc, rtol=POSITION_RTOL):
raise AssertionError(f'af_fd[loc] min: {min_loc:.3f}, max: '
f'{max_loc:.3f}, RTOL: {POSITION_RTOL}')
logging.debug('Assert reported sharpness is close at af_fd')
min_sharp = min([v['sharpness'] for v in d_af_fd.values()])
max_sharp = max([v['sharpness'] for v in d_af_fd.values()])
if not np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_RTOL):
raise AssertionError(f'af_fd[sharpness] min: {min_sharp:.3f}, '
f'max: {max_sharp:.3f}, RTOL: {SHARPNESS_RTOL}')
logging.debug('Assert reported loc is close to assign loc for af_fd')
first_key = min(d_af_fd.keys()) # find 1st non-moving frame
loc = d_af_fd[first_key]['loc']
fd = d_af_fd[first_key]['fd']
if not np.isclose(loc, fd, rtol=POSITION_RTOL):
raise AssertionError(f'af_fd[loc]: {loc:.3f}, af_fd[fd]: {fd:.3f}, '
f'RTOL: {POSITION_RTOL}')
logging.debug('Assert reported locs are close for min_fd captures')
min_loc = min([v['loc'] for v in d_min_fd.values()])
max_loc = max([v['loc'] for v in d_min_fd.values()])
if not np.isclose(min_loc, max_loc, rtol=POSITION_RTOL):
raise AssertionError(f'min_fd[loc] min: {min_loc:.3f}, max: '
f'{max_loc:.3f}, RTOL: {POSITION_RTOL}')
logging.debug('Assert reported sharpness is close at min_fd')
min_sharp = min([v['sharpness'] for v in d_min_fd.values()])
max_sharp = max([v['sharpness'] for v in d_min_fd.values()])
if not np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_RTOL):
raise AssertionError(f'min_fd[sharpness] min: {min_sharp:.3f}, '
f'max: {max_sharp:.3f}, RTOL: {SHARPNESS_RTOL}')
logging.debug('Assert reported loc is close to assigned loc for min_fd')
last_key = max(d_min_fd.keys()) # find last (non-moving) frame
loc = d_min_fd[last_key]['loc']
fd = d_min_fd[last_key]['fd']
if not np.isclose(loc, fd, rtol=POSITION_RTOL):
raise AssertionError(f'min_fd[loc]: {loc:.3f}, min_fd[fd]: {fd:.3f}, '
f'RTOL: {POSITION_RTOL}')
logging.debug('Assert AF focus distance > minimum focus distance')
min_fd = d_min_fd[last_key]['fd']
if af_fd > min_fd * MIN_AF_FD_TOL:
raise AssertionError(f'AF focus distance > min focus distance! af: '
f'{af_fd}, min: {min_fd}, TOL: {MIN_AF_FD_TOL}')
if __name__ == '__main__':
test_runner.main()