1# Copyright 2018 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"""Verifies sub-cameras have similar RGB values for gray patch.""" 15 16 17import logging 18import os.path 19 20from mobly import test_runner 21import numpy as np 22 23import its_base_test 24import camera_properties_utils 25import capture_request_utils 26import image_processing_utils 27import its_session_utils 28 29_NAME = os.path.splitext(os.path.basename(__file__))[0] 30_PATCH_H = 0.0625 # 1/16 x 1/16 in center of image 31_PATCH_W = 0.0625 32_PATCH_X = 0.5 - _PATCH_W/2 33_PATCH_Y = 0.5 - _PATCH_H/2 34_THRESH_DIFF = 0.06 35_THRESH_GAIN = 0.1 36_THRESH_EXP = 0.05 37 38 39class MultiCameraMatchTest(its_base_test.ItsBaseTest): 40 """Test both cameras give similar RGB values for gray patch. 41 42 This test uses android.lens.info.availableFocalLengths to determine 43 subcameras. The test will take images of the gray chart for each cameras, 44 crop the center patch, and compare the Y (of YUV) means of the two images. 45 Y means must be within _THRESH_DIFF for the test to pass. 46 47 Cameras that use android.control.zoomRatioRange will have only 1 focal 48 length and will need separate test. 49 """ 50 51 def test_multi_camera_match(self): 52 logging.debug('Starting %s', _NAME) 53 yuv_sizes = {} 54 with its_session_utils.ItsSession( 55 device_id=self.dut.serial, 56 camera_id=self.camera_id, 57 hidden_physical_id=self.hidden_physical_id) as cam: 58 props = cam.get_camera_properties() 59 props = cam.override_with_hidden_physical_camera_props(props) 60 log_path = self.log_path 61 62 # check SKIP conditions 63 camera_properties_utils.skip_unless( 64 camera_properties_utils.per_frame_control(props) and 65 camera_properties_utils.logical_multi_camera(props)) 66 67 # Load chart for scene 68 its_session_utils.load_scene( 69 cam, props, self.scene, self.tablet, self.chart_distance) 70 71 ids = camera_properties_utils.logical_multi_camera_physical_ids(props) 72 for i in ids: 73 physical_props = cam.get_camera_properties_by_id(i) 74 camera_properties_utils.skip_unless( 75 not camera_properties_utils.mono_camera(physical_props) and 76 camera_properties_utils.backward_compatible(physical_props)) 77 yuv_sizes[i] = capture_request_utils.get_available_output_sizes( 78 'yuv', physical_props) 79 if i == ids[0]: # get_available_output_sizes returns sorted list 80 yuv_match_sizes = yuv_sizes[i] 81 else: 82 yuv_match_sizes = list( 83 set(yuv_sizes[i]).intersection(yuv_match_sizes)) 84 85 # find matched size for captures 86 yuv_match_sizes.sort() 87 w = yuv_match_sizes[-1][0] 88 h = yuv_match_sizes[-1][1] 89 logging.debug('Matched YUV size: (%d, %d)', w, h) 90 91 # do 3a and create requests 92 cam.do_3a() 93 reqs = [] 94 avail_fls = sorted(props['android.lens.info.availableFocalLengths'], 95 reverse=True) 96 # SKIP test if only 1 focal length 97 camera_properties_utils.skip_unless(len(avail_fls) > 1) 98 99 for i, fl in enumerate(avail_fls): 100 reqs.append(capture_request_utils.auto_capture_request()) 101 reqs[i]['android.lens.focalLength'] = fl 102 if i > 0: 103 # Calculate the active sensor region for a non-cropped image 104 zoom = avail_fls[0] / fl 105 aa = props['android.sensor.info.activeArraySize'] 106 aa_w, aa_h = aa['right'] - aa['left'], aa['bottom'] - aa['top'] 107 108 # Calculate a center crop region. 109 assert zoom >= 1 110 crop_w = aa_w // zoom 111 crop_h = aa_h // zoom 112 crop_region = {'left': aa_w // 2 - crop_w // 2, 113 'top': aa_h // 2 - crop_h // 2, 114 'right': aa_w // 2 + crop_w // 2, 115 'bottom': aa_h // 2 + crop_h // 2} 116 reqs[i]['android.scaler.cropRegion'] = crop_region 117 118 # capture YUVs 119 y_means = {} 120 e_msg = '' 121 fmt = [{'format': 'yuv', 'width': w, 'height': h}] 122 caps = cam.do_capture(reqs, fmt) 123 for i, fl in enumerate(avail_fls): 124 img = image_processing_utils.convert_capture_to_rgb_image( 125 caps[i], props=props) 126 image_processing_utils.write_image(img, '%s_yuv_fl=%s.jpg' % ( 127 os.path.join(log_path, _NAME), fl)) 128 y, _, _ = image_processing_utils.convert_capture_to_planes( 129 caps[i], props=props) 130 y_mean = image_processing_utils.compute_image_means( 131 image_processing_utils.get_image_patch( 132 y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H))[0] 133 msg = 'y[%s]: %.3f, ' % (fl, y_mean) 134 logging.debug(msg) 135 e_msg += msg 136 y_means[fl] = y_mean 137 138 # compare Y means 139 e_msg += 'TOL=%.5f' % _THRESH_DIFF 140 assert np.isclose(max(y_means.values()), min(y_means.values()), 141 rtol=_THRESH_DIFF), e_msg 142 143if __name__ == '__main__': 144 test_runner.main() 145