• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 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 that flash is fired when lighting conditions are dark."""
15
16
17import logging
18import os.path
19
20from mobly import test_runner
21
22import its_base_test
23import camera_properties_utils
24import capture_request_utils
25import image_processing_utils
26import its_session_utils
27import lighting_control_utils
28
29_AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH',
30             4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'}
31_AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED',
32              4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'}
33_FLASH_STATES = {0: 'FLASH_STATE_UNAVAILABLE', 1: 'FLASH_STATE_CHARGING',
34                 2: 'FLASH_STATE_READY', 3: 'FLASH_STATE_FIRED',
35                 4: 'FLASH_STATE_PARTIAL'}
36_GRAD_DELTA_ATOL = 15  # gradiant for tablets as screen aborbs energy
37_MEAN_DELTA_ATOL = 15  # mean used for reflective charts
38
39_FORMAT_NAMES = ('jpeg', 'yuv')
40_IMG_SIZES = ((640, 480), (640, 360))
41_VGA_SIZE = (640, 480)
42
43_PATCH_H = 0.25  # center 25%
44_PATCH_W = 0.25
45_PATCH_X = 0.5 - _PATCH_W/2
46_PATCH_Y = 0.5 - _PATCH_H/2
47_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0]
48_CAPTURE_INTENT_STILL_CAPTURE = 2
49
50
51class AutoFlashTest(its_base_test.ItsBaseTest):
52  """Test that flash is fired when lighting conditions are dark."""
53
54  def test_auto_flash(self):
55    logging.debug('AE_MODES: %s', str(_AE_MODES))
56    logging.debug('AE_STATES: %s', str(_AE_STATES))
57    logging.debug('FLASH_STATES: %s', str(_FLASH_STATES))
58    failure_messages = []
59
60    with its_session_utils.ItsSession(
61        device_id=self.dut.serial,
62        camera_id=self.camera_id,
63        hidden_physical_id=self.hidden_physical_id) as cam:
64      props = cam.get_camera_properties()
65      props = cam.override_with_hidden_physical_camera_props(props)
66      test_name = os.path.join(self.log_path, _TEST_NAME)
67
68      # check SKIP conditions
69      vendor_api_level = its_session_utils.get_vendor_api_level(self.dut.serial)
70      camera_properties_utils.skip_unless(
71          camera_properties_utils.flash(props) and
72          vendor_api_level >= its_session_utils.ANDROID13_API_LEVEL)
73
74      # establish connection with lighting controller
75      arduino_serial_port = lighting_control_utils.lighting_control(
76          self.lighting_cntl, self.lighting_ch)
77
78      # turn OFF lights to darken scene
79      lighting_control_utils.set_lighting_state(
80          arduino_serial_port, self.lighting_ch, 'OFF')
81
82      # turn OFF tablet to darken scene
83      if self.tablet:
84        lighting_control_utils.turn_off_device(self.tablet)
85
86      for fmt_name in _FORMAT_NAMES:
87        for size in _IMG_SIZES:
88          width, height = size
89          if not (fmt_name == 'yuv' and size == _VGA_SIZE):
90            output_sizes = capture_request_utils.get_available_output_sizes(
91                fmt_name, props, match_ar_size=size)
92            if not output_sizes:
93              if size != _VGA_SIZE:
94                logging.debug('No output sizes for format %s, size %sx%s',
95                              fmt_name, width, height)
96                continue
97              else:
98                raise AssertionError(f'No output sizes for format {fmt_name}, '
99                                     f'size {width}x{height}')
100            # pick smallest size out of available output sizes
101            width, height = output_sizes[-1]
102          out_surfaces = {'format': fmt_name, 'width': width, 'height': height}
103          logging.debug('Testing %s format, size: %dx%d',
104                        fmt_name, width, height)
105          no_flash_exp_x_iso = 0
106          no_flash_mean = 0
107          no_flash_grad = 0
108          flash_exp_x_iso = []
109
110          # take capture with no flash as baseline
111          logging.debug('Taking reference frame with no flash.')
112          cam.do_3a(do_af=False)
113          no_flash_req = capture_request_utils.auto_capture_request()
114          no_flash_req[
115              'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
116          cap = cam.do_capture(no_flash_req, out_surfaces)
117          metadata = cap['metadata']
118          exp = int(metadata['android.sensor.exposureTime'])
119          iso = int(metadata['android.sensor.sensitivity'])
120          logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp)
121          logging.debug('AE_MODE (cap): %s',
122                        _AE_MODES[metadata['android.control.aeMode']])
123          logging.debug('AE_STATE (cap): %s',
124                        _AE_STATES[metadata['android.control.aeState']])
125          no_flash_exp_x_iso = exp * iso
126          y, _, _ = image_processing_utils.convert_capture_to_planes(
127              cap, props)
128          patch = image_processing_utils.get_image_patch(
129              y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
130          no_flash_mean = image_processing_utils.compute_image_means(
131              patch)[0]*255
132          no_flash_grad = image_processing_utils.compute_image_max_gradients(
133              patch)[0]*255
134          image_processing_utils.write_image(
135              y, f'{test_name}_{fmt_name}_{width}x{height}_no_flash_Y.jpg')
136
137          # log results
138          logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso)
139          logging.debug('No flash Y grad: %.4f', no_flash_grad)
140          logging.debug('No flash Y mean: %.4f', no_flash_mean)
141
142          # take capture with auto flash enabled
143          logging.debug('Taking capture with auto flash enabled.')
144          flash_fired = False
145
146          cap = capture_request_utils.take_captures_with_flash(cam, out_surfaces)
147          y, _, _ = image_processing_utils.convert_capture_to_planes(
148              cap, props)
149          # Save captured image
150          image_processing_utils.write_image(
151              y, f'{test_name}_{fmt_name}_{width}x{height}_flash_Y.jpg')
152          # evaluate captured image
153          metadata = cap['metadata']
154          exp = int(metadata['android.sensor.exposureTime'])
155          iso = int(metadata['android.sensor.sensitivity'])
156          logging.debug('cap ISO: %d, exp: %d ns', iso, exp)
157          logging.debug('AE_MODE (cap): %s',
158                        _AE_MODES[metadata['android.control.aeMode']])
159          ae_state = _AE_STATES[metadata['android.control.aeState']]
160          logging.debug('AE_STATE (cap): %s', ae_state)
161          flash_state = _FLASH_STATES[metadata['android.flash.state']]
162          logging.debug('FLASH_STATE: %s', flash_state)
163          if flash_state == 'FLASH_STATE_FIRED':
164            logging.debug('Flash fired')
165            flash_fired = True
166            flash_exp_x_iso = exp*iso
167            y, _, _ = image_processing_utils.convert_capture_to_planes(
168                cap, props)
169            patch = image_processing_utils.get_image_patch(
170                y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
171            flash_mean = image_processing_utils.compute_image_means(
172                patch)[0]*255
173            flash_grad = image_processing_utils.compute_image_max_gradients(
174                patch)[0]*255
175
176          if not flash_fired:
177            raise AssertionError('Flash was not fired. '
178                                 f'Format: {fmt_name}, Size: {width}x{height}')
179
180          # log results
181          logging.debug('Flash exposure X ISO %d', flash_exp_x_iso)
182          logging.debug('Flash frames Y grad: %.4f', flash_grad)
183          logging.debug('Flash frames Y mean: %.4f', flash_mean)
184
185          # document incorrect behavior
186          grad_delta = flash_grad - no_flash_grad
187          mean_delta = flash_mean - no_flash_mean
188          if not (grad_delta > _GRAD_DELTA_ATOL or
189                  mean_delta > _MEAN_DELTA_ATOL):
190            failure_messages.append(
191                f'format: {fmt_name}, size: {width}x{height}, '
192                f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, '
193                f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}')
194
195          # Ensure that the flash is turned OFF after flash was fired.
196          req = capture_request_utils.auto_capture_request()
197          req['android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
198          cap = cam.do_capture(req, out_surfaces)
199          flash_state_after = _FLASH_STATES[cap['metadata']
200                                            ['android.flash.state']]
201          logging.debug('FLASH_STATE after flash fired: %s', flash_state_after)
202          if flash_state_after != 'FLASH_STATE_READY':
203            raise AssertionError('Flash should turn OFF after it was fired.')
204
205      # turn lights back ON
206      lighting_control_utils.set_lighting_state(
207          arduino_serial_port, self.lighting_ch, 'ON')
208
209      # assert correct behavior for all formats
210      if failure_messages:
211        raise AssertionError('\n'.join(failure_messages))
212
213if __name__ == '__main__':
214  test_runner.main()
215
216