1# Copyright 2013 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 parameters is applied when set.""" 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 = list(_NR_MODES.values()) 36_NUM_COLORS = len(_COLORS) 37_NUM_FRAMES_PER_MODE = 4 38_PATCH_H = 0.1 # center 10% 39_PATCH_W = 0.1 40_PATCH_X = 0.5 - _PATCH_W/2 41_PATCH_Y = 0.5 - _PATCH_H/2 42_SNR_ATOL = 3 # unit in dB 43 44 45class ParamNoiseReductionTest(its_base_test.ItsBaseTest): 46 """Test that the android.noiseReduction.mode param is applied when set. 47 48 Capture images with the camera dimly lit. 49 50 Capture images with low gain and noise reduction off, and use the 51 variance of these captures as the baseline. 52 53 Use high analog gain on remaining tests to ensure captured images are noisy. 54 """ 55 56 def test_param_noise_reduction(self): 57 logging.debug('Starting %s', _NAME) 58 logging.debug('NR_MODES: %s', str(_NR_MODES)) 59 with its_session_utils.ItsSession( 60 device_id=self.dut.serial, 61 camera_id=self.camera_id, 62 hidden_physical_id=self.hidden_physical_id) as cam: 63 props = cam.get_camera_properties() 64 props = cam.override_with_hidden_physical_camera_props(props) 65 log_path = self.log_path 66 name_with_log_path = os.path.join(log_path, _NAME) 67 68 # check SKIP conditions 69 camera_properties_utils.skip_unless( 70 camera_properties_utils.compute_target_exposure(props) and 71 camera_properties_utils.per_frame_control(props) and 72 camera_properties_utils.noise_reduction_mode(props, 0)) 73 74 # Load chart for scene 75 its_session_utils.load_scene( 76 cam, props, self.scene, self.tablet, 77 its_session_utils.CHART_DISTANCE_NO_SCALING) 78 79 snrs = [[], [], []] # List of SNRs for R,G,B 80 ref_snr = [] # Reference (baseline) SNR for each of R,G,B 81 nr_modes_reported = [] 82 83 # NR mode 0 with low gain 84 e, s = target_exposure_utils.get_target_exposure_combos( 85 log_path, cam)['minSensitivity'] 86 req = capture_request_utils.manual_capture_request(s, e) 87 req['android.noiseReduction.mode'] = 0 88 cap = cam.do_capture(req) 89 rgb_image = image_processing_utils.convert_capture_to_rgb_image(cap) 90 image_processing_utils.write_image( 91 rgb_image, f'{name_with_log_path}_low_gain.jpg') 92 rgb_patch = image_processing_utils.get_image_patch( 93 rgb_image, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 94 ref_snr = image_processing_utils.compute_image_snrs(rgb_patch) 95 logging.debug('Ref SNRs: %s', str(ref_snr)) 96 97 e, s = target_exposure_utils.get_target_exposure_combos( 98 log_path, cam)['maxSensitivity'] 99 for mode in _NR_MODES_LIST: 100 # Skip unavailable modes 101 if not camera_properties_utils.noise_reduction_mode(props, mode): 102 nr_modes_reported.append(mode) 103 for channel in range(_NUM_COLORS): 104 snrs[channel].append(0) 105 continue 106 107 rgb_snr_list = [] 108 # Capture several images to account for per frame noise variations 109 for n in range(_NUM_FRAMES_PER_MODE): 110 req = capture_request_utils.manual_capture_request(s, e) 111 req['android.noiseReduction.mode'] = mode 112 cap = cam.do_capture(req) 113 rgb_image = image_processing_utils.convert_capture_to_rgb_image(cap) 114 if n == 0: 115 nr_modes_reported.append( 116 cap['metadata']['android.noiseReduction.mode']) 117 image_processing_utils.write_image( 118 rgb_image, f'{name_with_log_path}_high_gain_nr={mode}.jpg') 119 rgb_patch = image_processing_utils.get_image_patch( 120 rgb_image, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H) 121 rgb_snrs = image_processing_utils.compute_image_snrs(rgb_patch) 122 rgb_snr_list.append(rgb_snrs) 123 124 r_snrs = [rgb[0] for rgb in rgb_snr_list] 125 g_snrs = [rgb[1] for rgb in rgb_snr_list] 126 b_snrs = [rgb[2] for rgb in rgb_snr_list] 127 rgb_snrs = [np.mean(r_snrs), np.mean(g_snrs), np.mean(b_snrs)] 128 logging.debug('NR mode %s SNRs', mode) 129 logging.debug('R SNR: %.2f, Min: %.2f, Max: %.2f', 130 rgb_snrs[0], min(r_snrs), max(r_snrs)) 131 logging.debug('G SNR: %.2f, Min: %.2f, Max: %.2f', 132 rgb_snrs[1], min(g_snrs), max(g_snrs)) 133 logging.debug('B SNR: %.2f, Min: %.2f, Max: %.2f', 134 rgb_snrs[2], min(b_snrs), max(b_snrs)) 135 136 for chan in range(_NUM_COLORS): 137 snrs[chan].append(rgb_snrs[chan]) 138 139 # Draw plot 140 pylab.figure(_NAME) 141 pylab.title(f'{_NAME}') 142 for j in range(_NUM_COLORS): 143 pylab.plot(_NR_MODES_LIST, snrs[j], '-'+'rgb'[j]+'o') 144 pylab.xlabel(f'{str(_NR_MODES)[1:-1]}') # strip '{' '}' off string 145 pylab.ylabel('SNR (dB)') 146 pylab.xticks(_NR_MODES_LIST) 147 matplotlib.pyplot.savefig(f'{name_with_log_path}_plot_SNRs.png') 148 149 if nr_modes_reported != _NR_MODES_LIST: 150 raise AssertionError(f'{nr_modes_reported} != {_NR_MODES_LIST}') 151 152 for j in range(_NUM_COLORS): 153 # Higher SNR is better 154 # Verify OFF is not better than FAST 155 if (snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['FAST']] + 156 _SNR_ATOL): 157 raise AssertionError( 158 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 159 f"FAST: {snrs[j][_NR_MODES['FAST']]:.3f}, ATOL: {_SNR_ATOL}") 160 161 # Verify FAST is not better than HQ 162 if (snrs[j][_NR_MODES['FAST']] >= snrs[j][_NR_MODES['HQ']] + 163 _SNR_ATOL): 164 raise AssertionError( 165 f"{_COLORS[j]} FAST: {snrs[j][_NR_MODES['FAST']]:.3f}, " 166 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}, ATOL: {_SNR_ATOL}") 167 168 # Verify HQ is better than OFF 169 if snrs[j][_NR_MODES['HQ']] <= snrs[j][_NR_MODES['OFF']]: 170 raise AssertionError( 171 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 172 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}") 173 174 if camera_properties_utils.noise_reduction_mode(props, _NR_MODES['MIN']): 175 # Verify OFF is not better than MINIMAL 176 if not(snrs[j][_NR_MODES['OFF']] < snrs[j][_NR_MODES['MIN']] + 177 _SNR_ATOL): 178 raise AssertionError( 179 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 180 f"MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, ATOL: {_SNR_ATOL}") 181 182 # Verify MINIMAL is not better than HQ 183 if not (snrs[j][_NR_MODES['MIN']] < snrs[j][_NR_MODES['HQ']] + 184 _SNR_ATOL): 185 raise AssertionError( 186 f"{_COLORS[j]} MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, " 187 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}, ATOL: {_SNR_ATOL}") 188 189 # Verify ZSL is close to MINIMAL 190 if camera_properties_utils.noise_reduction_mode( 191 props, _NR_MODES['ZSL']): 192 if not math.isclose(snrs[j][_NR_MODES['ZSL']], 193 snrs[j][_NR_MODES['MIN']], 194 abs_tol=_SNR_ATOL): 195 raise AssertionError( 196 f"{_COLORS[j]} ZSL: {snrs[j][_NR_MODES['ZSL']]:.3f}, " 197 f"MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, ATOL: {_SNR_ATOL}") 198 199 elif camera_properties_utils.noise_reduction_mode( 200 props, _NR_MODES['ZSL']): 201 # Verify ZSL is close to OFF 202 if not math.isclose( 203 snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['OFF']], 204 abs_tol=_SNR_ATOL): 205 raise AssertionError( 206 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 207 f"ZSL: {snrs[j][_NR_MODES['ZSL']]:.3f}, ATOL: {_SNR_ATOL}") 208 209 210if __name__ == '__main__': 211 test_runner.main() 212