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_TOLERANCE = 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 for j in range(_NUM_COLORS): 142 pylab.plot(_NR_MODES_LIST, snrs[j], '-'+'rgb'[j]+'o') 143 pylab.xlabel('Noise Reduction Mode') 144 pylab.ylabel('SNR (dB)') 145 pylab.xticks(_NR_MODES_LIST) 146 matplotlib.pyplot.savefig(f'{name_with_log_path}_plot_SNRs.png') 147 148 if nr_modes_reported != _NR_MODES_LIST: 149 raise AssertionError(f'{nr_modes_reported} != {_NR_MODES_LIST}') 150 151 for j in range(_NUM_COLORS): 152 # Higher SNR is better 153 # Verify OFF is not better than FAST 154 if (snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['FAST']] + 155 _SNR_TOLERANCE): 156 raise AssertionError( 157 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 158 f"FAST: {snrs[j][_NR_MODES['FAST']]:.3f}, TOL: {_SNR_TOLERANCE}") 159 160 # Verify FAST is not better than HQ 161 if (snrs[j][_NR_MODES['FAST']] >= snrs[j][_NR_MODES['HQ']] + 162 _SNR_TOLERANCE): 163 raise AssertionError( 164 f"{_COLORS[j]} FAST: {snrs[j][_NR_MODES['FAST']]:.3f}, " 165 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}, TOL: {_SNR_TOLERANCE}") 166 167 # Verify HQ is better than OFF 168 if snrs[j][_NR_MODES['HQ']] <= snrs[j][_NR_MODES['OFF']]: 169 raise AssertionError( 170 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 171 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}") 172 173 if camera_properties_utils.noise_reduction_mode(props, _NR_MODES['MIN']): 174 # Verify OFF is not better than MINIMAL 175 if not(snrs[j][_NR_MODES['OFF']] < snrs[j][_NR_MODES['MIN']] + 176 _SNR_TOLERANCE): 177 raise AssertionError( 178 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 179 f"MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, TOL: {_SNR_TOLERANCE}") 180 181 # Verify MINIMAL is not better than HQ 182 if not (snrs[j][_NR_MODES['MIN']] < snrs[j][_NR_MODES['HQ']] + 183 _SNR_TOLERANCE): 184 raise AssertionError( 185 f"{_COLORS[j]} MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, " 186 f"HQ: {snrs[j][_NR_MODES['HQ']]:.3f}, TOL: {_SNR_TOLERANCE}") 187 188 # Verify ZSL is close to MINIMAL 189 if camera_properties_utils.noise_reduction_mode( 190 props, _NR_MODES['ZSL']): 191 if not math.isclose(snrs[j][_NR_MODES['ZSL']], 192 snrs[j][_NR_MODES['MIN']], 193 abs_tol=_SNR_TOLERANCE): 194 raise AssertionError( 195 f"{_COLORS[j]} ZSL: {snrs[j][_NR_MODES['ZSL']]:.3f}, " 196 f"MIN: {snrs[j][_NR_MODES['MIN']]:.3f}, TOL: {_SNR_TOLERANCE}") 197 198 elif camera_properties_utils.noise_reduction_mode( 199 props, _NR_MODES['ZSL']): 200 # Verify ZSL is close to OFF 201 if not math.isclose( 202 snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['OFF']], 203 abs_tol=_SNR_TOLERANCE): 204 raise AssertionError( 205 f"{_COLORS[j]} OFF: {snrs[j][_NR_MODES['OFF']]:.3f}, " 206 f"ZSL: {snrs[j][_NR_MODES['ZSL']]:.3f}, TOL: {_SNR_TOLERANCE}") 207 208 209if __name__ == '__main__': 210 test_runner.main() 211 212