216 lines
9.2 KiB
Python
216 lines
9.2 KiB
Python
# Copyright 2022 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 that flash is fired when lighting conditions are dark."""
|
|
|
|
|
|
import logging
|
|
import os.path
|
|
|
|
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 lighting_control_utils
|
|
|
|
_AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH',
|
|
4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'}
|
|
_AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED',
|
|
4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'}
|
|
_FLASH_STATES = {0: 'FLASH_STATE_UNAVAILABLE', 1: 'FLASH_STATE_CHARGING',
|
|
2: 'FLASH_STATE_READY', 3: 'FLASH_STATE_FIRED',
|
|
4: 'FLASH_STATE_PARTIAL'}
|
|
_GRAD_DELTA_ATOL = 15 # gradiant for tablets as screen aborbs energy
|
|
_MEAN_DELTA_ATOL = 15 # mean used for reflective charts
|
|
|
|
_FORMAT_NAMES = ('jpeg', 'yuv')
|
|
_IMG_SIZES = ((640, 480), (640, 360))
|
|
_VGA_SIZE = (640, 480)
|
|
|
|
_PATCH_H = 0.25 # center 25%
|
|
_PATCH_W = 0.25
|
|
_PATCH_X = 0.5 - _PATCH_W/2
|
|
_PATCH_Y = 0.5 - _PATCH_H/2
|
|
_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0]
|
|
_CAPTURE_INTENT_STILL_CAPTURE = 2
|
|
|
|
|
|
class AutoFlashTest(its_base_test.ItsBaseTest):
|
|
"""Test that flash is fired when lighting conditions are dark."""
|
|
|
|
def test_auto_flash(self):
|
|
logging.debug('AE_MODES: %s', str(_AE_MODES))
|
|
logging.debug('AE_STATES: %s', str(_AE_STATES))
|
|
logging.debug('FLASH_STATES: %s', str(_FLASH_STATES))
|
|
failure_messages = []
|
|
|
|
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)
|
|
test_name = os.path.join(self.log_path, _TEST_NAME)
|
|
|
|
# check SKIP conditions
|
|
vendor_api_level = its_session_utils.get_vendor_api_level(self.dut.serial)
|
|
camera_properties_utils.skip_unless(
|
|
camera_properties_utils.flash(props) and
|
|
vendor_api_level >= its_session_utils.ANDROID13_API_LEVEL)
|
|
|
|
# establish connection with lighting controller
|
|
arduino_serial_port = lighting_control_utils.lighting_control(
|
|
self.lighting_cntl, self.lighting_ch)
|
|
|
|
# turn OFF lights to darken scene
|
|
lighting_control_utils.set_lighting_state(
|
|
arduino_serial_port, self.lighting_ch, 'OFF')
|
|
|
|
# turn OFF tablet to darken scene
|
|
if self.tablet:
|
|
lighting_control_utils.turn_off_device(self.tablet)
|
|
|
|
for fmt_name in _FORMAT_NAMES:
|
|
for size in _IMG_SIZES:
|
|
width, height = size
|
|
if not (fmt_name == 'yuv' and size == _VGA_SIZE):
|
|
output_sizes = capture_request_utils.get_available_output_sizes(
|
|
fmt_name, props, match_ar_size=size)
|
|
if not output_sizes:
|
|
if size != _VGA_SIZE:
|
|
logging.debug('No output sizes for format %s, size %sx%s',
|
|
fmt_name, width, height)
|
|
continue
|
|
else:
|
|
raise AssertionError(f'No output sizes for format {fmt_name}, '
|
|
f'size {width}x{height}')
|
|
# pick smallest size out of available output sizes
|
|
width, height = output_sizes[-1]
|
|
out_surfaces = {'format': fmt_name, 'width': width, 'height': height}
|
|
logging.debug('Testing %s format, size: %dx%d',
|
|
fmt_name, width, height)
|
|
no_flash_exp_x_iso = 0
|
|
no_flash_mean = 0
|
|
no_flash_grad = 0
|
|
flash_exp_x_iso = []
|
|
|
|
# take capture with no flash as baseline
|
|
logging.debug('Taking reference frame with no flash.')
|
|
cam.do_3a(do_af=False)
|
|
no_flash_req = capture_request_utils.auto_capture_request()
|
|
no_flash_req[
|
|
'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
|
|
cap = cam.do_capture(no_flash_req, out_surfaces)
|
|
metadata = cap['metadata']
|
|
exp = int(metadata['android.sensor.exposureTime'])
|
|
iso = int(metadata['android.sensor.sensitivity'])
|
|
logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp)
|
|
logging.debug('AE_MODE (cap): %s',
|
|
_AE_MODES[metadata['android.control.aeMode']])
|
|
logging.debug('AE_STATE (cap): %s',
|
|
_AE_STATES[metadata['android.control.aeState']])
|
|
no_flash_exp_x_iso = exp * iso
|
|
y, _, _ = image_processing_utils.convert_capture_to_planes(
|
|
cap, props)
|
|
patch = image_processing_utils.get_image_patch(
|
|
y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
|
|
no_flash_mean = image_processing_utils.compute_image_means(
|
|
patch)[0]*255
|
|
no_flash_grad = image_processing_utils.compute_image_max_gradients(
|
|
patch)[0]*255
|
|
image_processing_utils.write_image(
|
|
y, f'{test_name}_{fmt_name}_{width}x{height}_no_flash_Y.jpg')
|
|
|
|
# log results
|
|
logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso)
|
|
logging.debug('No flash Y grad: %.4f', no_flash_grad)
|
|
logging.debug('No flash Y mean: %.4f', no_flash_mean)
|
|
|
|
# take capture with auto flash enabled
|
|
logging.debug('Taking capture with auto flash enabled.')
|
|
flash_fired = False
|
|
|
|
cap = capture_request_utils.take_captures_with_flash(cam, out_surfaces)
|
|
y, _, _ = image_processing_utils.convert_capture_to_planes(
|
|
cap, props)
|
|
# Save captured image
|
|
image_processing_utils.write_image(
|
|
y, f'{test_name}_{fmt_name}_{width}x{height}_flash_Y.jpg')
|
|
# evaluate captured image
|
|
metadata = cap['metadata']
|
|
exp = int(metadata['android.sensor.exposureTime'])
|
|
iso = int(metadata['android.sensor.sensitivity'])
|
|
logging.debug('cap ISO: %d, exp: %d ns', iso, exp)
|
|
logging.debug('AE_MODE (cap): %s',
|
|
_AE_MODES[metadata['android.control.aeMode']])
|
|
ae_state = _AE_STATES[metadata['android.control.aeState']]
|
|
logging.debug('AE_STATE (cap): %s', ae_state)
|
|
flash_state = _FLASH_STATES[metadata['android.flash.state']]
|
|
logging.debug('FLASH_STATE: %s', flash_state)
|
|
if flash_state == 'FLASH_STATE_FIRED':
|
|
logging.debug('Flash fired')
|
|
flash_fired = True
|
|
flash_exp_x_iso = exp*iso
|
|
y, _, _ = image_processing_utils.convert_capture_to_planes(
|
|
cap, props)
|
|
patch = image_processing_utils.get_image_patch(
|
|
y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
|
|
flash_mean = image_processing_utils.compute_image_means(
|
|
patch)[0]*255
|
|
flash_grad = image_processing_utils.compute_image_max_gradients(
|
|
patch)[0]*255
|
|
|
|
if not flash_fired:
|
|
raise AssertionError('Flash was not fired. '
|
|
f'Format: {fmt_name}, Size: {width}x{height}')
|
|
|
|
# log results
|
|
logging.debug('Flash exposure X ISO %d', flash_exp_x_iso)
|
|
logging.debug('Flash frames Y grad: %.4f', flash_grad)
|
|
logging.debug('Flash frames Y mean: %.4f', flash_mean)
|
|
|
|
# document incorrect behavior
|
|
grad_delta = flash_grad - no_flash_grad
|
|
mean_delta = flash_mean - no_flash_mean
|
|
if not (grad_delta > _GRAD_DELTA_ATOL or
|
|
mean_delta > _MEAN_DELTA_ATOL):
|
|
failure_messages.append(
|
|
f'format: {fmt_name}, size: {width}x{height}, '
|
|
f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, '
|
|
f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}')
|
|
|
|
# Ensure that the flash is turned OFF after flash was fired.
|
|
req = capture_request_utils.auto_capture_request()
|
|
req['android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
|
|
cap = cam.do_capture(req, out_surfaces)
|
|
flash_state_after = _FLASH_STATES[cap['metadata']
|
|
['android.flash.state']]
|
|
logging.debug('FLASH_STATE after flash fired: %s', flash_state_after)
|
|
if flash_state_after != 'FLASH_STATE_READY':
|
|
raise AssertionError('Flash should turn OFF after it was fired.')
|
|
|
|
# turn lights back ON
|
|
lighting_control_utils.set_lighting_state(
|
|
arduino_serial_port, self.lighting_ch, 'ON')
|
|
|
|
# assert correct behavior for all formats
|
|
if failure_messages:
|
|
raise AssertionError('\n'.join(failure_messages))
|
|
|
|
if __name__ == '__main__':
|
|
test_runner.main()
|
|
|