1# Copyright 2022 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"""Verifies that flash is fired when lighting conditions are dark.""" 15 16 17import logging 18import os.path 19 20from mobly import test_runner 21 22import its_base_test 23import camera_properties_utils 24import capture_request_utils 25import image_processing_utils 26import its_session_utils 27import lighting_control_utils 28 29_AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH', 30 4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'} 31_AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED', 32 4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'} 33_FLASH_STATES = {0: 'FLASH_STATE_UNAVAILABLE', 1: 'FLASH_STATE_CHARGING', 34 2: 'FLASH_STATE_READY', 3: 'FLASH_STATE_FIRED', 35 4: 'FLASH_STATE_PARTIAL'} 36_GRAD_DELTA_ATOL = 15 # gradiant for tablets as screen aborbs energy 37_MEAN_DELTA_ATOL = 15 # mean used for reflective charts 38 39_FORMAT_NAMES = ('jpeg', 'yuv') 40_IMG_SIZES = ((640, 480), (640, 360)) 41_VGA_SIZE = (640, 480) 42 43_PATCH_H = 0.25 # center 25% 44_PATCH_W = 0.25 45_PATCH_X = 0.5 - _PATCH_W/2 46_PATCH_Y = 0.5 - _PATCH_H/2 47_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0] 48_CAPTURE_INTENT_STILL_CAPTURE = 2 49 50 51class AutoFlashTest(its_base_test.ItsBaseTest): 52 """Test that flash is fired when lighting conditions are dark.""" 53 54 def test_auto_flash(self): 55 logging.debug('AE_MODES: %s', str(_AE_MODES)) 56 logging.debug('AE_STATES: %s', str(_AE_STATES)) 57 logging.debug('FLASH_STATES: %s', str(_FLASH_STATES)) 58 failure_messages = [] 59 60 with its_session_utils.ItsSession( 61 device_id=self.dut.serial, 62 camera_id=self.camera_id, 63 hidden_physical_id=self.hidden_physical_id) as cam: 64 props = cam.get_camera_properties() 65 props = cam.override_with_hidden_physical_camera_props(props) 66 test_name = os.path.join(self.log_path, _TEST_NAME) 67 68 # check SKIP conditions 69 vendor_api_level = its_session_utils.get_vendor_api_level(self.dut.serial) 70 camera_properties_utils.skip_unless( 71 camera_properties_utils.flash(props) and 72 vendor_api_level >= its_session_utils.ANDROID13_API_LEVEL) 73 74 # establish connection with lighting controller 75 arduino_serial_port = lighting_control_utils.lighting_control( 76 self.lighting_cntl, self.lighting_ch) 77 78 # turn OFF lights to darken scene 79 lighting_control_utils.set_lighting_state( 80 arduino_serial_port, self.lighting_ch, 'OFF') 81 82 # turn OFF tablet to darken scene 83 if self.tablet: 84 lighting_control_utils.turn_off_device(self.tablet) 85 86 for fmt_name in _FORMAT_NAMES: 87 for size in _IMG_SIZES: 88 width, height = size 89 if not (fmt_name == 'yuv' and size == _VGA_SIZE): 90 output_sizes = capture_request_utils.get_available_output_sizes( 91 fmt_name, props, match_ar_size=size) 92 if not output_sizes: 93 if size != _VGA_SIZE: 94 logging.debug('No output sizes for format %s, size %sx%s', 95 fmt_name, width, height) 96 continue 97 else: 98 raise AssertionError(f'No output sizes for format {fmt_name}, ' 99 f'size {width}x{height}') 100 # pick smallest size out of available output sizes 101 width, height = output_sizes[-1] 102 out_surfaces = {'format': fmt_name, 'width': width, 'height': height} 103 logging.debug('Testing %s format, size: %dx%d', 104 fmt_name, width, height) 105 no_flash_exp_x_iso = 0 106 no_flash_mean = 0 107 no_flash_grad = 0 108 flash_exp_x_iso = [] 109 110 # take capture with no flash as baseline 111 logging.debug('Taking reference frame with no flash.') 112 cam.do_3a(do_af=False) 113 no_flash_req = capture_request_utils.auto_capture_request() 114 no_flash_req[ 115 'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE 116 cap = cam.do_capture(no_flash_req, out_surfaces) 117 metadata = cap['metadata'] 118 exp = int(metadata['android.sensor.exposureTime']) 119 iso = int(metadata['android.sensor.sensitivity']) 120 logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp) 121 logging.debug('AE_MODE (cap): %s', 122 _AE_MODES[metadata['android.control.aeMode']]) 123 logging.debug('AE_STATE (cap): %s', 124 _AE_STATES[metadata['android.control.aeState']]) 125 no_flash_exp_x_iso = exp * iso 126 y, _, _ = image_processing_utils.convert_capture_to_planes( 127 cap, props) 128 patch = image_processing_utils.get_image_patch( 129 y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 130 no_flash_mean = image_processing_utils.compute_image_means( 131 patch)[0]*255 132 no_flash_grad = image_processing_utils.compute_image_max_gradients( 133 patch)[0]*255 134 image_processing_utils.write_image( 135 y, f'{test_name}_{fmt_name}_{width}x{height}_no_flash_Y.jpg') 136 137 # log results 138 logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso) 139 logging.debug('No flash Y grad: %.4f', no_flash_grad) 140 logging.debug('No flash Y mean: %.4f', no_flash_mean) 141 142 # take capture with auto flash enabled 143 logging.debug('Taking capture with auto flash enabled.') 144 flash_fired = False 145 146 cap = capture_request_utils.take_captures_with_flash(cam, out_surfaces) 147 y, _, _ = image_processing_utils.convert_capture_to_planes( 148 cap, props) 149 # Save captured image 150 image_processing_utils.write_image( 151 y, f'{test_name}_{fmt_name}_{width}x{height}_flash_Y.jpg') 152 # evaluate captured image 153 metadata = cap['metadata'] 154 exp = int(metadata['android.sensor.exposureTime']) 155 iso = int(metadata['android.sensor.sensitivity']) 156 logging.debug('cap ISO: %d, exp: %d ns', iso, exp) 157 logging.debug('AE_MODE (cap): %s', 158 _AE_MODES[metadata['android.control.aeMode']]) 159 ae_state = _AE_STATES[metadata['android.control.aeState']] 160 logging.debug('AE_STATE (cap): %s', ae_state) 161 flash_state = _FLASH_STATES[metadata['android.flash.state']] 162 logging.debug('FLASH_STATE: %s', flash_state) 163 if flash_state == 'FLASH_STATE_FIRED': 164 logging.debug('Flash fired') 165 flash_fired = True 166 flash_exp_x_iso = exp*iso 167 y, _, _ = image_processing_utils.convert_capture_to_planes( 168 cap, props) 169 patch = image_processing_utils.get_image_patch( 170 y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 171 flash_mean = image_processing_utils.compute_image_means( 172 patch)[0]*255 173 flash_grad = image_processing_utils.compute_image_max_gradients( 174 patch)[0]*255 175 176 if not flash_fired: 177 raise AssertionError('Flash was not fired. ' 178 f'Format: {fmt_name}, Size: {width}x{height}') 179 180 # log results 181 logging.debug('Flash exposure X ISO %d', flash_exp_x_iso) 182 logging.debug('Flash frames Y grad: %.4f', flash_grad) 183 logging.debug('Flash frames Y mean: %.4f', flash_mean) 184 185 # document incorrect behavior 186 grad_delta = flash_grad - no_flash_grad 187 mean_delta = flash_mean - no_flash_mean 188 if not (grad_delta > _GRAD_DELTA_ATOL or 189 mean_delta > _MEAN_DELTA_ATOL): 190 failure_messages.append( 191 f'format: {fmt_name}, size: {width}x{height}, ' 192 f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, ' 193 f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}') 194 195 # Ensure that the flash is turned OFF after flash was fired. 196 req = capture_request_utils.auto_capture_request() 197 req['android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE 198 cap = cam.do_capture(req, out_surfaces) 199 flash_state_after = _FLASH_STATES[cap['metadata'] 200 ['android.flash.state']] 201 logging.debug('FLASH_STATE after flash fired: %s', flash_state_after) 202 if flash_state_after != 'FLASH_STATE_READY': 203 raise AssertionError('Flash should turn OFF after it was fired.') 204 205 # turn lights back ON 206 lighting_control_utils.set_lighting_state( 207 arduino_serial_port, self.lighting_ch, 'ON') 208 209 # assert correct behavior for all formats 210 if failure_messages: 211 raise AssertionError('\n'.join(failure_messages)) 212 213if __name__ == '__main__': 214 test_runner.main() 215 216