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