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"""Verifies post RAW sensitivity boost.""" 15 16 17import logging 18import math 19import os.path 20import matplotlib 21from matplotlib import pylab 22from mobly import test_runner 23 24import its_base_test 25import camera_properties_utils 26import capture_request_utils 27import error_util 28import image_processing_utils 29import its_session_utils 30import target_exposure_utils 31 32_COLORS = ('R', 'G', 'B') 33_MAX_YUV_SIZE = (1920, 1080) 34_NAME = os.path.splitext(os.path.basename(__file__))[0] 35_PATCH_H = 0.1 # center 10% 36_PATCH_W = 0.1 37_PATCH_X = 0.5 - _PATCH_W/2 38_PATCH_Y = 0.5 - _PATCH_H/2 39_RATIO_RTOL = 0.1 # +/-10% TOL on images vs expected values 40_RAW_PIXEL_THRESH = 0.03 # Waive check if RAW [0, 1] value below this thresh 41 42 43def create_requests(cam, props, log_path): 44 """Create the requests and settings lists.""" 45 w, h = capture_request_utils.get_available_output_sizes( 46 'yuv', props, _MAX_YUV_SIZE)[0] 47 48 if camera_properties_utils.raw16(props): 49 raw_format = 'raw' 50 elif camera_properties_utils.raw10(props): 51 raw_format = 'raw10' 52 elif camera_properties_utils.raw12(props): 53 raw_format = 'raw12' 54 else: # should not reach here 55 raise error_util.Error('Cannot find available RAW output format') 56 57 out_surfaces = [{'format': raw_format}, 58 {'format': 'yuv', 'width': w, 'height': h}] 59 sens_min, sens_max = props['android.sensor.info.sensitivityRange'] 60 sens_boost_min, sens_boost_max = props[ 61 'android.control.postRawSensitivityBoostRange'] 62 exp_target, sens_target = target_exposure_utils.get_target_exposure_combos( 63 log_path, cam)['midSensitivity'] 64 65 reqs = [] 66 settings = [] 67 sens_boost = sens_boost_min 68 while sens_boost <= sens_boost_max: 69 sens_raw = int(round(sens_target * 100.0 / sens_boost)) 70 if sens_raw < sens_min or sens_raw > sens_max: 71 break 72 req = capture_request_utils.manual_capture_request(sens_raw, exp_target) 73 req['android.control.postRawSensitivityBoost'] = sens_boost 74 reqs.append(req) 75 settings.append((sens_raw, sens_boost)) 76 if sens_boost == sens_boost_max: 77 break 78 sens_boost *= 2 79 # Always try to test maximum sensitivity boost value 80 if sens_boost > sens_boost_max: 81 sens_boost = sens_boost_max 82 83 return settings, reqs, out_surfaces 84 85 86def compute_patch_means(cap, props, file_name): 87 """Compute the RGB means for center patch of capture.""" 88 89 rgb_img = image_processing_utils.convert_capture_to_rgb_image( 90 cap, props=props) 91 patch = image_processing_utils.get_image_patch( 92 rgb_img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 93 image_processing_utils.write_image(patch, file_name) 94 return image_processing_utils.compute_image_means(patch) 95 96 97def create_plots(idx, raw_means, yuv_means, log_path): 98 """Create plots from data. 99 100 Args: 101 idx: capture request indices for x-axis. 102 raw_means: array of RAW capture RGB converted means. 103 yuv_means: array of YUV capture RGB converted means. 104 log_path: path to save files. 105 """ 106 107 pylab.clf() 108 for i, _ in enumerate(_COLORS): 109 pylab.plot(idx, [ch[i] for ch in yuv_means], '-'+'rgb'[i]+'s', label='YUV', 110 alpha=0.7) 111 pylab.plot(idx, [ch[i] for ch in raw_means], '-'+'rgb'[i]+'o', label='RAW', 112 alpha=0.7) 113 pylab.ylim([0, 1]) 114 pylab.title('%s' % _NAME) 115 pylab.xlabel('requests') 116 pylab.ylabel('RGB means') 117 pylab.legend(loc='lower right', numpoints=1, fancybox=True) 118 matplotlib.pyplot.savefig('%s_plot_means.png' % os.path.join(log_path, _NAME)) 119 120 121class PostRawSensitivityBoost(its_base_test.ItsBaseTest): 122 """Check post RAW sensitivity boost. 123 124 Captures a set of RAW/YUV images with different sensitivity/post RAW 125 sensitivity boost combination and checks if output means match req settings 126 127 RAW images should get brighter. YUV images should stay about the same. 128 asserts RAW is ~2x brighter per step 129 asserts YUV is about the same per step 130 """ 131 132 def test_post_raw_sensitivity_boost(self): 133 logging.debug('Starting %s', _NAME) 134 with its_session_utils.ItsSession( 135 device_id=self.dut.serial, 136 camera_id=self.camera_id, 137 hidden_physical_id=self.hidden_physical_id) as cam: 138 props = cam.get_camera_properties() 139 props = cam.override_with_hidden_physical_camera_props(props) 140 camera_properties_utils.skip_unless( 141 camera_properties_utils.raw_output(props) and 142 camera_properties_utils.post_raw_sensitivity_boost(props) and 143 camera_properties_utils.compute_target_exposure(props) and 144 camera_properties_utils.per_frame_control(props) and 145 not camera_properties_utils.mono_camera(props)) 146 log_path = self.log_path 147 148 # Load chart for scene 149 its_session_utils.load_scene( 150 cam, props, self.scene, self.tablet, self.chart_distance) 151 152 # Create reqs & do caps 153 settings, reqs, out_surfaces = create_requests(cam, props, log_path) 154 raw_caps, yuv_caps = cam.do_capture(reqs, out_surfaces) 155 if not isinstance(raw_caps, list): 156 raw_caps = [raw_caps] 157 if not isinstance(yuv_caps, list): 158 yuv_caps = [yuv_caps] 159 160 # Extract data 161 raw_means = [] 162 yuv_means = [] 163 for i in range(len(reqs)): 164 sens, sens_boost = settings[i] 165 raw_file_name = '%s_raw_s=%04d_boost=%04d.jpg' % ( 166 os.path.join(log_path, _NAME), sens, sens_boost) 167 raw_means.append(compute_patch_means(raw_caps[i], props, raw_file_name)) 168 169 yuv_file_name = '%s_yuv_s=%04d_boost=%04d.jpg' % ( 170 os.path.join(log_path, _NAME), sens, sens_boost) 171 yuv_means.append(compute_patch_means(yuv_caps[i], props, yuv_file_name)) 172 173 logging.debug('s=%d, s_boost=%d: raw_means %s, yuv_means %s', 174 sens, sens_boost, str(raw_means[-1]), str(yuv_means[-1])) 175 cap_idxs = range(len(reqs)) 176 177 # Create plots 178 create_plots(cap_idxs, raw_means, yuv_means, log_path) 179 180 # RAW asserts 181 for step in range(1, len(reqs)): 182 sens_prev, _ = settings[step - 1] 183 sens, sens_boost = settings[step] 184 expected_ratio = sens_prev / sens 185 for ch, _ in enumerate(_COLORS): 186 ratio_per_step = raw_means[step-1][ch] / raw_means[step][ch] 187 logging.debug('Step: (%d, %d) %s channel: (%f, %f), ratio: %f,', 188 step - 1, step, _COLORS[ch], raw_means[step - 1][ch], 189 raw_means[step][ch], ratio_per_step) 190 if raw_means[step][ch] <= _RAW_PIXEL_THRESH: 191 continue 192 if not math.isclose(ratio_per_step, expected_ratio, 193 rel_tol=_RATIO_RTOL): 194 raise AssertionError( 195 f'step: {step}, ratio: {ratio_per_step}, expected ratio: ' 196 f'{expected_ratio:.3f}, RTOL: {_RATIO_RTOL}') 197 198 # YUV asserts 199 for ch, _ in enumerate(_COLORS): 200 vals = [val[ch] for val in yuv_means] 201 for idx in cap_idxs: 202 if raw_means[idx][ch] <= _RAW_PIXEL_THRESH: 203 vals = vals[:idx] 204 mean = sum(vals) / len(vals) 205 logging.debug('%s channel vals %s mean %f', _COLORS[ch], vals, mean) 206 for step in range(len(vals)): 207 ratio_mean = vals[step] / mean 208 if not math.isclose(1.0, ratio_mean, rel_tol=_RATIO_RTOL): 209 raise AssertionError( 210 f'Capture vs mean ratio: {ratio_mean}, RTOL: +/- {_RATIO_RTOL}') 211 212if __name__ == '__main__': 213 test_runner.main() 214