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 circle sizes correctly.""" 15 16 17import logging 18import os.path 19 20import camera_properties_utils 21import capture_request_utils 22import image_processing_utils 23import its_base_test 24import its_session_utils 25from mobly import test_runner 26import numpy as np 27import zoom_capture_utils 28 29_CIRCLISH_RTOL = 0.05 # contour area vs ideal circle area pi*((w+h)/4)**2 30_NAME = os.path.splitext(os.path.basename(__file__))[0] 31_NUM_STEPS = 10 32_TEST_FORMATS = ['yuv'] # list so can be appended for newer Android versions 33_TEST_REQUIRED_MPC = 33 34 35 36class ZoomTest(its_base_test.ItsBaseTest): 37 """Test the camera zoom behavior.""" 38 39 def test_zoom(self): 40 with its_session_utils.ItsSession( 41 device_id=self.dut.serial, 42 camera_id=self.camera_id, 43 hidden_physical_id=self.hidden_physical_id) as cam: 44 props = cam.get_camera_properties() 45 props = cam.override_with_hidden_physical_camera_props(props) 46 camera_properties_utils.skip_unless( 47 camera_properties_utils.zoom_ratio_range(props)) 48 49 # Load chart for scene 50 its_session_utils.load_scene( 51 cam, props, self.scene, self.tablet, self.chart_distance) 52 53 # Determine test zoom range 54 z_range = props['android.control.zoomRatioRange'] 55 debug = self.debug_mode 56 z_min, z_max = float(z_range[0]), float(z_range[1]) 57 camera_properties_utils.skip_unless( 58 z_max >= z_min * zoom_capture_utils.ZOOM_MIN_THRESH) 59 z_max = min(z_max, zoom_capture_utils.ZOOM_MAX_THRESH * z_min) 60 z_list = np.arange(z_min, z_max, (z_max - z_min) / (_NUM_STEPS - 1)) 61 z_list = np.append(z_list, z_max) 62 if z_min != 1: 63 z_list = np.insert(z_list, 0, 1) # make first (reference) zoom 1x 64 logging.debug('Testing zoom range: %s', str(z_list)) 65 66 # Check media performance class 67 media_performance_class = its_session_utils.get_media_performance_class( 68 self.dut.serial) 69 if (media_performance_class >= _TEST_REQUIRED_MPC and 70 cam.is_primary_camera() and 71 cam.has_ultrawide_camera(facing=props['android.lens.facing']) and 72 int(z_min) >= 1): 73 raise AssertionError( 74 f'With primary camera {self.camera_id}, ' 75 f'MPC >= {_TEST_REQUIRED_MPC}, and ' 76 'an ultrawide camera facing in the same direction as the primary, ' 77 'zoom_ratio minimum must be less than 1.0. ' 78 f'Found media performance class {media_performance_class} ' 79 f'and minimum zoom {z_min}.') 80 81 # set TOLs based on camera and test rig params 82 if camera_properties_utils.logical_multi_camera(props): 83 test_tols, size = zoom_capture_utils.get_test_tols_and_cap_size( 84 cam, props, self.chart_distance, debug) 85 else: 86 test_tols = {} 87 fls = props['android.lens.info.availableFocalLengths'] 88 for fl in fls: 89 test_tols[fl] = (zoom_capture_utils.RADIUS_RTOL, 90 zoom_capture_utils.OFFSET_RTOL) 91 yuv_size = capture_request_utils.get_largest_yuv_format(props) 92 size = [yuv_size['width'], yuv_size['height']] 93 logging.debug('capture size: %s', str(size)) 94 logging.debug('test TOLs: %s', str(test_tols)) 95 96 # determine first API level and test_formats to test 97 test_formats = _TEST_FORMATS 98 first_api_level = its_session_utils.get_first_api_level(self.dut.serial) 99 if first_api_level >= its_session_utils.ANDROID14_API_LEVEL: 100 test_formats.append(zoom_capture_utils.JPEG_STR) 101 102 # do captures over zoom range and find circles with cv2 103 img_name_stem = f'{os.path.join(self.log_path, _NAME)}' 104 req = capture_request_utils.auto_capture_request() 105 test_failed = False 106 for fmt in test_formats: 107 logging.debug('testing %s format', fmt) 108 test_data = [] 109 for z in z_list: 110 req['android.control.zoomRatio'] = z 111 logging.debug('zoom ratio: %.3f', z) 112 cam.do_3a( 113 zoom_ratio=z, 114 out_surfaces={ 115 'format': fmt, 116 'width': size[0], 117 'height': size[1] 118 }, 119 repeat_request=None, 120 ) 121 cap = cam.do_capture( 122 req, {'format': fmt, 'width': size[0], 'height': size[1]}, 123 reuse_session=True) 124 125 img = image_processing_utils.convert_capture_to_rgb_image( 126 cap, props=props) 127 img_name = (f'{img_name_stem}_{fmt}_{round(z, 2)}.' 128 f'{zoom_capture_utils.JPEG_STR}') 129 image_processing_utils.write_image(img, img_name) 130 131 # determine radius tolerance of capture 132 cap_fl = cap['metadata']['android.lens.focalLength'] 133 radius_tol, offset_tol = test_tols.get( 134 cap_fl, 135 (zoom_capture_utils.RADIUS_RTOL, zoom_capture_utils.OFFSET_RTOL) 136 ) 137 138 # Scale circlish RTOL for low zoom ratios 139 if z < 1: 140 circlish_rtol = _CIRCLISH_RTOL / z 141 else: 142 circlish_rtol = _CIRCLISH_RTOL 143 144 # Find the center circle in img and check if it's cropped 145 circle = zoom_capture_utils.find_center_circle( 146 img, img_name, size, z, z_list[0], circlish_rtol=circlish_rtol, 147 debug=debug) 148 149 # Zoom is too large to find center circle 150 if circle is None: 151 break 152 test_data.append( 153 zoom_capture_utils.ZoomTestData( 154 result_zoom=z, 155 circle=circle, 156 radius_tol=radius_tol, 157 offset_tol=offset_tol, 158 focal_length=cap_fl 159 ) 160 ) 161 162 if not zoom_capture_utils.verify_zoom_results( 163 test_data, size, z_max, z_min): 164 test_failed = True 165 166 if test_failed: 167 raise AssertionError(f'{_NAME} failed! Check test_log.DEBUG for errors') 168 169if __name__ == '__main__': 170 test_runner.main() 171