1# Copyright 2016 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 to check test patterns generation.""" 15 16import logging 17import math 18import os 19 20from mobly import test_runner 21import numpy as np 22 23import its_base_test 24import camera_properties_utils 25import capture_request_utils 26import image_processing_utils 27import its_session_utils 28 29 30_NAME = os.path.basename(__file__).split('.')[0] 31_CHECKED_PATTERNS = [1, 2, 5] # [SOLID_COLOR, COLOR_BARS, BLACK] 32_COLOR_BAR_ORDER = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED', 33 'BLUE', 'BLACK'] 34_COLOR_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]} 37_CH_ATOL = 2E-3 # 1/2 DN in [0:1] 38 39 40def check_solid_color(cap, props): 41 """Checks for solid color test pattern. 42 43 Args: 44 cap: capture element 45 props: capture properties 46 47 Returns: 48 True/False 49 """ 50 logging.debug('Checking solid TestPattern...') 51 r, gr, gb, b = image_processing_utils.convert_capture_to_planes(cap, props) 52 r_tile = image_processing_utils.get_image_patch(r, 0.0, 0.0, 1.0, 1.0) 53 gr_tile = image_processing_utils.get_image_patch(gr, 0.0, 0.0, 1.0, 1.0) 54 gb_tile = image_processing_utils.get_image_patch(gb, 0.0, 0.0, 1.0, 1.0) 55 b_tile = image_processing_utils.get_image_patch(b, 0.0, 0.0, 1.0, 1.0) 56 var_max = max( 57 np.amax(r_tile), np.amax(gr_tile), np.amax(gb_tile), np.amax(b_tile)) 58 var_min = min( 59 np.amin(r_tile), np.amin(gr_tile), np.amin(gb_tile), np.amin(b_tile)) 60 white_level = int(props['android.sensor.info.whiteLevel']) 61 logging.debug('pixel min: %.f, pixel max: %.f', white_level * var_min, 62 white_level * var_max) 63 return math.isclose(var_max, var_min, abs_tol=_CH_ATOL) 64 65 66def check_color_bars(cap, props, mirror=False): 67 """Checks for color bar test pattern.Compute avg of bars and compare to ideal. 68 69 Args: 70 cap: capture element 71 props: capture properties 72 mirror: boolean; whether to mirror image or not 73 74 Returns: 75 True/False 76 77 78 """ 79 logging.debug('Checking color bar TestPattern...') 80 delta = 0.0005 81 num_bars = len(_COLOR_BAR_ORDER) 82 color_match = [] 83 img = image_processing_utils.convert_capture_to_rgb_image(cap, props=props) 84 if mirror: 85 logging.debug('Image mirrored') 86 img = np.fliplr(img) 87 for i, color in enumerate(_COLOR_BAR_ORDER): 88 tile = image_processing_utils.get_image_patch(img, 89 float(i) / num_bars + delta, 90 0.0, 91 1.0 / num_bars - 2 * delta, 92 1.0) 93 color_match.append( 94 np.allclose( 95 image_processing_utils.compute_image_means(tile), 96 _COLOR_CHECKER[color], 97 atol=_CH_ATOL)) 98 logging.debug(_COLOR_BAR_ORDER) 99 logging.debug(color_match) 100 return all(color_match) 101 102 103def check_pattern(cap, props, pattern): 104 """Checks for pattern correctness. 105 106 Args: 107 cap: capture element 108 props: capture properties 109 pattern (int): valid number for pattern 110 111 Returns: 112 True/False 113 """ 114 if pattern == 1 or pattern == 5: # solid color or black 115 return check_solid_color(cap, props) 116 elif pattern == 2: # color bars 117 striped = check_color_bars(cap, props, mirror=False) 118 # check mirrored version in case image rotated from sensor orientation 119 if not striped: 120 striped = check_color_bars(cap, props, mirror=True) 121 return striped 122 else: 123 logging.debug('No specific test for TestPattern: %d', pattern) 124 return True 125 126 127def test_test_patterns_impl(cam, props, af_fd, name_with_log_path): 128 """Image sensor test patterns implementation. 129 130 Args: 131 cam: An open device session. 132 props: Properties of cam 133 af_fd: Focus distance 134 name_with_log_path: Path to save the captured image. 135 """ 136 137 avail_patterns = props['android.sensor.availableTestPatternModes'] 138 logging.debug('avail_patterns: %s', avail_patterns) 139 sens_min, _ = props['android.sensor.info.sensitivityRange'] 140 exposure = min(props['android.sensor.info.exposureTimeRange']) 141 142 for pattern in _CHECKED_PATTERNS: 143 if pattern in avail_patterns: 144 req = capture_request_utils.manual_capture_request( 145 int(sens_min), exposure) 146 req['android.lens.focusDistance'] = af_fd 147 req['android.sensor.testPatternMode'] = pattern 148 fmt = {'format': 'raw'} 149 cap = cam.do_capture(req, fmt) 150 img = image_processing_utils.convert_capture_to_rgb_image( 151 cap, props=props) 152 # Save pattern 153 image_processing_utils.write_image( 154 img, f'{name_with_log_path}_{pattern}.jpg', True) 155 156 # Check pattern for correctness 157 if not check_pattern(cap, props, pattern): 158 raise AssertionError(f'Pattern {pattern} failed') 159 else: 160 logging.debug('%d not in android.sensor.availableTestPatternModes.', 161 pattern) 162 163 164class TestPatterns(its_base_test.ItsBaseTest): 165 """Test pattern generation test. 166 167 Test: Capture frames for each valid test pattern and check if 168 generated correctly. 169 android.sensor.testPatternMode 170 0: OFF 171 1: SOLID_COLOR 172 2: COLOR_BARS 173 3: COLOR_BARS_FADE_TO_GREY 174 4: PN9 175 5: BLACK (test/system only) 176 """ 177 178 def test_test_patterns(self): 179 logging.debug('Starting %s', _NAME) 180 with its_session_utils.ItsSession( 181 device_id=self.dut.serial, 182 camera_id=self.camera_id, 183 hidden_physical_id=self.hidden_physical_id) as cam: 184 props = cam.get_camera_properties() 185 props = cam.override_with_hidden_physical_camera_props(props) 186 camera_properties_utils.skip_unless( 187 camera_properties_utils.raw16(props) and 188 camera_properties_utils.manual_sensor(props) and 189 camera_properties_utils.per_frame_control(props)) 190 191 # For test pattern, use min_fd 192 focus_distance = props['android.lens.info.minimumFocusDistance'] 193 name_with_log_path = os.path.join(self.log_path, _NAME) 194 test_test_patterns_impl(cam, props, focus_distance, name_with_log_path) 195 196if __name__ == '__main__': 197 test_runner.main() 198