xref: /aosp_15_r20/cts/apps/CameraITS/tests/scene6_tele/test_zoom_tele.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1# Copyright 2024 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 for the TELE camera."""
15
16
17import logging
18import math
19import os.path
20
21import camera_properties_utils
22import capture_request_utils
23import image_processing_utils
24import opencv_processing_utils
25import its_base_test
26import its_session_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_NUMBER_OF_CAMERAS_TO_TEST = 2  # WIDE and TELE
34_NUM_STEPS_PER_SECTION = 10
35# YUV only to improve marker detection, JPEG is tested in test_zoom
36_TEST_FORMATS = ('yuv',)
37# Empirically found zoom ratio for main cameras without custom offset behavior
38_WIDE_ZOOM_RATIO_MIN = 2.2
39# Empirically found zoom ratio for TELE cameras
40_TELE_TRANSITION_ZOOM_RATIO = 5.0
41_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL = 0.1
42
43
44class ZoomTestTELE(its_base_test.ItsBaseTest):
45  """Test the camera zoom behavior for the TELE camera, if available."""
46
47  def test_zoom_tele(self):
48    with its_session_utils.ItsSession(
49        device_id=self.dut.serial,
50        camera_id=self.camera_id,
51        # Use logical camera for captures. Physical ID only for result tracking
52        hidden_physical_id=None) as cam:
53      camera_properties_utils.skip_unless(self.hidden_physical_id is not None)
54      props = cam.get_camera_properties()
55      physical_props = cam.get_camera_properties_by_id(self.hidden_physical_id)
56      physical_fov = float(cam.calc_camera_fov(physical_props))
57      is_tele = physical_fov < opencv_processing_utils.FOV_THRESH_TELE
58      camera_properties_utils.skip_unless(
59          camera_properties_utils.zoom_ratio_range(props) and
60          is_tele)
61
62      # Load chart for scene
63      its_session_utils.load_scene(
64          cam, props, self.scene, self.tablet,
65          # Ensure markers are large enough by loading unscaled chart
66          its_session_utils.CHART_DISTANCE_NO_SCALING)
67
68      # Determine test zoom range
69      z_range = props['android.control.zoomRatioRange']
70      debug = self.debug_mode
71      z_min, z_max = float(z_range[0]), float(z_range[1])
72      camera_properties_utils.skip_unless(
73          z_max >= z_min * zoom_capture_utils.ZOOM_MIN_THRESH)
74      z_min = max(z_min, _WIDE_ZOOM_RATIO_MIN)  # Force W
75      tele_transition_zoom_ratio = min(z_max, _TELE_TRANSITION_ZOOM_RATIO)
76      # Increase data near transition ratio
77      transition_z_list = np.arange(
78          z_min,
79          tele_transition_zoom_ratio,
80          (tele_transition_zoom_ratio - z_min) / (_NUM_STEPS_PER_SECTION - 1)
81      )
82      tele_z_list = np.array([])
83      if z_max > tele_transition_zoom_ratio:
84        tele_z_list = np.arange(
85            tele_transition_zoom_ratio,
86            z_max,
87            (z_max - tele_transition_zoom_ratio) / (_NUM_STEPS_PER_SECTION - 1)
88        )
89      z_list = np.unique(np.concatenate((transition_z_list, tele_z_list)))
90      z_list = np.append(z_list, z_max)
91      logging.debug('Testing zoom range: %s', str(z_list))
92
93      # Set TOLs based on camera and test rig params
94      if camera_properties_utils.logical_multi_camera(props):
95        test_tols, size = zoom_capture_utils.get_test_tols_and_cap_size(
96            cam, props, self.chart_distance, debug)
97      else:
98        test_tols = {}
99        focal_lengths = props['android.lens.info.availableFocalLengths']
100        logging.debug('focal lengths: %s', focal_lengths)
101        for fl in focal_lengths:
102          test_tols[fl] = (zoom_capture_utils.RADIUS_RTOL,
103                           zoom_capture_utils.OFFSET_RTOL)
104        yuv_size = capture_request_utils.get_largest_format('yuv', props)
105        size = [yuv_size['width'], yuv_size['height']]
106      logging.debug('capture size: %s', str(size))
107      logging.debug('test TOLs: %s', str(test_tols))
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
114      for fmt in _TEST_FORMATS:
115        logging.debug('testing %s format', fmt)
116        test_data = []
117        all_aruco_ids = []
118        all_aruco_corners = []
119        images = []
120        found_markers = False
121        for z in z_list:
122          req['android.control.zoomRatio'] = z
123          logging.debug('zoom ratio: %.3f', z)
124          cam.do_3a(
125              zoom_ratio=z,
126              out_surfaces={
127                  'format': fmt,
128                  'width': size[0],
129                  'height': size[1]
130              },
131              repeat_request=None,
132          )
133          cap = cam.do_capture(
134              req, {'format': fmt, 'width': size[0], 'height': size[1]},
135              reuse_session=True)
136          cap_physical_id = (
137              cap['metadata']['android.logicalMultiCamera.activePhysicalId']
138          )
139          cap_zoom_ratio = float(cap['metadata']['android.control.zoomRatio'])
140          if not math.isclose(cap_zoom_ratio, z,
141                              rel_tol=_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL):
142            raise AssertionError(
143                'Request and result zoom ratios too different! '
144                f'Request zoom ratio: {z}. '
145                f'Result zoom ratio: {cap_zoom_ratio}. ',
146                f'RTOL: {_ZOOM_RATIO_REQUEST_RESULT_DIFF_RTOL}'
147            )
148          img = image_processing_utils.convert_capture_to_rgb_image(
149              cap, props=props)
150          img_name = (f'{img_name_stem}_{fmt}_{z:.2f}.'
151                      f'{zoom_capture_utils.JPEG_STR}')
152          image_processing_utils.write_image(img, img_name)
153
154          # Determine radius tolerance of capture
155          cap_fl = cap['metadata']['android.lens.focalLength']
156          radius_tol, offset_tol = test_tols.get(
157              cap_fl,
158              (zoom_capture_utils.RADIUS_RTOL, zoom_capture_utils.OFFSET_RTOL)
159          )
160
161          # Find ArUco markers
162          bgr_img = cv2.cvtColor(
163              image_processing_utils.convert_image_to_uint8(img),
164              cv2.COLOR_RGB2BGR
165          )
166          try:
167            corners, ids, _ = opencv_processing_utils.find_aruco_markers(
168                bgr_img,
169                (f'{img_name_stem}_{fmt}_{z:.2f}_'
170                 f'ArUco.{zoom_capture_utils.JPEG_STR}'),
171                aruco_marker_count=1,
172                force_greyscale=True  # Maximize number of markers detected
173            )
174            found_markers = True
175          except AssertionError as e:
176            logging.debug('Could not find ArUco marker at zoom ratio %.2f: %s',
177                          z, e)
178            if found_markers:
179              logging.debug('No more ArUco markers found at zoom %.2f', z)
180              break
181            else:
182              logging.debug('Still no ArUco markers found at zoom %.2f', z)
183              continue
184          all_aruco_corners.append([corner[0] for corner in corners])
185          all_aruco_ids.append([id[0] for id in ids])
186          images.append(bgr_img)
187
188          test_data.append(
189              zoom_capture_utils.ZoomTestData(
190                  result_zoom=cap_zoom_ratio,
191                  radius_tol=radius_tol,
192                  offset_tol=offset_tol,
193                  focal_length=cap_fl,
194                  physical_id=cap_physical_id,
195              )
196          )
197
198        # Find ArUco markers in all captures and update test data
199        zoom_capture_utils.update_zoom_test_data_with_shared_aruco_marker(
200            test_data, all_aruco_ids, all_aruco_corners, size)
201        test_artifacts_name_stem = f'{img_name_stem}_{fmt}'
202        # Mark ArUco marker center and image center
203        opencv_processing_utils.mark_zoom_images(
204            images, test_data, test_artifacts_name_stem)
205
206        if not zoom_capture_utils.verify_zoom_data(
207            test_data, size,
208            offset_plot_name_stem=test_artifacts_name_stem,
209            number_of_cameras_to_test=_NUMBER_OF_CAMERAS_TO_TEST):
210          test_failed = True
211
212    if test_failed:
213      raise AssertionError(f'{_NAME} failed! Check test_log.DEBUG for errors')
214
215if __name__ == '__main__':
216  test_runner.main()
217