• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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 RAW sensitivity burst."""
15
16
17import logging
18import os.path
19import matplotlib
20from matplotlib import pylab
21from mobly import test_runner
22
23import its_base_test
24import camera_properties_utils
25import capture_request_utils
26import image_processing_utils
27import its_session_utils
28
29_GR_PLANE_IDX = 1  # GR plane index in RGGB data
30_IMG_STATS_GRID = 9  # find used to find the center 11.11%
31_NAME = os.path.splitext(os.path.basename(__file__))[0]
32_NUM_STEPS = 5
33_VAR_THRESH = 1.01  # each shot must be 1% noisier than previous
34
35
36def define_raw_stats_fmt(props):
37  """Defines the format using camera props active array width and height."""
38  aax = props['android.sensor.info.preCorrectionActiveArraySize']['left']
39  aay = props['android.sensor.info.preCorrectionActiveArraySize']['top']
40  aaw = props['android.sensor.info.preCorrectionActiveArraySize']['right'] - aax
41  aah = props[
42      'android.sensor.info.preCorrectionActiveArraySize']['bottom'] - aay
43
44  return {'format': 'rawStats',
45          'gridWidth': aaw // _IMG_STATS_GRID,
46          'gridHeight': aah // _IMG_STATS_GRID}
47
48
49class RawSensitivityBurstTest(its_base_test.ItsBaseTest):
50  """Captures a set of RAW images with increasing sensitivity & measures noise.
51
52  Sensitivity range (gain) is determined from camera properties and limited to
53  the analog sensitivity range as captures are RAW only in a burst. Digital
54  sensitivity range from props['android.sensor.info.sensitivityRange'] is not
55  used.
56
57  Uses RawStats capture format to speed up processing. RawStats defines a grid
58  over the RAW image and returns average and variance of requested areas.
59  white_level is found from camera to normalize variance values from RawStats.
60
61  Noise (image variance) of center patch should increase with increasing
62  sensitivity.
63  """
64
65  def test_raw_sensitivity_burst(self):
66    logging.debug('Starting %s', _NAME)
67    with its_session_utils.ItsSession(
68        device_id=self.dut.serial,
69        camera_id=self.camera_id,
70        hidden_physical_id=self.hidden_physical_id) as cam:
71      props = cam.get_camera_properties()
72      props = cam.override_with_hidden_physical_camera_props(props)
73      camera_properties_utils.skip_unless(
74          camera_properties_utils.raw16(props) and
75          camera_properties_utils.manual_sensor(props) and
76          camera_properties_utils.read_3a(props) and
77          camera_properties_utils.per_frame_control(props) and
78          not camera_properties_utils.mono_camera(props))
79
80      # Load chart for scene
81      its_session_utils.load_scene(
82          cam, props, self.scene, self.tablet,
83          its_session_utils.CHART_DISTANCE_NO_SCALING)
84
85      # Find sensitivity range and create capture requests
86      sens_min, _ = props['android.sensor.info.sensitivityRange']
87      sens_max = props['android.sensor.maxAnalogSensitivity']
88      sens_step = (sens_max - sens_min) // _NUM_STEPS
89      # Intentionally blur images for noise measurements
90      sens_ae, exp_ae, _, _, _ = cam.do_3a(do_af=False, get_results=True)
91      sens_exp_prod = sens_ae * exp_ae
92      reqs = []
93      settings = []
94      for sens in range(sens_min, sens_max, sens_step):
95        exp = int(sens_exp_prod / float(sens))
96        req = capture_request_utils.manual_capture_request(sens, exp, 0)
97        reqs.append(req)
98        settings.append((sens, exp))
99
100      # Get rawStats capture format
101      fmt = define_raw_stats_fmt(props)
102
103      # Do captures
104      caps = cam.do_capture(reqs, fmt)
105
106      # Extract variances from each shot
107      variances = []
108      for i, cap in enumerate(caps):
109        (sens, exp) = settings[i]
110
111        # Find white_level for RawStats normalization
112        white_level = float(props['android.sensor.info.whiteLevel'])
113        _, var_image = image_processing_utils.unpack_rawstats_capture(cap)
114        cfa_idxs = image_processing_utils.get_canonical_cfa_order(props)
115        var = var_image[_IMG_STATS_GRID//2, _IMG_STATS_GRID//2,
116                        cfa_idxs[_GR_PLANE_IDX]]/white_level**2
117        variances.append(var)
118        logging.debug('s=%d, e=%d, var=%e', sens, exp, var)
119
120      # Create a plot
121      x = range(len(variances))
122      pylab.figure(_NAME)
123      pylab.plot(x, variances, '-ro')
124      pylab.xticks(x)
125      pylab.ticklabel_format(style='sci', axis='y', scilimits=(-6, -6))
126      pylab.xlabel('Setting Combination')
127      pylab.ylabel('Image Center Patch Variance')
128      pylab.title(_NAME)
129      matplotlib.pyplot.savefig(
130          f'{os.path.join(self.log_path, _NAME)}_variances.png')
131
132      # Asserts that each shot is noisier than previous
133      for i in x[0:-1]:
134        if variances[i] >= variances[i+1] / _VAR_THRESH:
135          raise AssertionError(
136              f'variances [i]: {variances[i] :.5f}, [i+1]: '
137              f'{variances[i+1]:.5f}, THRESH: {_VAR_THRESH}')
138
139if __name__ == '__main__':
140  test_runner.main()
141