blob: 9a8b262913f508e31980d38ab8b75710843ecfb1 [file] [log] [blame]
# Copyright 2020 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.
"""Verify that the android.control.afSceneChange asserted on scene change."""
import logging
import multiprocessing
import os.path
import time
from mobly import test_runner
import its_base_test
import camera_properties_utils
import capture_request_utils
import image_processing_utils
import its_session_utils
import scene_change_utils
_BRIGHT_CHANGE_TOL = 0.2
_CONTINUOUS_PICTURE_MODE = 4
_CONVERGED_3A = (2, 2, 2) # (AE, AF, AWB)
_DELAY_CAPTURE = 1.5 # Delay in first capture to sync events (sec).
_DELAY_DISPLAY = 3.0 # Time when display turns OFF (sec).
_FPS = 30 # Frames Per Second
_M_TO_CM = 100
_NAME = os.path.splitext(os.path.basename(__file__))[0]
_NSEC_TO_MSEC = 1E-6
_NUM_TRIES = 6
_NUM_FRAMES = 50
_PATCH_H = 0.1 # Center 10%.
_PATCH_W = 0.1
_PATCH_X = 0.5 - _PATCH_W/2
_PATCH_Y = 0.5 - _PATCH_H/2
_RGB_G_CH = 1
_SCENE_CHANGE_FLAG_TRUE = 1
_VALID_SCENE_CHANGE_VALS = (0, 1)
_VGA_W, _VGA_H = 640, 480
def find_3a_converged_frame(cap_data):
converged_frame = -1
for i, cap in enumerate(cap_data):
if cap['3a_state'] == _CONVERGED_3A:
converged_frame = i
break
logging.debug('Frame index where 3A converges: %d', converged_frame)
return converged_frame
def determine_if_scene_changed(cap_data, converged_frame):
"""Determine if the scene has changed during captures.
Args:
cap_data: Camera capture object.
converged_frame: Integer indicating when 3A converged.
Returns:
A 2-tuple of booleans where the first is for AF scene change flag asserted
and the second is for whether brightness in images changed.
"""
scene_change_flag = False
bright_change_flag = False
start_frame_brightness = cap_data[0]['avg']
for i in range(converged_frame, len(cap_data)):
if cap_data[i]['avg'] <= (
start_frame_brightness * (1.0 - _BRIGHT_CHANGE_TOL)):
bright_change_flag = True
if cap_data[i]['flag'] == _SCENE_CHANGE_FLAG_TRUE:
scene_change_flag = True
return scene_change_flag, bright_change_flag
def toggle_screen(tablet, delay=1):
"""Sets the chart host screen display level .
Args:
tablet: Object for screen tablet.
delay: Float value for time delay. Default is 1 second.
"""
t0 = time.time()
if delay >= 0:
time.sleep(delay)
else:
raise ValueError(f'Screen toggle time shifted to {delay} w/o scene change. '
'Tablet does not appear to be toggling. Check setup.')
tablet.adb.shell('input keyevent KEYCODE_POWER')
t = time.time() - t0
logging.debug('Toggling display at %.3f.', t)
def capture_frames(cam, delay, burst, log_path):
"""Capture NUM_FRAMES frames and log metadata.
3A state information:
AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
4: FLASH_REQ, 5: PRECAPTURE}
AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED,
6: PASSIVE_UNFOCUSED}
AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
Args:
cam: Camera object.
delay: Float value for time delay in seconds.
burst: Integer number of burst index.
log_path: String location to save images.
Returns:
cap_data_list. List of dicts for each capture.
"""
cap_data_list = []
req = capture_request_utils.auto_capture_request()
req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE
fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
t0 = time.time()
time.sleep(delay)
logging.debug('cap event start: %.6f', time.time() - t0)
caps = cam.do_capture([req]*_NUM_FRAMES, fmt)
logging.debug('cap event stop: %.6f', time.time() - t0)
# Extract frame metadata.
for i, cap in enumerate(caps):
cap_data = {}
exp = cap['metadata']['android.sensor.exposureTime'] * _NSEC_TO_MSEC
iso = cap['metadata']['android.sensor.sensitivity']
focal_length = cap['metadata']['android.lens.focalLength']
ae_state = cap['metadata']['android.control.aeState']
af_state = cap['metadata']['android.control.afState']
awb_state = cap['metadata']['android.control.awbState']
if focal_length:
fl_str = str(round(_M_TO_CM/focal_length, 2)) + 'cm'
else:
fl_str = 'infinity'
flag = cap['metadata']['android.control.afSceneChange']
if flag not in _VALID_SCENE_CHANGE_VALS:
raise AssertionError(f'afSceneChange not a valid value: {flag}.')
img = image_processing_utils.convert_capture_to_rgb_image(cap)
image_processing_utils.write_image(
img, '%s_%d_%d.jpg' % (os.path.join(log_path, _NAME), burst, i))
patch = image_processing_utils.get_image_patch(
img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
green_avg = image_processing_utils.compute_image_means(patch)[_RGB_G_CH]
logging.debug(
'%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, 3A: [%d,%d,%d], flag: %d',
i, iso, exp, fl_str, green_avg, ae_state, af_state, awb_state, flag)
cap_data['3a_state'] = (ae_state, af_state, awb_state)
cap_data['avg'] = green_avg
cap_data['flag'] = flag
cap_data_list.append(cap_data)
return cap_data_list
class SceneChangeTest(its_base_test.ItsBaseTest):
"""Tests that AF scene change detected metadata changes for scene change.
Confirm android.control.afSceneChangeDetected is asserted when scene changes.
Does continuous capture with face scene during scene change. With no scene
change, behavior should be similar to scene2_b/test_continuous_picture.
Scene change is modeled with scene tablet powered down during continuous
capture. If tablet does not exist, scene change can be modeled with hand wave
in front of camera.
Depending on scene brightness changes and scene change flag assertions during
test, adjust tablet timing to move scene change to appropriate timing for
test.
"""
def test_scene_change(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)
log_path = self.log_path
tablet = self.tablet
# Check SKIP conditions.
camera_properties_utils.skip_unless(
camera_properties_utils.continuous_picture(props) and
camera_properties_utils.af_scene_change(props))
# Load chart for scene.
its_session_utils.load_scene(
cam, props, self.scene, tablet, self.chart_distance)
# Do captures with scene change.
tablet_level = int(self.tablet_screen_brightness)
logging.debug('Tablet brightness: %d', tablet_level)
scene_change_delay = _DELAY_DISPLAY
cam.do_3a() # Do 3A up front to settle camera.
for burst in range(_NUM_TRIES):
logging.debug('burst number: %d', burst)
# Create scene change by turning off chart display & capture frames
if tablet:
multiprocessing.Process(name='p1', target=toggle_screen,
args=(tablet, scene_change_delay,)).start()
else:
print('Wave hand in front of camera to create scene change.')
cap_data = capture_frames(cam, _DELAY_CAPTURE, burst, log_path)
# Find frame where 3A converges and final brightness.
converged_frame = find_3a_converged_frame(cap_data)
converged_flag = True if converged_frame != -1 else False
bright_final = cap_data[_NUM_FRAMES - 1]['avg']
# Determine if scene changed.
scene_change_flag, bright_change_flag = determine_if_scene_changed(
cap_data, converged_frame)
# Adjust timing based on captured frames and scene change flags.
timing_adjustment = scene_change_utils.calc_timing_adjustment(
converged_flag, scene_change_flag, bright_change_flag, bright_final)
if timing_adjustment == scene_change_utils.SCENE_CHANGE_PASS_CODE:
break
elif timing_adjustment == scene_change_utils.SCENE_CHANGE_FAIL_CODE:
raise AssertionError('Test fails. Check logging.error.')
else:
if burst == _NUM_TRIES-1: # FAIL out after NUM_TRIES.
raise AssertionError(f'No scene change in {_NUM_TRIES}x tries.')
else:
scene_change_delay += timing_adjustment / _FPS
if tablet:
logging.debug('Turning screen back ON.')
toggle_screen(tablet)
if __name__ == '__main__':
test_runner.main()