1# Copyright 2017 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 3A settles consistently 3x.""" 15 16 17import logging 18import math 19import os.path 20 21from mobly import test_runner 22import numpy as np 23 24import its_base_test 25import camera_properties_utils 26import capture_request_utils 27import error_util 28import image_processing_utils 29import its_session_utils 30 31 32_NAME = os.path.splitext(os.path.basename(__file__))[0] 33 34_AWB_GREEN_CH = 2 35_GGAIN_TOL = 0.1 36_FD_TOL = 0.1 37_ISO_EXP_ISP_TOL = 0.2 # TOL used w/o postRawCapabilityBoost not available. 38_ISO_EXP_TOL = 0.16 # TOL used w/ postRawCapabilityBoost available 39 40_NUM_TEST_ITERATIONS = 3 41 42 43class ConsistencyTest(its_base_test.ItsBaseTest): 44 """Basic test for 3A consistency. 45 46 To PASS, 3A must converge for exp, gain, awb, fd within defined TOLs. 47 TOLs are based on camera capabilities. If postRawSensitivityBoost can be 48 fixed TOL is tighter. The TOL values in the CONSTANTS area are described in 49 b/144452069. 50 51 Note ISO and sensitivity are interchangeable for Android cameras. 52 """ 53 54 def test_3a_consistency(self): 55 logging.debug('Starting %s', _NAME) 56 with its_session_utils.ItsSession( 57 device_id=self.dut.serial, 58 camera_id=self.camera_id, 59 hidden_physical_id=self.hidden_physical_id) as cam: 60 props = cam.get_camera_properties() 61 props = cam.override_with_hidden_physical_camera_props(props) 62 debug = self.debug_mode 63 64 # Load chart for scene 65 its_session_utils.load_scene( 66 cam, props, self.scene, self.tablet, self.chart_distance) 67 68 # Check skip conditions 69 camera_properties_utils.skip_unless( 70 camera_properties_utils.read_3a(props)) 71 mono_camera = camera_properties_utils.mono_camera(props) 72 73 # Set postRawSensitivityBoost to minimum if available. 74 req = capture_request_utils.auto_capture_request() 75 if camera_properties_utils.post_raw_sensitivity_boost(props): 76 min_iso_boost, _ = props['android.control.postRawSensitivityBoostRange'] 77 req['android.control.postRawSensitivityBoost'] = min_iso_boost 78 iso_exp_tol = _ISO_EXP_TOL 79 logging.debug('Setting post RAW sensitivity boost to minimum') 80 else: 81 iso_exp_tol = _ISO_EXP_ISP_TOL 82 83 iso_exps = [] 84 g_gains = [] 85 fds = [] 86 cam.do_3a() # do 3A 1x up front to 'prime the pump' 87 # Do 3A _NUM_TEST_ITERATIONS times and save data. 88 for i in range(_NUM_TEST_ITERATIONS): 89 try: 90 iso, exposure, awb_gains, awb_transform, focus_distance = cam.do_3a( 91 get_results=True, mono_camera=mono_camera) 92 logging.debug('req iso: %d, exp: %d, iso*exp: %d', 93 iso, exposure, exposure * iso) 94 logging.debug('req awb_gains: %s, awb_transform: %s', 95 awb_gains, awb_transform) 96 logging.debug('req fd: %s', focus_distance) 97 req = capture_request_utils.manual_capture_request( 98 iso, exposure, focus_distance) 99 cap = cam.do_capture(req, cam.CAP_YUV) 100 if debug: 101 img = image_processing_utils.convert_capture_to_rgb_image(cap) 102 image_processing_utils.write_image( 103 img, f'{os.path.join(self.log_path, _NAME)}_{i}.jpg') 104 105 # Extract and save metadata. 106 iso_result = cap['metadata']['android.sensor.sensitivity'] 107 exposure_result = cap['metadata']['android.sensor.exposureTime'] 108 awb_gains_result = cap['metadata']['android.colorCorrection.gains'] 109 awb_transform_result = capture_request_utils.rational_to_float( 110 cap['metadata']['android.colorCorrection.transform']) 111 focus_distance_result = cap['metadata']['android.lens.focusDistance'] 112 logging.debug( 113 'res iso: %d, exposure: %d, iso*exp: %d', 114 iso_result, exposure_result, exposure_result*iso_result) 115 logging.debug('res awb_gains: %s, awb_transform: %s', 116 awb_gains_result, awb_transform_result) 117 logging.debug('res fd: %s', focus_distance_result) 118 iso_exps.append(exposure_result*iso_result) 119 g_gains.append(awb_gains_result[_AWB_GREEN_CH]) 120 fds.append(focus_distance_result) 121 except error_util.CameraItsError: 122 logging.debug('FAIL') 123 124 # Check for correct behavior. 125 if len(iso_exps) != _NUM_TEST_ITERATIONS: 126 raise AssertionError(f'number of captures: {len(iso_exps)}, ' 127 f'NUM_TEST_ITERATIONS: {_NUM_TEST_ITERATIONS}.') 128 iso_exp_min = np.amin(iso_exps) 129 iso_exp_max = np.amax(iso_exps) 130 if not math.isclose(iso_exp_max, iso_exp_min, rel_tol=iso_exp_tol): 131 raise AssertionError(f'ISO*exp min: {iso_exp_min}, max: {iso_exp_max}, ' 132 f'TOL:{iso_exp_tol}') 133 g_gain_min = np.amin(g_gains) 134 g_gain_max = np.amax(g_gains) 135 if not math.isclose(g_gain_max, g_gain_min, rel_tol=_GGAIN_TOL): 136 raise AssertionError(f'G gain min: {g_gain_min}, max: {g_gain_min}, ' 137 f'TOL: {_GGAIN_TOL}') 138 fd_min = np.amin(fds) 139 fd_max = np.amax(fds) 140 if not math.isclose(fd_max, fd_min, rel_tol=_FD_TOL): 141 raise AssertionError(f'FD min: {fd_min}, max: {fd_max} TOL: {_FD_TOL}') 142 for g in awb_gains: 143 if np.isnan(g): 144 raise AssertionError('AWB gain entry is not a number.') 145 for x in awb_transform: 146 if np.isnan(x): 147 raise AssertionError('AWB transform entry is not a number.') 148 149if __name__ == '__main__': 150 test_runner.main() 151