1# Copyright 2015 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 android.shading.mode parameter is applied.""" 15 16 17import logging 18import os.path 19import matplotlib 20from matplotlib import pylab 21from mobly import test_runner 22import numpy as np 23 24import its_base_test 25import camera_properties_utils 26import capture_request_utils 27import its_session_utils 28 29_NAME = os.path.splitext(os.path.basename(__file__))[0] 30_NUM_FRAMES = 4 # number of frames for temporal info to settle 31_NUM_SWITCH_LOOPS = 3 32_SHADING_MODES = {0: 'LSC_OFF', 1: 'LSC_FAST', 2: 'LSC_HQ'} 33_NUM_SHADING_MODES = len(_SHADING_MODES) 34_THRESHOLD_DIFF_RATIO = 0.15 35 36 37def create_plots(shading_maps, reference_maps, num_map_gains, log_path): 38 """Create 2 panel plot from data.""" 39 for mode in range(_NUM_SHADING_MODES): 40 for i in range(_NUM_SWITCH_LOOPS): 41 pylab.clf() 42 pylab.figure(figsize=(5, 5)) 43 pylab.subplot(2, 1, 1) 44 pylab.plot(range(num_map_gains), shading_maps[mode][i], '-r.', 45 label='shading', alpha=0.7) 46 pylab.plot(range(num_map_gains), reference_maps[mode], '-g.', 47 label='ref', alpha=0.7) 48 pylab.xlim([0, num_map_gains]) 49 pylab.ylim([0.9, 4.0]) 50 name_suffix = 'ls_maps_mode_%d_loop_%d' % (mode, i) 51 pylab.title('%s_%s' % (_NAME, name_suffix)) 52 pylab.xlabel('Map gains') 53 pylab.ylabel('Lens shading maps') 54 pylab.legend(loc='upper center', numpoints=1, fancybox=True) 55 56 pylab.subplot(2, 1, 2) 57 shading_ref_ratio = np.divide( 58 shading_maps[mode][i], reference_maps[mode]) 59 pylab.plot(range(num_map_gains), shading_ref_ratio, '-b.', clip_on=False) 60 pylab.xlim([0, num_map_gains]) 61 pylab.ylim([1.0-_THRESHOLD_DIFF_RATIO, 1.0+_THRESHOLD_DIFF_RATIO]) 62 pylab.title('Shading/reference Maps Ratio vs Gain') 63 pylab.xlabel('Map gains') 64 pylab.ylabel('Shading/reference maps ratio') 65 66 pylab.tight_layout() 67 matplotlib.pyplot.savefig( 68 f'{os.path.join(log_path, _NAME)}_{name_suffix}.png') 69 70 71class ParamShadingModeTest(its_base_test.ItsBaseTest): 72 """Test that the android.shading.mode param is applied. 73 74 Switches shading modes and checks that the lens shading maps are 75 modified as expected. 76 77 Lens shading correction modes are OFF=0, FAST=1, and HQ=2. 78 79 Uses smallest yuv size matching the aspect ratio of largest yuv size to 80 reduce some USB bandwidth overhead since we are only looking at output 81 metadata in this test. 82 83 First asserts all modes are supported. Then runs 2 captures. 84 85 cap1: switches shading modes several times and gets reference maps 86 cap2: gets the lens shading maps while switching modes in 1 session 87 88 Creates plots of reference maps and shading maps. 89 90 Asserts proper behavior: 91 1. Lens shading maps with OFF are all 1.0 92 2. Lens shading maps with FAST are similar after switching shading modes 93 3. Lens shading maps with HQ are similar after switching shading modes. 94 """ 95 96 def test_param_shading_mode(self): 97 logging.debug('Starting %s', _NAME) 98 with its_session_utils.ItsSession( 99 device_id=self.dut.serial, 100 camera_id=self.camera_id, 101 hidden_physical_id=self.hidden_physical_id) as cam: 102 props = cam.get_camera_properties() 103 props = cam.override_with_hidden_physical_camera_props(props) 104 camera_properties_utils.skip_unless( 105 camera_properties_utils.per_frame_control(props) and 106 camera_properties_utils.lsc_map(props) and 107 camera_properties_utils.lsc_off(props)) 108 log_path = self.log_path 109 110 # Load chart for scene 111 its_session_utils.load_scene( 112 cam, props, self.scene, self.tablet, self.chart_distance) 113 114 # lsc devices support all modes 115 if set(props.get('android.shading.availableModes')) != set( 116 _SHADING_MODES.keys()): 117 raise KeyError('Available modes: %s, SHADING_MODEs: %s.' 118 % str(props.get('android.shading.availableModes')), 119 [*_SHADING_MODES]) 120 121 # get smallest matching fmt 122 mono_camera = camera_properties_utils.mono_camera(props) 123 cam.do_3a(mono_camera=mono_camera) 124 largest_yuv_fmt = capture_request_utils.get_largest_yuv_format(props) 125 largest_yuv_size = (largest_yuv_fmt['width'], largest_yuv_fmt['height']) 126 cap_fmt = capture_request_utils.get_smallest_yuv_format( 127 props, match_ar=largest_yuv_size) 128 129 # cap1 130 reference_maps = [[] for mode in range(_NUM_SHADING_MODES)] 131 num_map_gains = 0 132 for mode in range(1, _NUM_SHADING_MODES): 133 req = capture_request_utils.auto_capture_request() 134 req['android.statistics.lensShadingMapMode'] = 1 135 req['android.shading.mode'] = mode 136 cap_res = cam.do_capture( 137 [req]*_NUM_FRAMES, cap_fmt)[_NUM_FRAMES-1]['metadata'] 138 lsc_map = cap_res['android.statistics.lensShadingCorrectionMap'] 139 if not lsc_map.get('width') or not lsc_map.get('height'): 140 raise KeyError('width or height not in LSC map.') 141 if mode == 1: 142 num_map_gains = lsc_map['width'] * lsc_map['height'] * 4 143 reference_maps[0] = [1.0] * num_map_gains 144 reference_maps[mode] = lsc_map['map'] 145 146 # cap2 147 reqs = [] 148 for i in range(_NUM_SWITCH_LOOPS): 149 for mode in range(_NUM_SHADING_MODES): 150 for _ in range(_NUM_FRAMES): 151 req = capture_request_utils.auto_capture_request() 152 req['android.statistics.lensShadingMapMode'] = 1 153 req['android.shading.mode'] = mode 154 reqs.append(req) 155 caps = cam.do_capture(reqs, cap_fmt) 156 157 # Populate shading maps from cap2 results 158 shading_maps = [[[] for loop in range(_NUM_SWITCH_LOOPS)] 159 for mode in range(_NUM_SHADING_MODES)] 160 for i in range(len(caps)//_NUM_FRAMES): 161 shading_maps[i%_NUM_SHADING_MODES][i//_NUM_SWITCH_LOOPS] = caps[ 162 (i+1)*_NUM_FRAMES-1]['metadata'][ 163 'android.statistics.lensShadingCorrectionMap']['map'] 164 165 # Plot the shading and reference maps 166 create_plots(shading_maps, reference_maps, num_map_gains, log_path) 167 168 # Assert proper behavior 169 for mode in range(_NUM_SHADING_MODES): 170 if mode == 0: 171 logging.debug('Verifying lens shading maps with mode %s are all 1.0', 172 _SHADING_MODES[mode]) 173 else: 174 logging.debug('Verifying lens shading maps with mode %s are similar', 175 _SHADING_MODES[mode]) 176 for i in range(_NUM_SWITCH_LOOPS): 177 if not (np.allclose(shading_maps[mode][i], reference_maps[mode], 178 rtol=_THRESHOLD_DIFF_RATIO)): 179 raise AssertionError(f'FAIL mode: {_SHADING_MODES[mode]}, ' 180 f'loop: {i}, THRESH: {_THRESHOLD_DIFF_RATIO}') 181 182if __name__ == '__main__': 183 test_runner.main() 184