xref: /aosp_15_r20/cts/apps/CameraITS/tests/scene6/test_zoom.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1# Copyright 2020 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 zoom ratio scales ArUco marker sizes correctly."""
15
16
17import logging
18import math
19import os.path
20
21import camera_properties_utils
22import capture_request_utils
23import image_processing_utils
24import its_base_test
25import its_session_utils
26import opencv_processing_utils
27import cv2
28from mobly import test_runner
29import numpy as np
30import zoom_capture_utils
31
32_NAME = os.path.splitext(os.path.basename(__file__))[0]
33_NUM_STEPS = 10
34_TEST_FORMATS = ['yuv']  # list so can be appended for newer Android versions
35_TEST_REQUIRED_MPC = 33
36_SINGLE_CAMERA_NUMBER_OF_CAMERAS_TO_TEST = 1
37_ULTRAWIDE_NUMBER_OF_CAMERAS_TO_TEST = 2  # UW and W
38# Wider zoom ratio range will be tested by test_zoom_tele
39_WIDE_ZOOM_RATIO_MAX = 2.2
40_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL = 0.1
41
42
43class ZoomTest(its_base_test.ItsBaseTest):
44  """Test the camera zoom behavior."""
45
46  def test_zoom(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      camera_properties_utils.skip_unless(
54          camera_properties_utils.zoom_ratio_range(props))
55
56      # Load chart for scene
57      its_session_utils.load_scene(
58          cam, props, self.scene, self.tablet, self.chart_distance)
59
60      # Determine test zoom range
61      z_range = props['android.control.zoomRatioRange']
62      debug = self.debug_mode
63      z_min, z_max = float(z_range[0]), float(z_range[1])
64      camera_properties_utils.skip_unless(
65          z_max >= z_min * zoom_capture_utils.ZOOM_MIN_THRESH)
66      z_max = min(z_max, _WIDE_ZOOM_RATIO_MAX)
67      z_list = np.arange(z_min, z_max, (z_max - z_min) / (_NUM_STEPS - 1))
68      z_list = np.append(z_list, z_max)
69      logging.debug('Testing zoom range: %s', str(z_list))
70
71      # Check media performance class
72      media_performance_class = its_session_utils.get_media_performance_class(
73          self.dut.serial)
74      ultrawide_camera_found = cam.has_ultrawide_camera(
75          facing=props['android.lens.facing'])
76      if (media_performance_class >= _TEST_REQUIRED_MPC and
77          cam.is_primary_camera() and
78          ultrawide_camera_found and
79          int(z_min) >= 1):
80        raise AssertionError(
81            f'With primary camera {self.camera_id}, '
82            f'MPC >= {_TEST_REQUIRED_MPC}, and '
83            'an ultrawide camera facing in the same direction as the primary, '
84            'zoom_ratio minimum must be less than 1.0. '
85            f'Found media performance class {media_performance_class} '
86            f'and minimum zoom {z_min}.')
87
88      # set TOLs based on camera and test rig params
89      if camera_properties_utils.logical_multi_camera(props):
90        test_tols, size = zoom_capture_utils.get_test_tols_and_cap_size(
91            cam, props, self.chart_distance, debug)
92      else:
93        test_tols = {}
94        fls = props['android.lens.info.availableFocalLengths']
95        for fl in fls:
96          test_tols[fl] = (zoom_capture_utils.RADIUS_RTOL,
97                           zoom_capture_utils.OFFSET_RTOL)
98        yuv_size = capture_request_utils.get_largest_format('yuv', props)
99        size = [yuv_size['width'], yuv_size['height']]
100      logging.debug('capture size: %s', str(size))
101      logging.debug('test TOLs: %s', str(test_tols))
102
103      # determine first API level and test_formats to test
104      test_formats = _TEST_FORMATS
105      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
106      if first_api_level >= its_session_utils.ANDROID14_API_LEVEL:
107        test_formats.append(zoom_capture_utils.JPEG_STR)
108
109      # do captures over zoom range and find ArUco markers with cv2
110      img_name_stem = f'{os.path.join(self.log_path, _NAME)}'
111      req = capture_request_utils.auto_capture_request()
112      test_failed = False
113      for fmt in test_formats:
114        logging.debug('testing %s format', fmt)
115        test_data = []
116        all_aruco_ids = []
117        all_aruco_corners = []
118        images = []
119        physical_ids = set()
120        for z in z_list:
121          req['android.control.zoomRatio'] = z
122          logging.debug('zoom ratio: %.3f', z)
123          cam.do_3a(
124              zoom_ratio=z,
125              out_surfaces={
126                  'format': fmt,
127                  'width': size[0],
128                  'height': size[1]
129              },
130              repeat_request=None,
131          )
132          cap = cam.do_capture(
133              req, {'format': fmt, 'width': size[0], 'height': size[1]},
134              reuse_session=True)
135          cap_physical_id = (
136              cap['metadata']['android.logicalMultiCamera.activePhysicalId']
137          )
138          cap_zoom_ratio = float(cap['metadata']['android.control.zoomRatio'])
139          if not math.isclose(cap_zoom_ratio, z,
140                              rel_tol=_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL):
141            raise AssertionError(
142                'Request and result zoom ratios too different! '
143                f'Request zoom ratio: {z}. '
144                f'Result zoom ratio: {cap_zoom_ratio}. ',
145                f'RTOL: {_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL}'
146            )
147
148          physical_ids.add(cap_physical_id)
149          logging.debug('Physical IDs: %s', physical_ids)
150
151          img = image_processing_utils.convert_capture_to_rgb_image(
152              cap, props=props)
153          img_name = (f'{img_name_stem}_{fmt}_{round(z, 2)}.'
154                      f'{zoom_capture_utils.JPEG_STR}')
155          image_processing_utils.write_image(img, img_name)
156
157          # determine radius tolerance of capture
158          cap_fl = cap['metadata']['android.lens.focalLength']
159          radius_tol, offset_tol = test_tols.get(
160              cap_fl,
161              (zoom_capture_utils.RADIUS_RTOL, zoom_capture_utils.OFFSET_RTOL)
162          )
163
164          # Find ArUco markers
165          bgr_img = cv2.cvtColor(
166              image_processing_utils.convert_image_to_uint8(img),
167              cv2.COLOR_RGB2BGR
168          )
169          try:
170            corners, ids, _ = opencv_processing_utils.find_aruco_markers(
171                bgr_img,
172                (f'{img_name_stem}_{fmt}_{z:.2f}_'
173                 f'ArUco.{zoom_capture_utils.JPEG_STR}'),
174                aruco_marker_count=1,
175                force_greyscale=True  # Maximize number of markers detected
176            )
177          except AssertionError as e:
178            logging.debug('Could not find ArUco marker at zoom ratio %.2f: %s',
179                          z, e)
180            break
181          all_aruco_corners.append([corner[0] for corner in corners])
182          all_aruco_ids.append([id[0] for id in ids])
183          images.append(bgr_img)
184
185          test_data.append(
186              zoom_capture_utils.ZoomTestData(
187                  result_zoom=cap_zoom_ratio,
188                  radius_tol=radius_tol,
189                  offset_tol=offset_tol,
190                  focal_length=cap_fl,
191                  physical_id=cap_physical_id,
192              )
193          )
194
195        # Find ArUco markers in all captures and update test data
196        zoom_capture_utils.update_zoom_test_data_with_shared_aruco_marker(
197            test_data, all_aruco_ids, all_aruco_corners, size)
198        # Mark ArUco marker center and image center
199        opencv_processing_utils.mark_zoom_images(
200            images, test_data, f'{img_name_stem}_{fmt}')
201
202        number_of_cameras_to_test = (
203            _ULTRAWIDE_NUMBER_OF_CAMERAS_TO_TEST
204            if ultrawide_camera_found
205            else _SINGLE_CAMERA_NUMBER_OF_CAMERAS_TO_TEST
206        )
207        if not zoom_capture_utils.verify_zoom_data(
208            test_data, size,
209            offset_plot_name_stem=f'{img_name_stem}_{fmt}',
210            number_of_cameras_to_test=number_of_cameras_to_test):
211          test_failed = True
212
213    if test_failed:
214      raise AssertionError(f'{_NAME} failed! Check test_log.DEBUG for errors')
215
216if __name__ == '__main__':
217  test_runner.main()
218