1# Copyright 2015 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 android.noiseReduction.mode applied for reprocessing reqs.""" 15 16 17import logging 18import math 19import os.path 20import matplotlib 21from matplotlib import pylab 22from mobly import test_runner 23import numpy as np 24 25import its_base_test 26import camera_properties_utils 27import capture_request_utils 28import image_processing_utils 29import its_session_utils 30import target_exposure_utils 31 32_COLORS = ('R', 'G', 'B') 33_NAME = os.path.splitext(os.path.basename(__file__))[0] 34_NR_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'MIN': 3, 'ZSL': 4} 35_NR_MODES_LIST = tuple(_NR_MODES.values()) 36_NUM_FRAMES = 4 37_PATCH_H = 0.1 # center 10% 38_PATCH_W = 0.1 39_PATCH_X = 0.5 - _PATCH_W/2 40_PATCH_Y = 0.5 - _PATCH_H/2 41_SNR_ATOL = 3 # unit in dB 42 43 44def calc_rgb_snr(cap, frame, nr_mode, name_with_log_path): 45 """Calculate the RGB SNRs from a capture center patch. 46 47 Args: 48 cap: Camera capture object. 49 frame: Integer frame number. 50 nr_mode: Integer noise reduction mode index. 51 name_with_log_path: Test name with path for storage 52 53 Returns: 54 RGB SNRs. 55 """ 56 img = image_processing_utils.decompress_jpeg_to_rgb_image(cap) 57 if frame == 0: # save 1st frame 58 image_processing_utils.write_image( 59 img, f'{name_with_log_path}_high_gain_nr={nr_mode}_fmt=jpg.jpg') 60 patch = image_processing_utils.get_image_patch( 61 img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 62 return image_processing_utils.compute_image_snrs(patch) 63 64 65def create_plot(snrs, reprocess_format, name_with_log_path): 66 """create plot from data. 67 68 Args: 69 snrs: RGB SNR data from NR_MODES captures. 70 reprocess_format: String of 'yuv' or 'private'. 71 name_with_log_path: Test name with path for storage. 72 """ 73 pylab.figure(reprocess_format) 74 for ch, color in enumerate(_COLORS): 75 pylab.plot(_NR_MODES_LIST, snrs[ch], f'-{color.lower()}o') 76 pylab.title(f'{_NAME} ({reprocess_format})') 77 pylab.xlabel(f'{str(_NR_MODES)[1:-1]}') # strip '{' '}' off string 78 pylab.ylabel('SNR (dB)') 79 pylab.xticks(_NR_MODES_LIST) 80 matplotlib.pyplot.savefig( 81 f'{name_with_log_path}_plot_{reprocess_format}_SNRs.png') 82 83 84class ReprocessNoiseReductionTest(its_base_test.ItsBaseTest): 85 """Test android.noiseReduction.mode is applied for reprocessing requests. 86 87 Uses JPEG captures for the reprocessing as YUV captures are not available. 88 Uses high analog gain to ensure the captured images are noisy. 89 90 Determines which reprocessing formats are available among 'yuv' and 'private'. 91 For each reprocessing format: 92 Captures in supported reprocessed modes. 93 Averages _NUM_FRAMES to account for frame-to-frame variation. 94 Logs min/max of captures for debug if gross outlier. 95 Noise reduction (NR) modes: 96 OFF, FAST, High Quality (HQ), Minimal (MIN), and zero shutter lag (ZSL) 97 98 Proper behavior: 99 FAST >= OFF, HQ >= FAST, HQ >> OFF 100 if MIN mode supported: MIN >= OFF, HQ >= MIN, ZSL ~ MIN 101 else: ZSL ~ OFF 102 """ 103 104 def test_reprocess_noise_reduction(self): 105 logging.debug('Starting %s', _NAME) 106 logging.debug('NR_MODES: %s', str(_NR_MODES)) 107 with its_session_utils.ItsSession( 108 device_id=self.dut.serial, 109 camera_id=self.camera_id, 110 hidden_physical_id=self.hidden_physical_id) as cam: 111 props = cam.get_camera_properties() 112 props = cam.override_with_hidden_physical_camera_props(props) 113 camera_properties_utils.skip_unless( 114 camera_properties_utils.compute_target_exposure(props) and 115 camera_properties_utils.per_frame_control(props) and 116 camera_properties_utils.noise_reduction_mode(props, 0) and 117 (camera_properties_utils.yuv_reprocess(props) or 118 camera_properties_utils.private_reprocess(props))) 119 log_path = self.log_path 120 name_with_log_path = os.path.join(log_path, _NAME) 121 122 # Load chart for scene. 123 its_session_utils.load_scene( 124 cam, props, self.scene, self.tablet, 125 its_session_utils.CHART_DISTANCE_NO_SCALING) 126 127 # If reprocessing is supported, ZSL NR mode must be available. 128 if not camera_properties_utils.noise_reduction_mode( 129 props, _NR_MODES['ZSL']): 130 raise KeyError('Reprocessing supported, so ZSL must be supported.') 131 132 reprocess_formats = [] 133 if camera_properties_utils.yuv_reprocess(props): 134 reprocess_formats.append('yuv') 135 if camera_properties_utils.private_reprocess(props): 136 reprocess_formats.append('private') 137 138 size = capture_request_utils.get_available_output_sizes('jpg', props)[0] 139 out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'} 140 for reprocess_format in reprocess_formats: 141 logging.debug('Reprocess format: %s', reprocess_format) 142 # List of variances for R, G, B. 143 snrs = [[], [], []] 144 nr_modes_reported = [] 145 146 # Capture for each mode. 147 exp, sens = target_exposure_utils.get_target_exposure_combos( 148 log_path, cam)['maxSensitivity'] 149 for nr_mode in _NR_MODES_LIST: 150 # Skip unavailable modes 151 if not camera_properties_utils.noise_reduction_mode(props, nr_mode): 152 nr_modes_reported.append(nr_mode) 153 for ch, _ in enumerate(_COLORS): 154 snrs[ch].append(0) 155 continue 156 157 # Create req, do caps and calc center SNRs. 158 rgb_snr_list = [] 159 nr_modes_reported.append(nr_mode) 160 req = capture_request_utils.manual_capture_request(sens, exp) 161 req['android.noiseReduction.mode'] = nr_mode 162 caps = cam.do_capture( 163 [req]*_NUM_FRAMES, out_surface, reprocess_format) 164 for i in range(_NUM_FRAMES): 165 rgb_snr_list.append(calc_rgb_snr(caps[i]['data'], i, nr_mode, 166 name_with_log_path)) 167 168 r_snrs = [rgb[0] for rgb in rgb_snr_list] 169 g_snrs = [rgb[1] for rgb in rgb_snr_list] 170 b_snrs = [rgb[2] for rgb in rgb_snr_list] 171 rgb_avg_snrs = [np.mean(r_snrs), np.mean(g_snrs), np.mean(b_snrs)] 172 for ch, x_snrs in enumerate([r_snrs, g_snrs, b_snrs]): 173 snrs[ch].append(rgb_avg_snrs[ch]) 174 logging.debug( 175 'NR mode %d %s SNR avg: %.2f min: %.2f, max: %.2f', nr_mode, 176 _COLORS[ch], rgb_avg_snrs[ch], min(x_snrs), max(x_snrs)) 177 178 # Plot data. 179 create_plot(snrs, reprocess_format, name_with_log_path) 180 181 # Assert proper behavior. 182 if nr_modes_reported != list(_NR_MODES_LIST): 183 raise KeyError('Reported modes: ' 184 f'{nr_modes_reported}. Expected: {_NR_MODES_LIST}.') 185 for j, _ in enumerate(_COLORS): 186 # OFF < FAST + ATOL 187 if snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['FAST']]+_SNR_ATOL: 188 raise AssertionError(f'FAST: {snrs[j][_NR_MODES["FAST"]]:.2f}, ' 189 f'OFF: {snrs[j][_NR_MODES["OFF"]]:.2f}, ' 190 f'ATOL: {_SNR_ATOL}') 191 192 # FAST < HQ + ATOL 193 if snrs[j][_NR_MODES['FAST']] >= snrs[j][_NR_MODES['HQ']]+_SNR_ATOL: 194 raise AssertionError(f'HQ: {snrs[j][_NR_MODES["HQ"]]:.2f}, ' 195 f'FAST: {snrs[j][_NR_MODES["FAST"]]:.2f}, ' 196 f'ATOL: {_SNR_ATOL}') 197 198 # HQ > OFF 199 if snrs[j][_NR_MODES['HQ']] <= snrs[j][_NR_MODES['OFF']]: 200 raise AssertionError(f'HQ: {snrs[j][_NR_MODES["HQ"]]:.2f}, ' 201 f'OFF: {snrs[j][_NR_MODES["OFF"]]:.2f}') 202 203 if camera_properties_utils.noise_reduction_mode( 204 props, _NR_MODES['MIN']): 205 # OFF < MIN + ATOL 206 if snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['MIN']]+_SNR_ATOL: 207 raise AssertionError(f'MIN: {snrs[j][_NR_MODES["MIN"]]:.2f}, ' 208 f'OFF: {snrs[j][_NR_MODES["OFF"]]:.2f}, ' 209 f'ATOL: {_SNR_ATOL}') 210 211 # MIN < HQ + ATOL 212 if snrs[j][_NR_MODES['MIN']] >= snrs[j][_NR_MODES['HQ']]+_SNR_ATOL: 213 raise AssertionError(f'MIN: {snrs[j][_NR_MODES["MIN"]]:.2f}, ' 214 f'HQ: {snrs[j][_NR_MODES["HQ"]]:.2f}, ' 215 f'ATOL: {_SNR_ATOL}') 216 217 # ZSL ~ MIN 218 if not math.isclose( 219 snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['MIN']], 220 abs_tol=_SNR_ATOL): 221 raise AssertionError(f'ZSL: {snrs[j][_NR_MODES["ZSL"]]:.2f}, ' 222 f'MIN: {snrs[j][_NR_MODES["MIN"]]:.2f}, ' 223 f'ATOL: {_SNR_ATOL}') 224 else: 225 # ZSL ~ OFF 226 if not math.isclose( 227 snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['OFF']], 228 abs_tol=_SNR_ATOL): 229 raise AssertionError(f'ZSL: {snrs[j][_NR_MODES["ZSL"]]:.2f}, ' 230 f'OFF: {snrs[j][_NR_MODES["OFF"]]:.2f}, ' 231 f'ATOL: {_SNR_ATOL}') 232 233if __name__ == '__main__': 234 test_runner.main() 235 236