• 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
20import camera_properties_utils
21import capture_request_utils
22import image_processing_utils
23import its_base_test
24import its_session_utils
25import lighting_control_utils
26from mobly import test_runner
27
28AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH',
29            4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'}
30AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED',
31             4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'}
32FLASH_STATES = {0: 'FLASH_STATE_UNAVAILABLE', 1: 'FLASH_STATE_CHARGING',
33                2: 'FLASH_STATE_READY', 3: 'FLASH_STATE_FIRED',
34                4: 'FLASH_STATE_PARTIAL'}
35_GRAD_DELTA_ATOL = 15  # gradiant for tablets as screen aborbs energy
36_MEAN_DELTA_ATOL = 15  # mean used for reflective charts
37
38_PATCH_H = 0.25  # center 25%
39_PATCH_W = 0.25
40_PATCH_X = 0.5 - _PATCH_W/2
41_PATCH_Y = 0.5 - _PATCH_H/2
42_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0]
43VGA_W, VGA_H = 640, 480
44_CAPTURE_INTENT_STILL_CAPTURE = 2
45_AE_MODE_ON_AUTO_FLASH = 2
46_CAPTURE_INTENT_PREVIEW = 1
47_CAPTURE_INTENT_STILL_CAPTURE = 2
48_AE_PRECAPTURE_TRIGGER_START = 1
49_AE_PRECAPTURE_TRIGGER_IDLE = 0
50
51
52def turn_off_tablet(tablet_device):
53  output = tablet_device.adb.shell('dumpsys display | grep mScreenState=')
54  output_val = str(output.decode('utf-8')).strip()
55  if 'ON' in output_val:
56    tablet_device.adb.shell(['input', 'keyevent', 'KEYCODE_POWER'])
57
58
59def take_captures_with_flash(cam, fmt):
60  # Run precapture sequence by setting the aePrecapture trigger to
61  # START and capture intent set to Preview.
62  preview_req_start = capture_request_utils.auto_capture_request()
63  preview_req_start[
64      'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH
65  preview_req_start[
66      'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW
67  preview_req_start[
68      'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_START
69  # Repeat preview requests with aePrecapture set to IDLE
70  # until AE is converged.
71  preview_req_idle = capture_request_utils.auto_capture_request()
72  preview_req_idle[
73      'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH
74  preview_req_idle[
75      'android.control.captureIntent'] = _CAPTURE_INTENT_PREVIEW
76  preview_req_idle[
77      'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE
78  # Single still capture request.
79  still_capture_req = capture_request_utils.auto_capture_request()
80  still_capture_req[
81      'android.control.aeMode'] = _AE_MODE_ON_AUTO_FLASH
82  still_capture_req[
83      'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
84  still_capture_req[
85      'android.control.aePrecaptureTrigger'] = _AE_PRECAPTURE_TRIGGER_IDLE
86  cap = cam.do_capture_with_flash(preview_req_start,
87                                  preview_req_idle,
88                                  still_capture_req, fmt)
89  return cap
90
91
92class AutoFlashTest(its_base_test.ItsBaseTest):
93  """Test that flash is fired when lighting conditions are dark."""
94
95  def test_auto_flash(self):
96    logging.debug('AE_MODES: %s', str(AE_MODES))
97    logging.debug('AE_STATES: %s', str(AE_STATES))
98    logging.debug('FLASH_STATES: %s', str(FLASH_STATES))
99
100    with its_session_utils.ItsSession(
101        device_id=self.dut.serial,
102        camera_id=self.camera_id,
103        hidden_physical_id=self.hidden_physical_id) as cam:
104      props = cam.get_camera_properties()
105      props = cam.override_with_hidden_physical_camera_props(props)
106      test_name = os.path.join(self.log_path, _TEST_NAME)
107
108      # check SKIP conditions
109      vendor_api_level = its_session_utils.get_vendor_api_level(self.dut.serial)
110      camera_properties_utils.skip_unless(
111          camera_properties_utils.flash(props) and
112          vendor_api_level >= its_session_utils.ANDROID13_API_LEVEL)
113
114      # establish connection with lighting controller
115      arduino_serial_port = lighting_control_utils.lighting_control(
116          self.lighting_cntl, self.lighting_ch)
117
118      # turn OFF lights to darken scene
119      lighting_control_utils.set_lighting_state(
120          arduino_serial_port, self.lighting_ch, 'OFF')
121
122      # turn OFF tablet to darken scene
123      if self.tablet:
124        turn_off_tablet(self.tablet)
125      fmt_name = 'jpeg'
126      fmt = {'format': fmt_name}
127      logging.debug('Testing %s format.', fmt_name)
128      no_flash_exp_x_iso = 0
129      no_flash_mean = 0
130      no_flash_grad = 0
131      flash_exp_x_iso = []
132
133      # take capture with no flash as baseline
134      logging.debug('Taking reference frame with no flash.')
135      cam.do_3a(do_af=False)
136      no_flash_req = capture_request_utils.auto_capture_request()
137      no_flash_req[
138          'android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
139      cap = cam.do_capture(no_flash_req, fmt)
140      metadata = cap['metadata']
141      exp = int(metadata['android.sensor.exposureTime'])
142      iso = int(metadata['android.sensor.sensitivity'])
143      logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp)
144      logging.debug('AE_MODE (cap): %s',
145                    AE_MODES[metadata['android.control.aeMode']])
146      logging.debug('AE_STATE (cap): %s',
147                    AE_STATES[metadata['android.control.aeState']])
148      no_flash_exp_x_iso = exp * iso
149      y, _, _ = image_processing_utils.convert_capture_to_planes(
150          cap, props)
151      patch = image_processing_utils.get_image_patch(
152          y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
153      no_flash_mean = image_processing_utils.compute_image_means(
154          patch)[0]*255
155      no_flash_grad = image_processing_utils.compute_image_max_gradients(
156          patch)[0]*255
157      image_processing_utils.write_image(
158          y, f'{test_name}_{fmt_name}_no_flash_Y.jpg')
159
160      # log results
161      logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso)
162      logging.debug('No flash Y grad: %.4f', no_flash_grad)
163      logging.debug('No flash Y mean: %.4f', no_flash_mean)
164
165      # take capture with auto flash enabled
166      logging.debug('Taking capture with auto flash enabled.')
167      flash_fired = False
168
169      cap = take_captures_with_flash(cam, fmt)
170      y, _, _ = image_processing_utils.convert_capture_to_planes(
171          cap, props)
172      # Save captured image
173      image_processing_utils.write_image(y,
174                                         f'{test_name}_{fmt_name}_flash_Y.jpg')
175      # evaluate captured image
176      metadata = cap['metadata']
177      exp = int(metadata['android.sensor.exposureTime'])
178      iso = int(metadata['android.sensor.sensitivity'])
179      logging.debug('cap ISO: %d, exp: %d ns', iso, exp)
180      logging.debug('AE_MODE (cap): %s',
181                    AE_MODES[metadata['android.control.aeMode']])
182      ae_state = AE_STATES[metadata['android.control.aeState']]
183      logging.debug('AE_STATE (cap): %s', ae_state)
184      flash_state = FLASH_STATES[metadata['android.flash.state']]
185      logging.debug('FLASH_STATE: %s', flash_state)
186      if flash_state == 'FLASH_STATE_FIRED':
187        logging.debug('Flash fired')
188        flash_fired = True
189        flash_exp_x_iso = exp*iso
190        y, _, _ = image_processing_utils.convert_capture_to_planes(
191            cap, props)
192        patch = image_processing_utils.get_image_patch(
193            y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
194        flash_mean = image_processing_utils.compute_image_means(
195            patch)[0]*255
196        flash_grad = image_processing_utils.compute_image_max_gradients(
197            patch)[0]*255
198
199      if not flash_fired:
200        raise AssertionError('Flash was not fired.')
201
202      # log results
203      logging.debug('Flash exposure X ISO %d', flash_exp_x_iso)
204      logging.debug('Flash frames Y grad: %.4f', flash_grad)
205      logging.debug('Flash frames Y mean: %.4f', flash_mean)
206
207      # assert correct behavior
208      grad_delta = flash_grad - no_flash_grad
209      mean_delta = flash_mean - no_flash_mean
210      if not (grad_delta > _GRAD_DELTA_ATOL or
211              mean_delta > _MEAN_DELTA_ATOL):
212        raise AssertionError(
213            f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, '
214            f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}')
215
216      # Ensure that the flash is turned OFF after flash was fired.
217      req = capture_request_utils.auto_capture_request()
218      req['android.control.captureIntent'] = _CAPTURE_INTENT_STILL_CAPTURE
219      cap = cam.do_capture(req, fmt)
220      flash_state_after = FLASH_STATES[cap['metadata']['android.flash.state']]
221      logging.debug('FLASH_STATE after flash fired: %s', flash_state_after)
222      if flash_state_after != 'FLASH_STATE_READY':
223        raise AssertionError('Flash should turn OFF after it was fired.')
224
225      # turn lights back ON
226      lighting_control_utils.set_lighting_state(
227          arduino_serial_port, self.lighting_ch, 'ON')
228
229if __name__ == '__main__':
230  test_runner.main()
231
232