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