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