1# Copyright 2013 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 valid data return from CaptureResult objects.""" 15 16 17import logging 18import os.path 19import matplotlib.pyplot 20from mobly import test_runner 21# mplot3 is required for 3D plots in draw_lsc_plot() though not called directly. 22from mpl_toolkits import mplot3d # pylint: disable=unused-import 23import numpy as np 24 25# required for 3D plots 26import its_base_test 27import camera_properties_utils 28import capture_request_utils 29import its_session_utils 30 31AWB_GAINS_NUM = 4 32AWB_XFORM_NUM = 9 33ISCLOSE_ATOL = 0.05 # not for absolute ==, but if something grossly wrong 34MANUAL_AWB_GAINS = [1, 1.5, 2.0, 3.0] 35MANUAL_AWB_XFORM = capture_request_utils.float_to_rational([-1.5, -1.0, -0.5, 36 0.0, 0.5, 1.0, 37 1.5, 2.0, 3.0]) 38# The camera HAL may not support different gains for two G channels. 39MANUAL_GAINS_OK = [[1, 1.5, 2.0, 3.0], 40 [1, 1.5, 1.5, 3.0], 41 [1, 2.0, 2.0, 3.0]] 42MANUAL_TONEMAP = [0, 0, 1, 1] # Linear tonemap 43MANUAL_REGION = [{'x': 8, 'y': 8, 'width': 128, 'height': 128, 'weight': 1}] 44NAME = os.path.splitext(os.path.basename(__file__))[0] 45 46 47def is_close_rational(n1, n2): 48 return np.isclose(capture_request_utils.rational_to_float(n1), 49 capture_request_utils.rational_to_float(n2), 50 atol=ISCLOSE_ATOL) 51 52 53def draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, name, log_path): 54 for ch in range(4): 55 fig = matplotlib.pyplot.figure() 56 ax = fig.gca(projection='3d') 57 xs = np.array([range(lsc_map_w)] * lsc_map_h).reshape(lsc_map_h, lsc_map_w) 58 ys = np.array([[i]*lsc_map_w for i in range(lsc_map_h)]).reshape( 59 lsc_map_h, lsc_map_w) 60 zs = np.array(lsc_map[ch::4]).reshape(lsc_map_h, lsc_map_w) 61 ax.plot_wireframe(xs, ys, zs) 62 matplotlib.pyplot.savefig('%s_plot_lsc_%s_ch%d.png' % ( 63 os.path.join(log_path, NAME), name, ch)) 64 65 66def metadata_checks(metadata, props): 67 """Common checks on AWB color correction matrix. 68 69 Args: 70 metadata: capture metadata 71 props: camera properties 72 """ 73 awb_gains = metadata['android.colorCorrection.gains'] 74 awb_xform = metadata['android.colorCorrection.transform'] 75 logging.debug('AWB gains: %s', str(awb_gains)) 76 logging.debug('AWB transform: %s', str( 77 [capture_request_utils.rational_to_float(t) for t in awb_xform])) 78 if props['android.control.maxRegionsAe'] > 0: 79 logging.debug('AE region: %s', str(metadata['android.control.aeRegions'])) 80 if props['android.control.maxRegionsAf'] > 0: 81 logging.debug('AF region: %s', str(metadata['android.control.afRegions'])) 82 if props['android.control.maxRegionsAwb'] > 0: 83 logging.debug('AWB region: %s', str(metadata['android.control.awbRegions'])) 84 85 # Color correction gains and transform should be the same size 86 assert len(awb_gains) == AWB_GAINS_NUM 87 assert len(awb_xform) == AWB_XFORM_NUM 88 89 90def test_auto(cam, props, log_path): 91 """Do auto capture and test values. 92 93 Args: 94 cam: camera object 95 props: camera properties 96 log_path: path for plot directory 97 """ 98 logging.debug('Testing auto capture results') 99 req = capture_request_utils.auto_capture_request() 100 req['android.statistics.lensShadingMapMode'] = 1 101 sync_latency = camera_properties_utils.sync_latency(props) 102 103 # Get 3A lock first, so auto values in capture result are populated properly. 104 mono_camera = camera_properties_utils.mono_camera(props) 105 cam.do_3a(do_af=False, mono_camera=mono_camera) 106 107 # Do capture 108 cap = its_session_utils.do_capture_with_latency(cam, req, sync_latency) 109 metadata = cap['metadata'] 110 111 ctrl_mode = metadata['android.control.mode'] 112 logging.debug('Control mode: %d', ctrl_mode) 113 assert ctrl_mode == 1, 'ctrl_mode: %d' % ctrl_mode 114 115 # Color correction gain and transform must be valid. 116 metadata_checks(metadata, props) 117 awb_gains = metadata['android.colorCorrection.gains'] 118 awb_xform = metadata['android.colorCorrection.transform'] 119 assert all([g > 0 for g in awb_gains]) 120 assert all([t['denominator'] != 0 for t in awb_xform]) 121 122 # Color correction should not match the manual settings. 123 assert not np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL) 124 assert not all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i]) 125 for i in range(AWB_XFORM_NUM)]) 126 127 # Exposure time must be valid. 128 exp_time = metadata['android.sensor.exposureTime'] 129 assert exp_time > 0 130 131 # Draw lens shading correction map 132 lsc_obj = metadata['android.statistics.lensShadingCorrectionMap'] 133 lsc_map = lsc_obj['map'] 134 lsc_map_w = lsc_obj['width'] 135 lsc_map_h = lsc_obj['height'] 136 logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8])) 137 draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'auto', log_path) 138 139 140def test_manual(cam, props, log_path): 141 """Do manual capture and test results. 142 143 Args: 144 cam: camera object 145 props: camera properties 146 log_path: path for plot directory 147 """ 148 logging.debug('Testing manual capture results') 149 exp_min = min(props['android.sensor.info.exposureTimeRange']) 150 sens_min = min(props['android.sensor.info.sensitivityRange']) 151 sync_latency = camera_properties_utils.sync_latency(props) 152 req = { 153 'android.control.mode': 0, 154 'android.control.aeMode': 0, 155 'android.control.awbMode': 0, 156 'android.control.afMode': 0, 157 'android.sensor.sensitivity': sens_min, 158 'android.sensor.exposureTime': exp_min, 159 'android.colorCorrection.mode': 0, 160 'android.colorCorrection.transform': MANUAL_AWB_XFORM, 161 'android.colorCorrection.gains': MANUAL_AWB_GAINS, 162 'android.tonemap.mode': 0, 163 'android.tonemap.curve': {'red': MANUAL_TONEMAP, 164 'green': MANUAL_TONEMAP, 165 'blue': MANUAL_TONEMAP}, 166 'android.control.aeRegions': MANUAL_REGION, 167 'android.control.afRegions': MANUAL_REGION, 168 'android.control.awbRegions': MANUAL_REGION, 169 'android.statistics.lensShadingMapMode': 1 170 } 171 cap = its_session_utils.do_capture_with_latency(cam, req, sync_latency) 172 metadata = cap['metadata'] 173 174 ctrl_mode = metadata['android.control.mode'] 175 logging.debug('Control mode: %d', ctrl_mode) 176 assert ctrl_mode == 0, 'ctrl_mode: %d' % ctrl_mode 177 178 # Color correction gains and transform should be the same size and 179 # values as the manually set values. 180 metadata_checks(metadata, props) 181 awb_gains = metadata['android.colorCorrection.gains'] 182 awb_xform = metadata['android.colorCorrection.transform'] 183 assert (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i], 184 atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or 185 all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[1][i], 186 atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or 187 all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[2][i], 188 atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)])) 189 assert (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i]) 190 for i in range(AWB_XFORM_NUM)])) 191 192 # The returned tonemap must be linear. 193 curves = [metadata['android.tonemap.curve']['red'], 194 metadata['android.tonemap.curve']['green'], 195 metadata['android.tonemap.curve']['blue']] 196 logging.debug('Tonemap: %s', str(curves[0][1::16])) 197 for c in curves: 198 assert c, 'c in curves is empty.' 199 assert all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL) 200 for i in range(0, len(c), 2)]) 201 202 # Exposure time must be close to the requested exposure time. 203 exp_time = metadata['android.sensor.exposureTime'] 204 assert np.isclose(exp_time*1.0E-6, exp_min*1.0E-6, atol=ISCLOSE_ATOL) 205 206 # Lens shading map must be valid 207 lsc_obj = metadata['android.statistics.lensShadingCorrectionMap'] 208 lsc_map = lsc_obj['map'] 209 lsc_map_w = lsc_obj['width'] 210 lsc_map_h = lsc_obj['height'] 211 logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8])) 212 assert (lsc_map_w > 0 and lsc_map_h > 0 and 213 lsc_map_w*lsc_map_h*4 == len(lsc_map)) 214 assert all([m >= 1 for m in lsc_map]) 215 216 # Draw lens shading correction map 217 draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'manual', log_path) 218 219 220class CaptureResult(its_base_test.ItsBaseTest): 221 """Test that valid data comes back in CaptureResult objects.""" 222 223 def test_capture_result(self): 224 logging.debug('Starting %s', NAME) 225 with its_session_utils.ItsSession( 226 device_id=self.dut.serial, 227 camera_id=self.camera_id, 228 hidden_physical_id=self.hidden_physical_id) as cam: 229 props = cam.get_camera_properties() 230 props = cam.override_with_hidden_physical_camera_props(props) 231 232 # Check SKIP conditions 233 camera_properties_utils.skip_unless( 234 camera_properties_utils.manual_sensor(props) and 235 camera_properties_utils.manual_post_proc(props) and 236 camera_properties_utils.per_frame_control(props)) 237 238 # Load chart for scene 239 its_session_utils.load_scene( 240 cam, props, self.scene, self.tablet, self.chart_distance) 241 242 # Run tests. Run auto, then manual, then auto. Check correct metadata 243 # values and ensure manual settings do not leak into auto captures. 244 test_auto(cam, props, self.log_path) 245 test_manual(cam, props, self.log_path) 246 test_auto(cam, props, self.log_path) 247 248 249if __name__ == '__main__': 250 test_runner.main() 251 252