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