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 android.tonemap.mode parameter applies.""" 15 16 17import logging 18import os.path 19from mobly import test_runner 20 21import its_base_test 22import camera_properties_utils 23import capture_request_utils 24import image_processing_utils 25import its_session_utils 26import target_exposure_utils 27 28_COLORS = ('R', 'G', 'B') 29_L_TMAP = 32 30_MAX_RGB_MEANS_DIFF = 0.05 # max RBG means diff for same tonemaps 31_MIN_RGB_RATIO_DIFF = 0.1 # min RGB ratio diff for different tonemaps 32_NAME = os.path.splitext(os.path.basename(__file__))[0] 33_NUM_COLORS = len(_COLORS) 34_PATCH_H = 0.1 # center 10% 35_PATCH_W = 0.1 36_PATCH_X = 0.5 - _PATCH_W/2 37_PATCH_Y = 0.5 - _PATCH_H/2 38 39 40def compute_means_and_save(cap, img_name): 41 """Compute the RGB means of a capture and save image. 42 43 Args: 44 cap: 'YUV' or 'JPEG' capture. 45 img_name: text for saved image name. 46 47 Returns: 48 RGB means. 49 """ 50 img = image_processing_utils.convert_capture_to_rgb_image(cap) 51 image_processing_utils.write_image(img, img_name) 52 patch = image_processing_utils.get_image_patch( 53 img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 54 rgb_means = image_processing_utils.compute_image_means(patch) 55 logging.debug('RGB means: %s', str(rgb_means)) 56 return rgb_means 57 58 59class ParamTonemapModeTest(its_base_test.ItsBaseTest): 60 """Test that android.tonemap.mode param is applied. 61 62 Applies different tonemap curves to each R,G,B channel, and checks that the 63 output images are modified as expected. 64 65 The HAL3.2 spec requires curves up to l=64 control pts must be supported. 66 67 Test #1: test tonemap curves have expected effect. 68 Take two shots where each has a linear tonemap, with the 2nd shot having a 69 steeper gradient. The gradient for each R,G,B channel increases. 70 i.e. R[n=1] should be brighter than R[n=0], and G[n=1] should be brighter 71 than G[n=0] by a larger margin, etc. 72 73 Test #2: length of tonemap curve (i.e. num of pts) has no effect 74 Take two shots with tonemap curve of length _L_TMAP and _L_TMAP*2 75 The two shots should be the same. 76 """ 77 78 def test_param_tonemap_mode(self): 79 logging.debug('Starting %s', _NAME) 80 with its_session_utils.ItsSession( 81 device_id=self.dut.serial, 82 camera_id=self.camera_id, 83 hidden_physical_id=self.hidden_physical_id) as cam: 84 props = cam.get_camera_properties() 85 props = cam.override_with_hidden_physical_camera_props(props) 86 camera_properties_utils.skip_unless( 87 camera_properties_utils.compute_target_exposure(props) and 88 camera_properties_utils.per_frame_control(props)) 89 log_path = self.log_path 90 91 # Load chart for scene 92 its_session_utils.load_scene( 93 cam, props, self.scene, self.tablet, self.chart_distance) 94 95 # Determine format, exposure and gain for requests 96 largest_yuv = capture_request_utils.get_largest_yuv_format(props) 97 match_ar = (largest_yuv['width'], largest_yuv['height']) 98 fmt = capture_request_utils.get_smallest_yuv_format( 99 props, match_ar=match_ar) 100 exp, sens = target_exposure_utils.get_target_exposure_combos( 101 log_path, cam)['midExposureTime'] 102 exp //= 2 103 104 # Test 1 105 means_1 = [] 106 for n in [0, 1]: 107 req = capture_request_utils.manual_capture_request(sens, exp) 108 req['android.tonemap.mode'] = 0 109 req['android.tonemap.curve'] = { 110 'red': sum([[i/(_L_TMAP-1), min(1.0, (1+0.5*n)*i/(_L_TMAP-1))] 111 for i in range(_L_TMAP)], []), 112 'green': sum([[i/(_L_TMAP-1), min(1.0, (1+1.0*n)*i/(_L_TMAP-1))] 113 for i in range(_L_TMAP)], []), 114 'blue': sum([[i/(_L_TMAP-1), min(1.0, (1+1.5*n)*i/(_L_TMAP-1))] 115 for i in range(_L_TMAP)], [])} 116 cap = cam.do_capture(req, fmt) 117 img_name = '%s_n=%d.jpg' % (os.path.join(log_path, _NAME), n) 118 means_1.append(compute_means_and_save(cap, img_name)) 119 if 0.0 in means_1[1]: 120 raise AssertionError(f'0.0 value in test 1 means: {means_1[0]}') 121 rgb_ratios = [means_1[1][i]/means_1[0][i] for i in range(_NUM_COLORS)] 122 logging.debug('Test 1: RGB ratios: %s', str(rgb_ratios)) 123 124 # Assert proper behavior 125 for i in range(_NUM_COLORS-1): 126 if rgb_ratios[i+1]-rgb_ratios[i] < _MIN_RGB_RATIO_DIFF: 127 raise AssertionError( 128 f'RGB ratios {i+1}: {rgb_ratios[i+1]:.4f}, {i}: ' 129 f'{rgb_ratios[i]:.4f}, ATOL: {_MIN_RGB_RATIO_DIFF}') 130 131 # Test 2 132 means_2 = [] 133 for size in [_L_TMAP, 2*_L_TMAP]: 134 tonemap_curve = sum([[i/(size-1)]*2 for i in range(size)], []) 135 req = capture_request_utils.manual_capture_request(sens, exp) 136 req['android.tonemap.mode'] = 0 137 req['android.tonemap.curve'] = {'red': tonemap_curve, 138 'green': tonemap_curve, 139 'blue': tonemap_curve} 140 cap = cam.do_capture(req) 141 img_name = '%s_size=%02d.jpg' % (os.path.join(log_path, _NAME), size) 142 means_2.append(compute_means_and_save(cap, img_name)) 143 144 rgb_diffs = [means_2[1][i] - means_2[0][i] for i in range(_NUM_COLORS)] 145 logging.debug('Test 2: RGB diffs: %s', str(rgb_diffs)) 146 147 # assert proper behavior 148 for i, ch in enumerate(_COLORS): 149 if abs(rgb_diffs[i]) > _MAX_RGB_MEANS_DIFF: 150 raise AssertionError(f'{ch} rgb_diffs: {rgb_diffs[i]:.4f}, ' 151 f'THRESH: {_MAX_RGB_MEANS_DIFF}') 152 153if __name__ == '__main__': 154 test_runner.main() 155