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