# Copyright 2017 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Verifies 3A settles consistently 3x.""" import logging import os.path from mobly import test_runner import numpy as np import its_base_test import camera_properties_utils import capture_request_utils import error_util import image_processing_utils import its_session_utils _NAME = os.path.splitext(os.path.basename(__file__))[0] _AWB_GREEN_CH = 2 _GGAIN_TOL = 0.1 _FD_TOL = 0.1 _ISO_EXP_ISP_TOL = 0.2 # TOL used w/o postRawCapabilityBoost not available. _ISO_EXP_TOL = 0.16 # TOL used w/ postRawCapabilityBoost available _NUM_TEST_ITERATIONS = 3 class ConsistencyTest(its_base_test.ItsBaseTest): """Basic test for 3A consistency. To PASS, 3A must converge for exp, gain, awb, fd within defined TOLs. TOLs are based on camera capabilities. If postRawSensitivityBoost can be fixed TOL is tighter. The TOL values in the CONSTANTS area are described in b/144452069. Note ISO and sensitivity are interchangeable for Android cameras. """ def test_3a_consistency(self): logging.debug('Starting %s', _NAME) with its_session_utils.ItsSession( device_id=self.dut.serial, camera_id=self.camera_id, hidden_physical_id=self.hidden_physical_id) as cam: props = cam.get_camera_properties() props = cam.override_with_hidden_physical_camera_props(props) debug = self.debug_mode # Load chart for scene its_session_utils.load_scene( cam, props, self.scene, self.tablet, self.chart_distance) # Check skip conditions camera_properties_utils.skip_unless( camera_properties_utils.read_3a(props)) mono_camera = camera_properties_utils.mono_camera(props) # Set postRawSensitivityBoost to minimum if available. req = capture_request_utils.auto_capture_request() if camera_properties_utils.post_raw_sensitivity_boost(props): min_iso_boost, _ = props['android.control.postRawSensitivityBoostRange'] req['android.control.postRawSensitivityBoost'] = min_iso_boost iso_exp_tol = _ISO_EXP_TOL logging.debug('Setting post RAW sensitivity boost to minimum') else: iso_exp_tol = _ISO_EXP_ISP_TOL # Do 3A and save data. iso_exps = [] g_gains = [] fds = [] for i in range(_NUM_TEST_ITERATIONS): try: iso, exposure, awb_gains, awb_transform, focus_distance = cam.do_3a( get_results=True, mono_camera=mono_camera) logging.debug('req iso: %d, exp: %d, iso*exp: %d', iso, exposure, exposure * iso) logging.debug('req awb_gains: %s, awb_transform: %s', awb_gains, awb_transform) logging.debug('req fd: %s', focus_distance) req = capture_request_utils.manual_capture_request( iso, exposure, focus_distance) cap = cam.do_capture(req, cam.CAP_YUV) if debug: img = image_processing_utils.convert_capture_to_rgb_image(cap) img_name = '%s_%d.jpg' % (os.path.join(self.log_path, _NAME), i) image_processing_utils.write_image(img, img_name) # Extract and save metadata. iso_result = cap['metadata']['android.sensor.sensitivity'] exposure_result = cap['metadata']['android.sensor.exposureTime'] awb_gains_result = cap['metadata']['android.colorCorrection.gains'] awb_transform_result = capture_request_utils.rational_to_float( cap['metadata']['android.colorCorrection.transform']) focus_distance_result = cap['metadata']['android.lens.focusDistance'] logging.debug( 'res iso: %d, exposure: %d, iso*exp: %d', iso_result, exposure_result, exposure_result*iso_result) logging.debug('res awb_gains: %s, awb_transform: %s', awb_gains_result, awb_transform_result) logging.debug('res fd: %s', focus_distance_result) iso_exps.append(exposure_result*iso_result) g_gains.append(awb_gains_result[_AWB_GREEN_CH]) fds.append(focus_distance_result) except error_util.CameraItsError: logging.debug('FAIL') # Check for correct behavior. if len(iso_exps) != _NUM_TEST_ITERATIONS: raise AssertionError(f'number of captures: {len(iso_exps)}, ' f'NUM_TEST_ITERATIONS: {_NUM_TEST_ITERATIONS}.') iso_exp_min = np.amin(iso_exps) iso_exp_max = np.amax(iso_exps) if not np.isclose(iso_exp_max, iso_exp_min, iso_exp_tol): raise AssertionError(f'ISO*exp min: {iso_exp_min}, max: {iso_exp_max}, ' f'TOL:{iso_exp_tol}') g_gain_min = np.amin(g_gains) g_gain_max = np.amax(g_gains) if not np.isclose(g_gain_max, g_gain_min, _GGAIN_TOL): raise AssertionError(f'G gain min: {g_gain_min}, max: {g_gain_min}, ' f'TOL: {_GGAIN_TOL}') fd_min = np.amin(fds) fd_max = np.amax(fds) if not np.isclose(fd_max, fd_min, _FD_TOL): raise AssertionError(f'FD min: {fd_min}, max: {fd_min} TOL: {_FD_TOL}') for g in awb_gains: if np.isnan(g): raise AssertionError('AWB gain entry is not a number.') for x in awb_transform: if np.isnan(x): raise AssertionError('AWB transform entry is not a number.') if __name__ == '__main__': test_runner.main()