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"""CameraITS test that device will request/capture correct exp/gain values.""" 15 16import logging 17import os.path 18 19from mobly import test_runner 20 21import its_base_test 22import camera_properties_utils 23import capture_request_utils 24import its_session_utils 25 26 27_NAME = os.path.basename(__file__).split('.')[0] 28# Spec to be within 3% but not over for exposure in capture vs exposure request. 29_RTOL_EXP_GAIN = 0.97 30_TEST_EXP_RANGE = [6E6, 1E9] # ns [6ms, 1s] 31 32 33class RequestCaptureMatchTest(its_base_test.ItsBaseTest): 34 """Test device captures have correct exp/gain values from request.""" 35 36 def test_request_capture_match(self): 37 with its_session_utils.ItsSession( 38 device_id=self.dut.serial, 39 camera_id=self.camera_id, 40 hidden_physical_id=self.hidden_physical_id) as cam: 41 props = cam.get_camera_properties() 42 props = cam.override_with_hidden_physical_camera_props(props) 43 camera_properties_utils.skip_unless( 44 camera_properties_utils.manual_sensor(props) and 45 camera_properties_utils.per_frame_control(props)) 46 first_api_level = its_session_utils.get_first_api_level(self.dut.serial) 47 48 valid_formats = ['yuv', 'jpg'] 49 if camera_properties_utils.raw16(props): 50 valid_formats.insert(0, 'raw') 51 # grab exp/gain ranges from camera 52 sensor_exp_range = props['android.sensor.info.exposureTimeRange'] 53 sens_range = props['android.sensor.info.sensitivityRange'] 54 logging.debug('sensor exposure time range: %s', sensor_exp_range) 55 logging.debug('sensor sensitivity range: %s', sens_range) 56 57 # determine if exposure test range is within sensor reported range 58 if sensor_exp_range[0] == 0: 59 raise AssertionError('Min expsoure == 0') 60 exp_range = [] 61 if sensor_exp_range[0] < _TEST_EXP_RANGE[0]: 62 exp_range.append(_TEST_EXP_RANGE[0]) 63 else: 64 exp_range.append(sensor_exp_range[0]) 65 if sensor_exp_range[1] > _TEST_EXP_RANGE[1]: 66 exp_range.append(_TEST_EXP_RANGE[1]) 67 else: 68 exp_range.append(sensor_exp_range[1]) 69 70 data = {} 71 # build requests 72 for fmt in valid_formats: 73 logging.debug('format: %s', fmt) 74 size = capture_request_utils.get_available_output_sizes(fmt, props)[-1] 75 out_surface = {'width': size[0], 'height': size[1], 'format': fmt} 76 # pylint: disable=protected-access 77 if cam._hidden_physical_id: 78 out_surface['physicalCamera'] = cam._hidden_physical_id 79 reqs = [] 80 index_list = [] 81 for exp in exp_range: 82 for sens in sens_range: 83 reqs.append(capture_request_utils.manual_capture_request(sens, exp)) 84 index_list.append((fmt, exp, sens)) 85 logging.debug('exp_req: %d, sens_req: %d', exp, sens) 86 87 # take shots 88 caps = cam.do_capture(reqs, out_surface) 89 90 # extract exp/sensitivity data 91 for i, cap in enumerate(caps): 92 exposure_cap = cap['metadata']['android.sensor.exposureTime'] 93 sensitivity_cap = cap['metadata']['android.sensor.sensitivity'] 94 data[index_list[i]] = (fmt, exposure_cap, sensitivity_cap) 95 96 # check read/write match across all shots 97 e_failed = [] # exposure time FAILs 98 s_failed = [] # sensitivity FAILs 99 r_failed = [] # sensitivity range FAILs 100 for fmt_req in valid_formats: 101 for e_req in exp_range: 102 for s_req in sens_range: 103 fmt_cap, e_cap, s_cap = data[(fmt_req, e_req, s_req)] 104 if (e_req < e_cap or e_cap / float(e_req) <= _RTOL_EXP_GAIN): 105 e_failed.append({ 106 'format': fmt_cap, 107 'e_req': e_req, 108 'e_cap': e_cap, 109 's_req': s_req, 110 's_cap': s_cap 111 }) 112 if (s_req < s_cap or s_cap / float(s_req) <= _RTOL_EXP_GAIN): 113 s_failed.append({ 114 'format': fmt_cap, 115 'e_req': e_req, 116 'e_cap': e_cap, 117 's_req': s_req, 118 's_cap': s_cap 119 }) 120 if (first_api_level >= its_session_utils.ANDROID14_API_LEVEL and 121 s_cap < sens_range[0]): 122 r_failed.append({ 123 'format': fmt_cap, 124 'e_req': e_req, 125 'e_cap': e_cap, 126 's_req': s_req, 127 's_cap': s_cap 128 }) 129 130 # print results 131 if e_failed: 132 logging.debug('FAILs for exposure time') 133 for fail in e_failed: 134 logging.debug('format: %s, e_req: %d, e_cap: %d, RTOL: %.2f, ', 135 fail['format'], fail['e_req'], fail['e_cap'], 136 _RTOL_EXP_GAIN) 137 logging.debug('s_req: %d, s_cap: %d, RTOL: %.2f', 138 fail['s_req'], fail['s_cap'], _RTOL_EXP_GAIN) 139 if s_failed: 140 logging.debug('FAILs for sensitivity(ISO)') 141 for fail in s_failed: 142 logging.debug('format: %s, s_req: %d, s_cap: %d, RTOL: %.2f, ', 143 fail['format'], fail['s_req'], fail['s_cap'], 144 _RTOL_EXP_GAIN) 145 logging.debug('e_req: %d, e_cap: %d, RTOL: %.2f', 146 fail['e_req'], fail['e_cap'], _RTOL_EXP_GAIN) 147 if r_failed: 148 logging.debug('FAILs for sensitivity(ISO) range') 149 for fail in r_failed: 150 logging.debug('format: %s, s_req: %d, s_cap: %d, RTOL: %.2f, ', 151 fail['format'], fail['s_req'], fail['s_cap'], 152 _RTOL_EXP_GAIN) 153 logging.debug('e_req: %d, e_cap: %d, RTOL: %.2f', 154 fail['e_req'], fail['e_cap'], _RTOL_EXP_GAIN) 155 156 # PASS/FAIL 157 if e_failed: 158 raise AssertionError(f'Exposure fails: {e_failed}') 159 if s_failed: 160 raise AssertionError(f'Sensitivity fails: {s_failed}') 161 if r_failed: 162 raise AssertionError(f'Sensitivity range FAILs: {r_failed}') 163 164 165if __name__ == '__main__': 166 test_runner.main() 167