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) and 89 camera_properties_utils.tonemap_mode(props, 0)) 90 log_path = self.log_path 91 name_with_log_path = os.path.join(log_path, _NAME) 92 93 # Load chart for scene 94 its_session_utils.load_scene( 95 cam, props, self.scene, self.tablet, 96 its_session_utils.CHART_DISTANCE_NO_SCALING) 97 98 # Determine format, exposure and gain for requests 99 largest_yuv = capture_request_utils.get_largest_yuv_format(props) 100 match_ar = (largest_yuv['width'], largest_yuv['height']) 101 fmt = capture_request_utils.get_near_vga_yuv_format( 102 props, match_ar=match_ar) 103 exp, sens = target_exposure_utils.get_target_exposure_combos( 104 log_path, cam)['midExposureTime'] 105 exp //= 2 106 107 # Test 1 108 means_1 = [] 109 for n in [0, 1]: 110 req = capture_request_utils.manual_capture_request(sens, exp) 111 req['android.tonemap.mode'] = 0 112 req['android.tonemap.curve'] = { 113 'red': sum([[i/(_L_TMAP-1), min(1.0, (1+0.5*n)*i/(_L_TMAP-1))] 114 for i in range(_L_TMAP)], []), 115 'green': sum([[i/(_L_TMAP-1), min(1.0, (1+1.0*n)*i/(_L_TMAP-1))] 116 for i in range(_L_TMAP)], []), 117 'blue': sum([[i/(_L_TMAP-1), min(1.0, (1+1.5*n)*i/(_L_TMAP-1))] 118 for i in range(_L_TMAP)], [])} 119 cap = cam.do_capture(req, fmt) 120 img_name = f'{name_with_log_path}_n={n}.jpg' 121 means_1.append(compute_means_and_save(cap, img_name)) 122 if 0.0 in means_1[1]: 123 raise AssertionError(f'0.0 value in test 1 means: {means_1[0]}') 124 rgb_ratios = [means_1[1][i]/means_1[0][i] for i in range(_NUM_COLORS)] 125 logging.debug('Test 1: RGB ratios: %s', str(rgb_ratios)) 126 127 # Assert proper behavior 128 for i in range(_NUM_COLORS-1): 129 if rgb_ratios[i+1]-rgb_ratios[i] < _MIN_RGB_RATIO_DIFF: 130 raise AssertionError( 131 f'RGB ratios {i+1}: {rgb_ratios[i+1]:.4f}, {i}: ' 132 f'{rgb_ratios[i]:.4f}, ATOL: {_MIN_RGB_RATIO_DIFF}') 133 134 # Test 2 135 means_2 = [] 136 for size in [_L_TMAP, 2*_L_TMAP]: 137 tonemap_curve = sum([[i/(size-1)]*2 for i in range(size)], []) 138 req = capture_request_utils.manual_capture_request(sens, exp) 139 req['android.tonemap.mode'] = 0 140 req['android.tonemap.curve'] = {'red': tonemap_curve, 141 'green': tonemap_curve, 142 'blue': tonemap_curve} 143 cap = cam.do_capture(req) 144 img_name = f'{name_with_log_path}_size={size:02d}.jpg' 145 means_2.append(compute_means_and_save(cap, img_name)) 146 147 rgb_diffs = [means_2[1][i] - means_2[0][i] for i in range(_NUM_COLORS)] 148 logging.debug('Test 2: RGB diffs: %s', str(rgb_diffs)) 149 150 # assert proper behavior 151 for i, ch in enumerate(_COLORS): 152 if abs(rgb_diffs[i]) > _MAX_RGB_MEANS_DIFF: 153 raise AssertionError(f'{ch} rgb_diffs: {rgb_diffs[i]:.4f}, ' 154 f'THRESH: {_MAX_RGB_MEANS_DIFF}') 155 156if __name__ == '__main__': 157 test_runner.main() 158