1# Copyright 2018 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"""CameraITS test for tonemap curve with sensor test pattern.""" 15 16import logging 17import os 18 19from mobly import test_runner 20import numpy as np 21 22import its_base_test 23import camera_properties_utils 24import capture_request_utils 25import image_processing_utils 26import its_session_utils 27 28 29NAME = os.path.basename(__file__).split('.')[0] 30COLOR_BAR_PATTERN = 2 # Note scene0/test_test_patterns must PASS 31COLOR_BARS = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED', 32 'BLUE', 'BLACK'] 33N_BARS = len(COLOR_BARS) 34COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0], 35 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1], 36 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]} 37DELTA = 0.0005 # crop on edge of color bars 38RAW_TOL = 0.001 # 1 DN in [0:1] (1/(1023-64) 39RGB_VAR_TOL = 0.0039 # 1/255 40RGB_MEAN_TOL = 0.1 41TONEMAP_MAX = 0.5 42YUV_H = 480 43YUV_W = 640 44# Normalized co-ordinates for the color bar patch. 45Y_NORM = 0.0 46W_NORM = 1.0 / N_BARS - 2 * DELTA 47H_NORM = 1.0 48 49# Linear tonemap with maximum of 0.5 50LINEAR_TONEMAP = sum([[i/63.0, i/126.0] for i in range(64)], []) 51 52 53def get_x_norm(num): 54 """Returns the normalized x co-ordinate for the title. 55 56 Args: 57 num: int; position on color in the color bar. 58 59 Returns: 60 normalized x co-ordinate. 61 """ 62 return float(num) / N_BARS + DELTA 63 64 65def check_raw_pattern(img_raw): 66 """Checks for RAW capture matches color bar pattern. 67 68 Args: 69 img_raw: RAW image 70 """ 71 logging.debug('Checking RAW/PATTERN match') 72 color_match = [] 73 for n in range(N_BARS): 74 x_norm = get_x_norm(n) 75 raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM, 76 W_NORM, H_NORM) 77 raw_means = image_processing_utils.compute_image_means(raw_patch) 78 logging.debug('patch: %d, x_norm: %.3f, RAW means: %s', 79 n, x_norm, str(raw_means)) 80 for color in COLOR_BARS: 81 if np.allclose(COLOR_CHECKER[color], raw_means, atol=RAW_TOL): 82 color_match.append(color) 83 logging.debug('%s match', color) 84 break 85 else: 86 logging.debug('No match w/ %s: %s, ATOL: %.3f', 87 color, str(COLOR_CHECKER[color]), RAW_TOL) 88 if set(color_match) != set(COLOR_BARS): 89 raise AssertionError('RAW COLOR_BARS test pattern does not have all colors') 90 91 92def check_yuv_vs_raw(img_raw, img_yuv): 93 """Checks for YUV vs RAW match in 8 patches. 94 95 Check for correct values and color consistency 96 97 Args: 98 img_raw: RAW image 99 img_yuv: YUV image 100 """ 101 logging.debug('Checking YUV/RAW match') 102 color_match_errs = [] 103 color_variance_errs = [] 104 for n in range(N_BARS): 105 x_norm = get_x_norm(n) 106 logging.debug('x_norm: %.3f', x_norm) 107 raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM, 108 W_NORM, H_NORM) 109 yuv_patch = image_processing_utils.get_image_patch(img_yuv, x_norm, Y_NORM, 110 W_NORM, H_NORM) 111 raw_means = np.array(image_processing_utils.compute_image_means(raw_patch)) 112 raw_vars = np.array( 113 image_processing_utils.compute_image_variances(raw_patch)) 114 yuv_means = np.array(image_processing_utils.compute_image_means(yuv_patch)) 115 yuv_means /= TONEMAP_MAX # Normalize to tonemap max 116 yuv_vars = np.array( 117 image_processing_utils.compute_image_variances(yuv_patch)) 118 if not np.allclose(raw_means, yuv_means, atol=RGB_MEAN_TOL): 119 color_match_errs.append( 120 'RAW: %s, RGB(norm): %s, ATOL: %.2f' % 121 (str(raw_means), str(np.round(yuv_means, 3)), RGB_MEAN_TOL)) 122 if not np.allclose(raw_vars, yuv_vars, atol=RGB_VAR_TOL): 123 color_variance_errs.append('RAW: %s, RGB: %s, ATOL: %.4f' % 124 (str(raw_vars), str(yuv_vars), RGB_VAR_TOL)) 125 126 # Print all errors before assertion 127 if color_match_errs: 128 for err in color_match_errs: 129 logging.debug(err) 130 for err in color_variance_errs: 131 logging.error(err) 132 raise AssertionError('Color match errors. See test_log.DEBUG') 133 if color_variance_errs: 134 for err in color_variance_errs: 135 logging.error(err) 136 raise AssertionError('Color variance errors. See test_log.DEBUG') 137 138 139def test_tonemap_curve_impl(name, cam, props): 140 """Test tonemap curve with sensor test pattern. 141 142 Args: 143 name: Path to save the captured image. 144 cam: An open device session. 145 props: Properties of cam. 146 """ 147 148 avail_patterns = props['android.sensor.availableTestPatternModes'] 149 logging.debug('Available Patterns: %s', avail_patterns) 150 sens_min, _ = props['android.sensor.info.sensitivityRange'] 151 min_exposure = min(props['android.sensor.info.exposureTimeRange']) 152 153 # RAW image 154 req_raw = capture_request_utils.manual_capture_request( 155 int(sens_min), min_exposure) 156 req_raw['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN 157 fmt_raw = {'format': 'raw'} 158 cap_raw = cam.do_capture(req_raw, fmt_raw) 159 img_raw = image_processing_utils.convert_capture_to_rgb_image( 160 cap_raw, props=props) 161 162 # Save RAW pattern 163 image_processing_utils.write_image( 164 img_raw, '%s_raw_%d.jpg' % (name, COLOR_BAR_PATTERN), True) 165 check_raw_pattern(img_raw) 166 167 # YUV image 168 req_yuv = capture_request_utils.manual_capture_request( 169 int(sens_min), min_exposure) 170 req_yuv['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN 171 req_yuv['android.distortionCorrection.mode'] = 0 172 req_yuv['android.tonemap.mode'] = 0 173 req_yuv['android.tonemap.curve'] = { 174 'red': LINEAR_TONEMAP, 175 'green': LINEAR_TONEMAP, 176 'blue': LINEAR_TONEMAP 177 } 178 fmt_yuv = {'format': 'yuv', 'width': YUV_W, 'height': YUV_H} 179 cap_yuv = cam.do_capture(req_yuv, fmt_yuv) 180 img_yuv = image_processing_utils.convert_capture_to_rgb_image(cap_yuv, True) 181 182 # Save YUV pattern 183 image_processing_utils.write_image( 184 img_yuv, '%s_yuv_%d.jpg' % (name, COLOR_BAR_PATTERN), True) 185 186 # Check pattern for correctness 187 check_yuv_vs_raw(img_raw, img_yuv) 188 189 190class TonemapCurveTest(its_base_test.ItsBaseTest): 191 """Test conversion of test pattern from RAW to YUV with linear tonemap. 192 193 Test makes use of android.sensor.testPatternMode 2 (COLOR_BARS). 194 """ 195 196 def test_tonemap_curve(self): 197 logging.debug('Starting %s', NAME) 198 name = os.path.join(self.log_path, NAME) 199 with its_session_utils.ItsSession( 200 device_id=self.dut.serial, 201 camera_id=self.camera_id, 202 hidden_physical_id=self.hidden_physical_id) as cam: 203 props = cam.get_camera_properties() 204 camera_properties_utils.skip_unless( 205 camera_properties_utils.raw16(props) and 206 camera_properties_utils.manual_sensor(props) and 207 camera_properties_utils.per_frame_control(props) and 208 camera_properties_utils.manual_post_proc(props) and 209 camera_properties_utils.color_bars_test_pattern(props)) 210 211 test_tonemap_curve_impl(name, cam, props) 212 213 214if __name__ == '__main__': 215 test_runner.main() 216