1# Copyright 2014 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 RAW sensitivity burst.""" 15 16 17import logging 18import os.path 19import matplotlib 20from matplotlib import pylab 21from mobly import test_runner 22import numpy as np 23 24import its_base_test 25import camera_properties_utils 26import capture_request_utils 27import image_processing_utils 28import its_session_utils 29 30_GR_PLANE_IDX = 1 # GR plane index in RGGB data 31_IMG_STATS_GRID = 9 # find used to find the center 11.11% 32_NAME = os.path.splitext(os.path.basename(__file__))[0] 33_NUM_FRAMES = 4 34_NUM_STEPS = 5 35_VAR_THRESH = 1.01 # each shot must be 1% noisier than previous 36 37 38def define_raw_stats_fmt(props): 39 """Defines the format using camera props active array width and height.""" 40 aax = props['android.sensor.info.preCorrectionActiveArraySize']['left'] 41 aay = props['android.sensor.info.preCorrectionActiveArraySize']['top'] 42 aaw = props['android.sensor.info.preCorrectionActiveArraySize']['right'] - aax 43 aah = props[ 44 'android.sensor.info.preCorrectionActiveArraySize']['bottom'] - aay 45 46 return {'format': 'rawStats', 47 'gridWidth': aaw // _IMG_STATS_GRID, 48 'gridHeight': aah // _IMG_STATS_GRID} 49 50 51class RawBurstSensitivityTest(its_base_test.ItsBaseTest): 52 """Captures a set of RAW images with increasing sensitivity & measures noise. 53 54 Sensitivity range (gain) is determined from camera properties and limited to 55 the analog sensitivity range as captures are RAW only in a burst. Digital 56 sensitivity range from props['android.sensor.info.sensitivityRange'] is not 57 used. 58 59 Uses RawStats capture format to speed up processing. RawStats defines a grid 60 over the RAW image and returns average and variance of requested areas. 61 white_level is found from camera to normalize variance values from RawStats. 62 63 Noise (image variance) of center patch should increase with increasing 64 sensitivity. 65 """ 66 67 def test_raw_burst_sensitivity(self): 68 with its_session_utils.ItsSession( 69 device_id=self.dut.serial, 70 camera_id=self.camera_id, 71 hidden_physical_id=self.hidden_physical_id) as cam: 72 props = cam.get_camera_properties() 73 props = cam.override_with_hidden_physical_camera_props(props) 74 camera_properties_utils.skip_unless( 75 camera_properties_utils.raw16(props) and 76 camera_properties_utils.manual_sensor(props) and 77 camera_properties_utils.read_3a(props) and 78 camera_properties_utils.per_frame_control(props) and 79 not camera_properties_utils.mono_camera(props)) 80 name_with_log_path = os.path.join(self.log_path, _NAME) 81 82 # Load chart for scene 83 its_session_utils.load_scene( 84 cam, props, self.scene, self.tablet, 85 its_session_utils.CHART_DISTANCE_NO_SCALING) 86 87 # Find sensitivity range and create capture requests 88 sens_min, _ = props['android.sensor.info.sensitivityRange'] 89 sens_max = props['android.sensor.maxAnalogSensitivity'] 90 sens_step = (sens_max - sens_min) // _NUM_STEPS 91 # Intentionally blur images for noise measurements 92 sens_ae, exp_ae, _, _, _ = cam.do_3a(do_af=False, get_results=True) 93 sens_exp_prod = sens_ae * exp_ae 94 reqs = [] 95 settings = [] 96 for sens in range(sens_min, sens_max, sens_step): 97 exp = int(sens_exp_prod / float(sens)) 98 req = capture_request_utils.manual_capture_request(sens, exp, 0) 99 for i in range(_NUM_FRAMES): 100 reqs.append(req) 101 settings.append((sens, exp)) 102 103 # Get rawStats capture format 104 fmt = define_raw_stats_fmt(props) 105 106 # Do captures 107 caps = cam.do_capture(reqs, fmt) 108 109 # Find white_level for RawStats normalization & CFA order 110 white_level = float(props['android.sensor.info.whiteLevel']) 111 cfa_idxs = image_processing_utils.get_canonical_cfa_order(props) 112 113 # Extract variances from each shot 114 variances = [] 115 for i, cap in enumerate(caps): 116 sens, exp = settings[i] 117 _, var_image = image_processing_utils.unpack_rawstats_capture(cap) 118 var = var_image[_IMG_STATS_GRID//2, _IMG_STATS_GRID//2, 119 cfa_idxs[_GR_PLANE_IDX]]/white_level**2 120 variances.append(var) 121 logging.debug('s=%d, e=%d, var=%e', sens, exp, var) 122 123 # Create a plot 124 x = range(len(variances)) 125 pylab.figure(_NAME) 126 pylab.plot(x, variances, '-ro') 127 pylab.xticks(x) 128 pylab.ticklabel_format(style='sci', axis='y', scilimits=(-6, -6)) 129 pylab.xlabel('Setting Combination') 130 pylab.ylabel('Image Center Patch Variance') 131 pylab.title(_NAME) 132 matplotlib.pyplot.savefig( 133 f'{name_with_log_path}_variances.png') 134 135 # Find average variance at each step 136 vars_step_means = [] 137 for i in range(_NUM_STEPS): 138 vars_step = [] 139 for j in range(_NUM_FRAMES): 140 vars_step.append(variances[_NUM_FRAMES * i + j]) 141 vars_step_means.append(np.mean(vars_step)) 142 logging.debug('averaged variances: %s', vars_step_means) 143 144 # Assert each set of shots is noisier than previous and save img on FAIL 145 for variance_idx, variance in enumerate(vars_step_means[:-1]): 146 if variance >= vars_step_means[variance_idx+1] / _VAR_THRESH: 147 image_processing_utils.capture_scene_image( 148 cam, props, name_with_log_path 149 ) 150 raise AssertionError( 151 f'variances [i]: {variances[variance_idx]:.5f}, ' 152 f'[i+1]: {variances[variance_idx+1]:.5f}, THRESH: {_VAR_THRESH}' 153 ) 154 155if __name__ == '__main__': 156 test_runner.main() 157