xref: /aosp_15_r20/cts/apps/CameraITS/tests/scene1_1/test_burst_capture.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1# Copyright 2016 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"""Verify capture burst of full size images is fast enough to not timeout."""
15
16import logging
17import os
18
19from mobly import test_runner
20
21import its_base_test
22import camera_properties_utils
23import capture_request_utils
24import image_processing_utils
25import its_session_utils
26
27_FRAME_TIME_DELTA_RTOL = 0.1  # allow 10% variation from reported value
28_NAME = os.path.splitext(os.path.basename(__file__))[0]
29_NR_MODE_FAST = 1  # burst capture uses noise reduction mode FAST
30_NUM_TEST_FRAMES = 15
31_PATCH_H = 0.1  # center 10% patch params
32_PATCH_W = 0.1
33_PATCH_X = 0.5 - _PATCH_W/2
34_PATCH_Y = 0.5 - _PATCH_H/2
35_START_FRAME = 2  # allow 1st frame to have some push out (see test_jitter.py)
36_THRESH_MIN_LEVEL = 0.1  # check images aren't too dark
37
38
39class BurstCaptureTest(its_base_test.ItsBaseTest):
40  """Test capture a burst of full size images is fast enough and doesn't timeout.
41
42  This test verifies that the entire capture pipeline can keep up the speed of
43  fullsize capture + CPU read for at least some time.
44  """
45
46  def test_burst_capture(self):
47    with its_session_utils.ItsSession(
48        device_id=self.dut.serial,
49        camera_id=self.camera_id,
50        hidden_physical_id=self.hidden_physical_id) as cam:
51      props = cam.get_camera_properties()
52      props = cam.override_with_hidden_physical_camera_props(props)
53
54      # Check SKIP conditions
55      camera_properties_utils.skip_unless(
56          camera_properties_utils.backward_compatible(props) and
57          camera_properties_utils.burst_capture_capable
58      )
59
60      # Load chart for scene
61      its_session_utils.load_scene(
62          cam, props, self.scene, self.tablet, self.chart_distance)
63
64      req = capture_request_utils.auto_capture_request()
65      if camera_properties_utils.noise_reduction_mode(props, _NR_MODE_FAST):
66        req['android.noiseReduction.mode'] = _NR_MODE_FAST
67      camera_properties_utils.log_minimum_focus_distance(props)
68      cam.do_3a()
69      caps = cam.do_capture([req] * _NUM_TEST_FRAMES)
70      img = image_processing_utils.convert_capture_to_rgb_image(
71          caps[0], props=props)
72      name_with_log_path = os.path.join(self.log_path, _NAME)
73      image_processing_utils.write_image(img, f'{name_with_log_path}.jpg')
74      logging.debug('Image W, H: %d, %d', caps[0]['width'], caps[0]['height'])
75
76      # Confirm center patch brightness
77      patch = image_processing_utils.get_image_patch(
78          img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
79      r, g, b = image_processing_utils.compute_image_means(patch)
80      logging.debug('RGB levels %.3f, %.3f, %.3f', r, g, b)
81      if g < _THRESH_MIN_LEVEL:
82        raise AssertionError(f'Image is too dark! G center patch avg: {g:.3f}, '
83                             f'THRESH: {_THRESH_MIN_LEVEL}')
84
85      # Check frames are consecutive
86      error_msg = []
87      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
88      frame_time_duration_deltas = []
89      if first_api_level >= its_session_utils.ANDROID15_API_LEVEL:
90        frame_times = [cap['metadata']['android.sensor.timestamp']
91                       for cap in caps]
92        for i, time in enumerate(frame_times):
93          if i < _START_FRAME:
94            continue
95          frame_time_delta = time - frame_times[i-1]
96          frame_duration = caps[i]['metadata']['android.sensor.frameDuration']
97          logging.debug('cap %d frameDuration: %d ns', i, frame_duration)
98          frame_time_delta_atol = frame_duration * (1+_FRAME_TIME_DELTA_RTOL)
99          frame_time_duration_deltas.append(frame_time_delta - frame_duration)
100          logging.debug(
101              'frame_time-frameDuration: %d ns', frame_time_delta-frame_duration
102          )
103          if frame_time_delta > frame_time_delta_atol:
104            error_msg.append(
105                f'Frame {i-1} --> {i} delta: {frame_time_delta}, '
106                f'ATOL: {frame_time_delta_atol:.1f} ns. '
107            )
108        # Note: Do not change from print to logging. print used for data-mining
109        print(
110            f'{_NAME}_max_frame_time_minus_frameDuration_ns: '
111            f'{max(frame_time_duration_deltas)}'
112        )
113        if error_msg:
114          raise AssertionError(f'Frame drop(s)! {error_msg}')
115
116
117if __name__ == '__main__':
118  test_runner.main()
119