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