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