• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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