# Copyright 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Verifies android.shading.mode parameter is applied.""" import logging import os.path import matplotlib from matplotlib import pylab from mobly import test_runner import numpy as np import its_base_test import camera_properties_utils import capture_request_utils import its_session_utils _NAME = os.path.splitext(os.path.basename(__file__))[0] _NUM_FRAMES = 4 # number of frames for temporal info to settle _NUM_SWITCH_LOOPS = 3 _SHADING_MODES = {0: 'LSC_OFF', 1: 'LSC_FAST', 2: 'LSC_HQ'} _NUM_SHADING_MODES = len(_SHADING_MODES) _THRESHOLD_DIFF_RATIO = 0.15 def create_plots(shading_maps, reference_maps, num_map_gains, log_path): """Create 2 panel plot from data.""" for mode in range(_NUM_SHADING_MODES): for i in range(_NUM_SWITCH_LOOPS): pylab.clf() pylab.figure(figsize=(5, 5)) pylab.subplot(2, 1, 1) pylab.plot(range(num_map_gains), shading_maps[mode][i], '-r.', label='shading', alpha=0.7) pylab.plot(range(num_map_gains), reference_maps[mode], '-g.', label='ref', alpha=0.7) pylab.xlim([0, num_map_gains]) pylab.ylim([0.9, 4.0]) name_suffix = 'ls_maps_mode_%d_loop_%d' % (mode, i) pylab.title('%s_%s' % (_NAME, name_suffix)) pylab.xlabel('Map gains') pylab.ylabel('Lens shading maps') pylab.legend(loc='upper center', numpoints=1, fancybox=True) pylab.subplot(2, 1, 2) shading_ref_ratio = np.divide( shading_maps[mode][i], reference_maps[mode]) pylab.plot(range(num_map_gains), shading_ref_ratio, '-b.', clip_on=False) pylab.xlim([0, num_map_gains]) pylab.ylim([1.0-_THRESHOLD_DIFF_RATIO, 1.0+_THRESHOLD_DIFF_RATIO]) pylab.title('Shading/reference Maps Ratio vs Gain') pylab.xlabel('Map gains') pylab.ylabel('Shading/reference maps ratio') pylab.tight_layout() matplotlib.pyplot.savefig( f'{os.path.join(log_path, _NAME)}_{name_suffix}.png') class ParamShadingModeTest(its_base_test.ItsBaseTest): """Test that the android.shading.mode param is applied. Switches shading modes and checks that the lens shading maps are modified as expected. Lens shading correction modes are OFF=0, FAST=1, and HQ=2. Uses smallest yuv size matching the aspect ratio of largest yuv size to reduce some USB bandwidth overhead since we are only looking at output metadata in this test. First asserts all modes are supported. Then runs 2 captures. cap1: switches shading modes several times and gets reference maps cap2: gets the lens shading maps while switching modes in 1 session Creates plots of reference maps and shading maps. Asserts proper behavior: 1. Lens shading maps with OFF are all 1.0 2. Lens shading maps with FAST are similar after switching shading modes 3. Lens shading maps with HQ are similar after switching shading modes. """ def test_param_shading_mode(self): logging.debug('Starting %s', _NAME) with its_session_utils.ItsSession( device_id=self.dut.serial, camera_id=self.camera_id, hidden_physical_id=self.hidden_physical_id) as cam: props = cam.get_camera_properties() props = cam.override_with_hidden_physical_camera_props(props) camera_properties_utils.skip_unless( camera_properties_utils.per_frame_control(props) and camera_properties_utils.lsc_map(props) and camera_properties_utils.lsc_off(props)) log_path = self.log_path # Load chart for scene its_session_utils.load_scene( cam, props, self.scene, self.tablet, self.chart_distance) # lsc devices support all modes if set(props.get('android.shading.availableModes')) != set( _SHADING_MODES.keys()): raise KeyError('Available modes: %s, SHADING_MODEs: %s.' % str(props.get('android.shading.availableModes')), [*_SHADING_MODES]) # get smallest matching fmt mono_camera = camera_properties_utils.mono_camera(props) cam.do_3a(mono_camera=mono_camera) largest_yuv_fmt = capture_request_utils.get_largest_yuv_format(props) largest_yuv_size = (largest_yuv_fmt['width'], largest_yuv_fmt['height']) cap_fmt = capture_request_utils.get_smallest_yuv_format( props, match_ar=largest_yuv_size) # cap1 reference_maps = [[] for mode in range(_NUM_SHADING_MODES)] num_map_gains = 0 for mode in range(1, _NUM_SHADING_MODES): req = capture_request_utils.auto_capture_request() req['android.statistics.lensShadingMapMode'] = 1 req['android.shading.mode'] = mode cap_res = cam.do_capture( [req]*_NUM_FRAMES, cap_fmt)[_NUM_FRAMES-1]['metadata'] lsc_map = cap_res['android.statistics.lensShadingCorrectionMap'] if not lsc_map.get('width') or not lsc_map.get('height'): raise KeyError('width or height not in LSC map.') if mode == 1: num_map_gains = lsc_map['width'] * lsc_map['height'] * 4 reference_maps[0] = [1.0] * num_map_gains reference_maps[mode] = lsc_map['map'] # cap2 reqs = [] for i in range(_NUM_SWITCH_LOOPS): for mode in range(_NUM_SHADING_MODES): for _ in range(_NUM_FRAMES): req = capture_request_utils.auto_capture_request() req['android.statistics.lensShadingMapMode'] = 1 req['android.shading.mode'] = mode reqs.append(req) caps = cam.do_capture(reqs, cap_fmt) # Populate shading maps from cap2 results shading_maps = [[[] for loop in range(_NUM_SWITCH_LOOPS)] for mode in range(_NUM_SHADING_MODES)] for i in range(len(caps)//_NUM_FRAMES): shading_maps[i%_NUM_SHADING_MODES][i//_NUM_SWITCH_LOOPS] = caps[ (i+1)*_NUM_FRAMES-1]['metadata'][ 'android.statistics.lensShadingCorrectionMap']['map'] # Plot the shading and reference maps create_plots(shading_maps, reference_maps, num_map_gains, log_path) # Assert proper behavior for mode in range(_NUM_SHADING_MODES): if mode == 0: logging.debug('Verifying lens shading maps with mode %s are all 1.0', _SHADING_MODES[mode]) else: logging.debug('Verifying lens shading maps with mode %s are similar', _SHADING_MODES[mode]) for i in range(_NUM_SWITCH_LOOPS): if not (np.allclose(shading_maps[mode][i], reference_maps[mode], rtol=_THRESHOLD_DIFF_RATIO)): raise AssertionError(f'FAIL mode: {_SHADING_MODES[mode]}, ' f'loop: {i}, THRESH: {_THRESHOLD_DIFF_RATIO}') if __name__ == '__main__': test_runner.main()