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