1# Copyright 2020 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"""Verify that the android.control.afSceneChange asserted on scene change.""" 15 16 17import logging 18import multiprocessing 19import os.path 20import time 21 22from mobly import test_runner 23 24import its_base_test 25import camera_properties_utils 26import capture_request_utils 27import image_processing_utils 28import its_session_utils 29import scene_change_utils 30 31_BRIGHT_CHANGE_TOL = 0.2 32_CONTINUOUS_PICTURE_MODE = 4 33_CONVERGED_3A = (2, 2, 2) # (AE, AF, AWB) 34_DELAY_CAPTURE = 1.5 # Delay in first capture to sync events (sec). 35_DELAY_DISPLAY = 3.0 # Time when display turns OFF (sec). 36_FPS = 30 # Frames Per Second 37_M_TO_CM = 100 38_NAME = os.path.splitext(os.path.basename(__file__))[0] 39_NSEC_TO_MSEC = 1E-6 40_NUM_TRIES = 6 41_NUM_FRAMES = 50 42_PATCH_H = 0.1 # Center 10%. 43_PATCH_W = 0.1 44_PATCH_X = 0.5 - _PATCH_W/2 45_PATCH_Y = 0.5 - _PATCH_H/2 46_RGB_G_CH = 1 47_SCENE_CHANGE_FLAG_TRUE = 1 48_VALID_SCENE_CHANGE_VALS = (0, 1) 49_VGA_W, _VGA_H = 640, 480 50 51 52def find_3a_converged_frame(cap_data): 53 converged_frame = -1 54 for i, cap in enumerate(cap_data): 55 if cap['3a_state'] == _CONVERGED_3A: 56 converged_frame = i 57 break 58 logging.debug('Frame index where 3A converges: %d', converged_frame) 59 return converged_frame 60 61 62def determine_if_scene_changed(cap_data, converged_frame): 63 """Determine if the scene has changed during captures. 64 65 Args: 66 cap_data: Camera capture object. 67 converged_frame: Integer indicating when 3A converged. 68 69 Returns: 70 A 2-tuple of booleans where the first is for AF scene change flag asserted 71 and the second is for whether brightness in images changed. 72 """ 73 scene_change_flag = False 74 bright_change_flag = False 75 start_frame_brightness = cap_data[0]['avg'] 76 for i in range(converged_frame, len(cap_data)): 77 if cap_data[i]['avg'] <= ( 78 start_frame_brightness * (1.0 - _BRIGHT_CHANGE_TOL)): 79 bright_change_flag = True 80 if cap_data[i]['flag'] == _SCENE_CHANGE_FLAG_TRUE: 81 scene_change_flag = True 82 return scene_change_flag, bright_change_flag 83 84 85def toggle_screen(tablet, delay=1): 86 """Sets the chart host screen display level . 87 88 Args: 89 tablet: Object for screen tablet. 90 delay: Float value for time delay. Default is 1 second. 91 """ 92 t0 = time.time() 93 if delay >= 0: 94 time.sleep(delay) 95 else: 96 raise ValueError(f'Screen toggle time shifted to {delay} w/o scene change. ' 97 'Tablet does not appear to be toggling. Check setup.') 98 tablet.adb.shell('input keyevent KEYCODE_POWER') 99 t = time.time() - t0 100 logging.debug('Toggling display at %.3f.', t) 101 102 103def capture_frames(cam, delay, burst, log_path): 104 """Capture NUM_FRAMES frames and log metadata. 105 106 3A state information: 107 AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED, 108 4: FLASH_REQ, 5: PRECAPTURE} 109 AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED, 110 3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED, 111 6: PASSIVE_UNFOCUSED} 112 AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED} 113 114 Args: 115 cam: Camera object. 116 delay: Float value for time delay in seconds. 117 burst: Integer number of burst index. 118 log_path: String location to save images. 119 Returns: 120 cap_data_list. List of dicts for each capture. 121 """ 122 cap_data_list = [] 123 req = capture_request_utils.auto_capture_request() 124 req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE 125 fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H} 126 t0 = time.time() 127 time.sleep(delay) 128 logging.debug('cap event start: %.6f', time.time() - t0) 129 caps = cam.do_capture([req]*_NUM_FRAMES, fmt) 130 logging.debug('cap event stop: %.6f', time.time() - t0) 131 132 # Extract frame metadata. 133 for i, cap in enumerate(caps): 134 cap_data = {} 135 exp = cap['metadata']['android.sensor.exposureTime'] * _NSEC_TO_MSEC 136 iso = cap['metadata']['android.sensor.sensitivity'] 137 focal_length = cap['metadata']['android.lens.focalLength'] 138 ae_state = cap['metadata']['android.control.aeState'] 139 af_state = cap['metadata']['android.control.afState'] 140 awb_state = cap['metadata']['android.control.awbState'] 141 if focal_length: 142 fl_str = str(round(_M_TO_CM/focal_length, 2)) + 'cm' 143 else: 144 fl_str = 'infinity' 145 flag = cap['metadata']['android.control.afSceneChange'] 146 if flag not in _VALID_SCENE_CHANGE_VALS: 147 raise AssertionError(f'afSceneChange not a valid value: {flag}.') 148 img = image_processing_utils.convert_capture_to_rgb_image(cap) 149 image_processing_utils.write_image( 150 img, '%s_%d_%d.jpg' % (os.path.join(log_path, _NAME), burst, i)) 151 patch = image_processing_utils.get_image_patch( 152 img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 153 green_avg = image_processing_utils.compute_image_means(patch)[_RGB_G_CH] 154 logging.debug( 155 '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, 3A: [%d,%d,%d], flag: %d', 156 i, iso, exp, fl_str, green_avg, ae_state, af_state, awb_state, flag) 157 cap_data['3a_state'] = (ae_state, af_state, awb_state) 158 cap_data['avg'] = green_avg 159 cap_data['flag'] = flag 160 cap_data_list.append(cap_data) 161 return cap_data_list 162 163 164class SceneChangeTest(its_base_test.ItsBaseTest): 165 """Tests that AF scene change detected metadata changes for scene change. 166 167 Confirm android.control.afSceneChangeDetected is asserted when scene changes. 168 169 Does continuous capture with face scene during scene change. With no scene 170 change, behavior should be similar to scene2_b/test_continuous_picture. 171 Scene change is modeled with scene tablet powered down during continuous 172 capture. If tablet does not exist, scene change can be modeled with hand wave 173 in front of camera. 174 175 Depending on scene brightness changes and scene change flag assertions during 176 test, adjust tablet timing to move scene change to appropriate timing for 177 test. 178 """ 179 180 def test_scene_change(self): 181 logging.debug('Starting %s', _NAME) 182 with its_session_utils.ItsSession( 183 device_id=self.dut.serial, 184 camera_id=self.camera_id, 185 hidden_physical_id=self.hidden_physical_id) as cam: 186 props = cam.get_camera_properties() 187 props = cam.override_with_hidden_physical_camera_props(props) 188 log_path = self.log_path 189 tablet = self.tablet 190 191 # Check SKIP conditions. 192 camera_properties_utils.skip_unless( 193 camera_properties_utils.continuous_picture(props) and 194 camera_properties_utils.af_scene_change(props)) 195 196 # Load chart for scene. 197 its_session_utils.load_scene( 198 cam, props, self.scene, tablet, self.chart_distance) 199 200 # Do captures with scene change. 201 tablet_level = int(self.tablet_screen_brightness) 202 logging.debug('Tablet brightness: %d', tablet_level) 203 scene_change_delay = _DELAY_DISPLAY 204 cam.do_3a() # Do 3A up front to settle camera. 205 for burst in range(_NUM_TRIES): 206 logging.debug('burst number: %d', burst) 207 # Create scene change by turning off chart display & capture frames 208 if tablet: 209 multiprocessing.Process(name='p1', target=toggle_screen, 210 args=(tablet, scene_change_delay,)).start() 211 else: 212 print('Wave hand in front of camera to create scene change.') 213 cap_data = capture_frames(cam, _DELAY_CAPTURE, burst, log_path) 214 215 # Find frame where 3A converges and final brightness. 216 converged_frame = find_3a_converged_frame(cap_data) 217 converged_flag = True if converged_frame != -1 else False 218 bright_final = cap_data[_NUM_FRAMES - 1]['avg'] 219 220 # Determine if scene changed. 221 scene_change_flag, bright_change_flag = determine_if_scene_changed( 222 cap_data, converged_frame) 223 224 # Adjust timing based on captured frames and scene change flags. 225 timing_adjustment = scene_change_utils.calc_timing_adjustment( 226 converged_flag, scene_change_flag, bright_change_flag, bright_final) 227 if timing_adjustment == scene_change_utils.SCENE_CHANGE_PASS_CODE: 228 break 229 elif timing_adjustment == scene_change_utils.SCENE_CHANGE_FAIL_CODE: 230 raise AssertionError('Test fails. Check logging.error.') 231 else: 232 if burst == _NUM_TRIES-1: # FAIL out after NUM_TRIES. 233 raise AssertionError(f'No scene change in {_NUM_TRIES}x tries.') 234 else: 235 scene_change_delay += timing_adjustment / _FPS 236 237 if tablet: 238 logging.debug('Turning screen back ON.') 239 toggle_screen(tablet) 240 241 242if __name__ == '__main__': 243 test_runner.main() 244