xref: /aosp_15_r20/cts/apps/CameraITS/tools/dng_noise_model.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1*b7c941bbSAndroid Build Coastguard Worker# Copyright 2014 The Android Open Source Project
2*b7c941bbSAndroid Build Coastguard Worker#
3*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*b7c941bbSAndroid Build Coastguard Worker#
7*b7c941bbSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*b7c941bbSAndroid Build Coastguard Worker#
9*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*b7c941bbSAndroid Build Coastguard Worker# limitations under the License.
14*b7c941bbSAndroid Build Coastguard Worker"""CameraITS script to generate noise models."""
15*b7c941bbSAndroid Build Coastguard Worker
16*b7c941bbSAndroid Build Coastguard Workerimport logging
17*b7c941bbSAndroid Build Coastguard Workerimport math
18*b7c941bbSAndroid Build Coastguard Workerimport os.path
19*b7c941bbSAndroid Build Coastguard Workerimport pathlib
20*b7c941bbSAndroid Build Coastguard Workerimport pickle
21*b7c941bbSAndroid Build Coastguard Workerimport tempfile
22*b7c941bbSAndroid Build Coastguard Workerimport textwrap
23*b7c941bbSAndroid Build Coastguard Worker
24*b7c941bbSAndroid Build Coastguard Workerimport capture_read_noise_utils
25*b7c941bbSAndroid Build Coastguard Workerimport its_base_test
26*b7c941bbSAndroid Build Coastguard Workerimport its_session_utils
27*b7c941bbSAndroid Build Coastguard Workerfrom matplotlib import pyplot as plt
28*b7c941bbSAndroid Build Coastguard Workerimport matplotlib.ticker
29*b7c941bbSAndroid Build Coastguard Workerfrom mobly import test_runner
30*b7c941bbSAndroid Build Coastguard Workerimport noise_model_constants
31*b7c941bbSAndroid Build Coastguard Workerimport noise_model_utils
32*b7c941bbSAndroid Build Coastguard Workerimport numpy as np
33*b7c941bbSAndroid Build Coastguard Worker
34*b7c941bbSAndroid Build Coastguard Worker_IS_QUAD_BAYER = False  # A manual flag to choose standard or quad Bayer noise
35*b7c941bbSAndroid Build Coastguard Worker                        # model generation.
36*b7c941bbSAndroid Build Coastguard Workerif _IS_QUAD_BAYER:
37*b7c941bbSAndroid Build Coastguard Worker  _COLOR_CHANNEL_NAMES = noise_model_constants.QUAD_BAYER_COLORS
38*b7c941bbSAndroid Build Coastguard Worker  _PLOT_COLORS = noise_model_constants.QUAD_BAYER_PLOT_COLORS
39*b7c941bbSAndroid Build Coastguard Worker  _TILE_SIZE = 64  # Tile size to compute mean/variance. Large tiles may have
40*b7c941bbSAndroid Build Coastguard Worker                   # their variance corrupted by low freq image changes.
41*b7c941bbSAndroid Build Coastguard Worker  _STATS_FORMAT = 'raw10QuadBayerStats'  # rawQuadBayerStats|raw10QuadBayerStats
42*b7c941bbSAndroid Build Coastguard Worker  _READ_NOISE_RAW_FORMAT = 'raw10QuadBayer'  # rawQuadBayer|raw10QuadBayer
43*b7c941bbSAndroid Build Coastguard Workerelse:
44*b7c941bbSAndroid Build Coastguard Worker  _COLOR_CHANNEL_NAMES = noise_model_constants.BAYER_COLORS
45*b7c941bbSAndroid Build Coastguard Worker  _PLOT_COLORS = noise_model_constants.BAYER_PLOT_COLORS
46*b7c941bbSAndroid Build Coastguard Worker  _TILE_SIZE = 32  # Tile size to compute mean/variance. Large tiles may have
47*b7c941bbSAndroid Build Coastguard Worker                   # their variance corrupted by low freq image changes.
48*b7c941bbSAndroid Build Coastguard Worker  _STATS_FORMAT = 'rawStats'  # rawStats|raw10Stats
49*b7c941bbSAndroid Build Coastguard Worker  _READ_NOISE_RAW_FORMAT = 'raw'  # raw|raw10
50*b7c941bbSAndroid Build Coastguard Worker
51*b7c941bbSAndroid Build Coastguard Worker_STATS_CONFIG = {
52*b7c941bbSAndroid Build Coastguard Worker    'format': _STATS_FORMAT,
53*b7c941bbSAndroid Build Coastguard Worker    'gridWidth': _TILE_SIZE,
54*b7c941bbSAndroid Build Coastguard Worker    'gridHeight': _TILE_SIZE,
55*b7c941bbSAndroid Build Coastguard Worker}
56*b7c941bbSAndroid Build Coastguard Worker_BRACKET_MAX = 8  # Exposure bracketing range in stops
57*b7c941bbSAndroid Build Coastguard Worker_BRACKET_FACTOR = math.pow(2, _BRACKET_MAX)
58*b7c941bbSAndroid Build Coastguard Worker_ISO_MAX_VALUE = None  # ISO range max value, uses sensor max if None
59*b7c941bbSAndroid Build Coastguard Worker_ISO_MIN_VALUE = None  # ISO range min value, uses sensor min if None
60*b7c941bbSAndroid Build Coastguard Worker_MAX_SCALE_FUDGE = 1.1
61*b7c941bbSAndroid Build Coastguard Worker_MAX_SIGNAL_VALUE = 0.25  # Maximum value to allow mean of the tiles to go.
62*b7c941bbSAndroid Build Coastguard Worker_NAME = os.path.basename(__file__).split('.')[0]
63*b7c941bbSAndroid Build Coastguard Worker_NAME_READ_NOISE = os.path.join(tempfile.gettempdir(), 'CameraITS/ReadNoise')
64*b7c941bbSAndroid Build Coastguard Worker_NAME_READ_NOISE_FILE = 'read_noise_results.pkl'
65*b7c941bbSAndroid Build Coastguard Worker_STATS_FILE_NAME = 'stats.pkl'
66*b7c941bbSAndroid Build Coastguard Worker_OUTLIER_MEDIAN_ABS_DEVS = 10  # Defines the number of Median Absolute
67*b7c941bbSAndroid Build Coastguard Worker                               # Deviations that constitutes acceptable data
68*b7c941bbSAndroid Build Coastguard Worker_READ_NOISE_STEPS_PER_STOP = 12  # Sensitivities per stop to sample for read
69*b7c941bbSAndroid Build Coastguard Worker                                 # noise
70*b7c941bbSAndroid Build Coastguard Worker_REMOVE_VAR_OUTLIERS = False  # When True, filters the variance to remove
71*b7c941bbSAndroid Build Coastguard Worker                              # outliers
72*b7c941bbSAndroid Build Coastguard Worker_STEPS_PER_STOP = 3  # How many sensitivities per stop to sample.
73*b7c941bbSAndroid Build Coastguard Worker_ISO_MULTIPLIER = math.pow(2, 1.0 / _STEPS_PER_STOP)
74*b7c941bbSAndroid Build Coastguard Worker_TILE_CROP_N = 0  # Number of tiles to crop from edge of image. Usually 0.
75*b7c941bbSAndroid Build Coastguard Worker_TWO_STAGE_MODEL = False  # Require read noise data prior to running noise model
76*b7c941bbSAndroid Build Coastguard Worker_ZOOM_RATIO = 1  # Zoom target to be used while running the model
77*b7c941bbSAndroid Build Coastguard Worker_FIG_DPI = 100  # DPI for plotting noise model figures.
78*b7c941bbSAndroid Build Coastguard Worker_BAYER_COLORS_FOR_NOISE_PROFILE = tuple(
79*b7c941bbSAndroid Build Coastguard Worker    map(str.lower, noise_model_constants.BAYER_COLORS)
80*b7c941bbSAndroid Build Coastguard Worker)
81*b7c941bbSAndroid Build Coastguard Worker_QUAD_BAYER_COLORS_FOR_NOISE_PROFILE = tuple(
82*b7c941bbSAndroid Build Coastguard Worker    map(str.lower, noise_model_constants.QUAD_BAYER_COLORS)
83*b7c941bbSAndroid Build Coastguard Worker)
84*b7c941bbSAndroid Build Coastguard Worker
85*b7c941bbSAndroid Build Coastguard Worker
86*b7c941bbSAndroid Build Coastguard Workerclass DngNoiseModel(its_base_test.ItsBaseTest):
87*b7c941bbSAndroid Build Coastguard Worker  """Create DNG noise model.
88*b7c941bbSAndroid Build Coastguard Worker
89*b7c941bbSAndroid Build Coastguard Worker  Captures RAW images with increasing analog gains to create the model.
90*b7c941bbSAndroid Build Coastguard Worker  """
91*b7c941bbSAndroid Build Coastguard Worker
92*b7c941bbSAndroid Build Coastguard Worker  def _create_noise_model_code(self, noise_model, sens_min, sens_max,
93*b7c941bbSAndroid Build Coastguard Worker                               sens_max_analog, file_path):
94*b7c941bbSAndroid Build Coastguard Worker    """Creates the C file for the noise model.
95*b7c941bbSAndroid Build Coastguard Worker
96*b7c941bbSAndroid Build Coastguard Worker    Args:
97*b7c941bbSAndroid Build Coastguard Worker      noise_model: Noise model parameters.
98*b7c941bbSAndroid Build Coastguard Worker      sens_min: The minimum sensitivity value.
99*b7c941bbSAndroid Build Coastguard Worker      sens_max: The maximum sensitivity value.
100*b7c941bbSAndroid Build Coastguard Worker      sens_max_analog: The maximum analog sensitivity value.
101*b7c941bbSAndroid Build Coastguard Worker      file_path: The path to the noise model file.
102*b7c941bbSAndroid Build Coastguard Worker    """
103*b7c941bbSAndroid Build Coastguard Worker    # Generate individual noise model components.
104*b7c941bbSAndroid Build Coastguard Worker    scale_a, scale_b, offset_a, offset_b = zip(*noise_model)
105*b7c941bbSAndroid Build Coastguard Worker    digital_gain_cdef = (
106*b7c941bbSAndroid Build Coastguard Worker        f'(sens / {sens_max_analog:.1f}) < 1.0 ? '
107*b7c941bbSAndroid Build Coastguard Worker        f'1.0 : (sens / {sens_max_analog:.1f})'
108*b7c941bbSAndroid Build Coastguard Worker    )
109*b7c941bbSAndroid Build Coastguard Worker
110*b7c941bbSAndroid Build Coastguard Worker    with open(file_path, 'w') as text_file:
111*b7c941bbSAndroid Build Coastguard Worker      scale_a_str = ','.join([str(i) for i in scale_a])
112*b7c941bbSAndroid Build Coastguard Worker      scale_b_str = ','.join([str(i) for i in scale_b])
113*b7c941bbSAndroid Build Coastguard Worker      offset_a_str = ','.join([str(i) for i in offset_a])
114*b7c941bbSAndroid Build Coastguard Worker      offset_b_str = ','.join([str(i) for i in offset_b])
115*b7c941bbSAndroid Build Coastguard Worker      # pylint: disable=line-too-long
116*b7c941bbSAndroid Build Coastguard Worker      code = textwrap.dedent(f"""\
117*b7c941bbSAndroid Build Coastguard Worker              /* Generated test code to dump a table of data for external validation
118*b7c941bbSAndroid Build Coastguard Worker              * of the noise model parameters.
119*b7c941bbSAndroid Build Coastguard Worker              */
120*b7c941bbSAndroid Build Coastguard Worker              #include <stdio.h>
121*b7c941bbSAndroid Build Coastguard Worker              #include <assert.h>
122*b7c941bbSAndroid Build Coastguard Worker              double compute_noise_model_entry_S(int plane, int sens);
123*b7c941bbSAndroid Build Coastguard Worker              double compute_noise_model_entry_O(int plane, int sens);
124*b7c941bbSAndroid Build Coastguard Worker              int main(void) {{
125*b7c941bbSAndroid Build Coastguard Worker                  for (int plane = 0; plane < {len(scale_a)}; plane++) {{
126*b7c941bbSAndroid Build Coastguard Worker                      for (int sens = {sens_min}; sens <= {sens_max}; sens += 100) {{
127*b7c941bbSAndroid Build Coastguard Worker                          double o = compute_noise_model_entry_O(plane, sens);
128*b7c941bbSAndroid Build Coastguard Worker                          double s = compute_noise_model_entry_S(plane, sens);
129*b7c941bbSAndroid Build Coastguard Worker                          printf("%d,%d,%lf,%lf\\n", plane, sens, o, s);
130*b7c941bbSAndroid Build Coastguard Worker                      }}
131*b7c941bbSAndroid Build Coastguard Worker                  }}
132*b7c941bbSAndroid Build Coastguard Worker                  return 0;
133*b7c941bbSAndroid Build Coastguard Worker              }}
134*b7c941bbSAndroid Build Coastguard Worker
135*b7c941bbSAndroid Build Coastguard Worker              /* Generated functions to map a given sensitivity to the O and S noise
136*b7c941bbSAndroid Build Coastguard Worker              * model parameters in the DNG noise model. The planes are in
137*b7c941bbSAndroid Build Coastguard Worker              * R, Gr, Gb, B order.
138*b7c941bbSAndroid Build Coastguard Worker              */
139*b7c941bbSAndroid Build Coastguard Worker              double compute_noise_model_entry_S(int plane, int sens) {{
140*b7c941bbSAndroid Build Coastguard Worker                  static double noise_model_A[] = {{ {scale_a_str:s} }};
141*b7c941bbSAndroid Build Coastguard Worker                  static double noise_model_B[] = {{ {scale_b_str:s} }};
142*b7c941bbSAndroid Build Coastguard Worker                  double A = noise_model_A[plane];
143*b7c941bbSAndroid Build Coastguard Worker                  double B = noise_model_B[plane];
144*b7c941bbSAndroid Build Coastguard Worker                  double s = A * sens + B;
145*b7c941bbSAndroid Build Coastguard Worker                  return s < 0.0 ? 0.0 : s;
146*b7c941bbSAndroid Build Coastguard Worker              }}
147*b7c941bbSAndroid Build Coastguard Worker
148*b7c941bbSAndroid Build Coastguard Worker              double compute_noise_model_entry_O(int plane, int sens) {{
149*b7c941bbSAndroid Build Coastguard Worker                  static double noise_model_C[] = {{ {offset_a_str:s} }};
150*b7c941bbSAndroid Build Coastguard Worker                  static double noise_model_D[] = {{ {offset_b_str:s} }};
151*b7c941bbSAndroid Build Coastguard Worker                  double digital_gain = {digital_gain_cdef:s};
152*b7c941bbSAndroid Build Coastguard Worker                  double C = noise_model_C[plane];
153*b7c941bbSAndroid Build Coastguard Worker                  double D = noise_model_D[plane];
154*b7c941bbSAndroid Build Coastguard Worker                  double o = C * sens * sens + D * digital_gain * digital_gain;
155*b7c941bbSAndroid Build Coastguard Worker                  return o < 0.0 ? 0.0 : o;
156*b7c941bbSAndroid Build Coastguard Worker              }}
157*b7c941bbSAndroid Build Coastguard Worker              """)
158*b7c941bbSAndroid Build Coastguard Worker
159*b7c941bbSAndroid Build Coastguard Worker      text_file.write(code)
160*b7c941bbSAndroid Build Coastguard Worker
161*b7c941bbSAndroid Build Coastguard Worker  def _create_noise_profile_code(self, noise_model, color_channels, file_path):
162*b7c941bbSAndroid Build Coastguard Worker    """Creates the noise profile C++ file.
163*b7c941bbSAndroid Build Coastguard Worker
164*b7c941bbSAndroid Build Coastguard Worker    Args:
165*b7c941bbSAndroid Build Coastguard Worker      noise_model: Noise model parameters.
166*b7c941bbSAndroid Build Coastguard Worker      color_channels: Color channels in canonical order.
167*b7c941bbSAndroid Build Coastguard Worker      file_path: The path to the noise profile C++ file.
168*b7c941bbSAndroid Build Coastguard Worker    """
169*b7c941bbSAndroid Build Coastguard Worker    # Generate individual noise model components.
170*b7c941bbSAndroid Build Coastguard Worker    scale_a, scale_b, offset_a, offset_b = zip(*noise_model)
171*b7c941bbSAndroid Build Coastguard Worker    num_channels = noise_model.shape[0]
172*b7c941bbSAndroid Build Coastguard Worker    params = []
173*b7c941bbSAndroid Build Coastguard Worker    for ch, color in enumerate(color_channels):
174*b7c941bbSAndroid Build Coastguard Worker      prefix = f'.noise_coefficients_{color} = {{'
175*b7c941bbSAndroid Build Coastguard Worker      spaces = ' ' * len(prefix)
176*b7c941bbSAndroid Build Coastguard Worker      suffix = '},' if ch != num_channels - 1 else '}'
177*b7c941bbSAndroid Build Coastguard Worker      params.append(textwrap.dedent(f"""
178*b7c941bbSAndroid Build Coastguard Worker        {prefix}.gradient_slope = {scale_a[ch]},
179*b7c941bbSAndroid Build Coastguard Worker        {spaces}.offset_slope = {scale_b[ch]},
180*b7c941bbSAndroid Build Coastguard Worker        {spaces}.gradient_intercept = {offset_a[ch]},
181*b7c941bbSAndroid Build Coastguard Worker        {spaces}.offset_intercept = {offset_b[ch]}{suffix}"""))
182*b7c941bbSAndroid Build Coastguard Worker
183*b7c941bbSAndroid Build Coastguard Worker    with open(file_path, 'w') as text_file:
184*b7c941bbSAndroid Build Coastguard Worker      # pylint: disable=line-too-long
185*b7c941bbSAndroid Build Coastguard Worker      code_comment = textwrap.dedent("""\
186*b7c941bbSAndroid Build Coastguard Worker              /* noise_profile.cc
187*b7c941bbSAndroid Build Coastguard Worker                Note: gradient_slope --> gradient of API s_measured parameter
188*b7c941bbSAndroid Build Coastguard Worker                      offset_slope --> o_model of API s_measured parameter
189*b7c941bbSAndroid Build Coastguard Worker                      gradient_intercept--> gradient of API o_measured parameter
190*b7c941bbSAndroid Build Coastguard Worker                      offset_intercept --> o_model of API o_measured parameter
191*b7c941bbSAndroid Build Coastguard Worker                Note: SENSOR_NOISE_PROFILE in Android Developers doc uses
192*b7c941bbSAndroid Build Coastguard Worker                      N(x) = sqrt(Sx + O), where 'S' is 's_measured' & 'O' is 'o_measured'
193*b7c941bbSAndroid Build Coastguard Worker              */
194*b7c941bbSAndroid Build Coastguard Worker      """)
195*b7c941bbSAndroid Build Coastguard Worker      params_str = textwrap.indent(''.join(params), ' ' * 4)
196*b7c941bbSAndroid Build Coastguard Worker      code_params = '.profile = {' + params_str + '},'
197*b7c941bbSAndroid Build Coastguard Worker      code = code_comment + code_params
198*b7c941bbSAndroid Build Coastguard Worker      text_file.write(code)
199*b7c941bbSAndroid Build Coastguard Worker
200*b7c941bbSAndroid Build Coastguard Worker  def _create_noise_model_and_profile_code(self, noise_model, sens_min,
201*b7c941bbSAndroid Build Coastguard Worker                                           sens_max, sens_max_analog, log_path):
202*b7c941bbSAndroid Build Coastguard Worker    """Creates the code file with noise model parameters.
203*b7c941bbSAndroid Build Coastguard Worker
204*b7c941bbSAndroid Build Coastguard Worker    Args:
205*b7c941bbSAndroid Build Coastguard Worker      noise_model: Noise model parameters.
206*b7c941bbSAndroid Build Coastguard Worker      sens_min: The minimum sensitivity value.
207*b7c941bbSAndroid Build Coastguard Worker      sens_max: The maximum sensitivity value.
208*b7c941bbSAndroid Build Coastguard Worker      sens_max_analog: The maximum analog sensitivity value.
209*b7c941bbSAndroid Build Coastguard Worker      log_path: The path to the log file.
210*b7c941bbSAndroid Build Coastguard Worker    """
211*b7c941bbSAndroid Build Coastguard Worker    noise_model_utils.check_noise_model_shape(noise_model)
212*b7c941bbSAndroid Build Coastguard Worker    # Create noise model code with noise model parameters.
213*b7c941bbSAndroid Build Coastguard Worker    self._create_noise_model_code(
214*b7c941bbSAndroid Build Coastguard Worker        noise_model,
215*b7c941bbSAndroid Build Coastguard Worker        sens_min,
216*b7c941bbSAndroid Build Coastguard Worker        sens_max,
217*b7c941bbSAndroid Build Coastguard Worker        sens_max_analog,
218*b7c941bbSAndroid Build Coastguard Worker        os.path.join(log_path, 'noise_model.c'),
219*b7c941bbSAndroid Build Coastguard Worker    )
220*b7c941bbSAndroid Build Coastguard Worker
221*b7c941bbSAndroid Build Coastguard Worker    num_channels = noise_model.shape[0]
222*b7c941bbSAndroid Build Coastguard Worker    is_quad_bayer = (
223*b7c941bbSAndroid Build Coastguard Worker        num_channels == noise_model_constants.NUM_QUAD_BAYER_CHANNELS
224*b7c941bbSAndroid Build Coastguard Worker    )
225*b7c941bbSAndroid Build Coastguard Worker    if is_quad_bayer:
226*b7c941bbSAndroid Build Coastguard Worker      # Average noise model parameters of every four channels.
227*b7c941bbSAndroid Build Coastguard Worker      avg_noise_model = noise_model.reshape(-1, 4, noise_model.shape[1]).mean(
228*b7c941bbSAndroid Build Coastguard Worker          axis=1
229*b7c941bbSAndroid Build Coastguard Worker      )
230*b7c941bbSAndroid Build Coastguard Worker      # Create noise model code with average noise model parameters.
231*b7c941bbSAndroid Build Coastguard Worker      self._create_noise_model_code(
232*b7c941bbSAndroid Build Coastguard Worker          avg_noise_model,
233*b7c941bbSAndroid Build Coastguard Worker          sens_min,
234*b7c941bbSAndroid Build Coastguard Worker          sens_max,
235*b7c941bbSAndroid Build Coastguard Worker          sens_max_analog,
236*b7c941bbSAndroid Build Coastguard Worker          os.path.join(log_path, 'noise_model_avg.c'),
237*b7c941bbSAndroid Build Coastguard Worker      )
238*b7c941bbSAndroid Build Coastguard Worker      # Create noise profile code with average noise model parameters.
239*b7c941bbSAndroid Build Coastguard Worker      self._create_noise_profile_code(
240*b7c941bbSAndroid Build Coastguard Worker          avg_noise_model,
241*b7c941bbSAndroid Build Coastguard Worker          _BAYER_COLORS_FOR_NOISE_PROFILE,
242*b7c941bbSAndroid Build Coastguard Worker          os.path.join(log_path, 'noise_profile_avg.cc'),
243*b7c941bbSAndroid Build Coastguard Worker      )
244*b7c941bbSAndroid Build Coastguard Worker      # Create noise profile code with noise model parameters.
245*b7c941bbSAndroid Build Coastguard Worker      self._create_noise_profile_code(
246*b7c941bbSAndroid Build Coastguard Worker          noise_model,
247*b7c941bbSAndroid Build Coastguard Worker          _QUAD_BAYER_COLORS_FOR_NOISE_PROFILE,
248*b7c941bbSAndroid Build Coastguard Worker          os.path.join(log_path, 'noise_profile.cc'),
249*b7c941bbSAndroid Build Coastguard Worker      )
250*b7c941bbSAndroid Build Coastguard Worker
251*b7c941bbSAndroid Build Coastguard Worker    else:
252*b7c941bbSAndroid Build Coastguard Worker      # Create noise profile code with noise model parameters.
253*b7c941bbSAndroid Build Coastguard Worker      self._create_noise_profile_code(
254*b7c941bbSAndroid Build Coastguard Worker          noise_model,
255*b7c941bbSAndroid Build Coastguard Worker          _BAYER_COLORS_FOR_NOISE_PROFILE,
256*b7c941bbSAndroid Build Coastguard Worker          os.path.join(log_path, 'noise_profile.cc'),
257*b7c941bbSAndroid Build Coastguard Worker      )
258*b7c941bbSAndroid Build Coastguard Worker
259*b7c941bbSAndroid Build Coastguard Worker  def _plot_stats_and_noise_model_fittings(
260*b7c941bbSAndroid Build Coastguard Worker      self, iso_to_stats_dict, measured_models, noise_model, sens_max_analog,
261*b7c941bbSAndroid Build Coastguard Worker      folder_path_prefix):
262*b7c941bbSAndroid Build Coastguard Worker    """Plots the stats (means, vars_) and noise models fittings.
263*b7c941bbSAndroid Build Coastguard Worker
264*b7c941bbSAndroid Build Coastguard Worker    Args:
265*b7c941bbSAndroid Build Coastguard Worker      iso_to_stats_dict: A dictionary mapping ISO to a list of tuples of
266*b7c941bbSAndroid Build Coastguard Worker        exposure time in milliseconds, mean values, and variance values.
267*b7c941bbSAndroid Build Coastguard Worker      measured_models: A list of measured noise models for each ISO value.
268*b7c941bbSAndroid Build Coastguard Worker      noise_model: A numpy array of global noise model parameters for all ISO
269*b7c941bbSAndroid Build Coastguard Worker        values.
270*b7c941bbSAndroid Build Coastguard Worker      sens_max_analog: The maximum analog sensitivity value.
271*b7c941bbSAndroid Build Coastguard Worker      folder_path_prefix: The prefix of path to save figures.
272*b7c941bbSAndroid Build Coastguard Worker
273*b7c941bbSAndroid Build Coastguard Worker    Raises:
274*b7c941bbSAndroid Build Coastguard Worker      ValueError: If the noise model shape is invalid.
275*b7c941bbSAndroid Build Coastguard Worker    """
276*b7c941bbSAndroid Build Coastguard Worker    noise_model_utils.check_noise_model_shape(noise_model)
277*b7c941bbSAndroid Build Coastguard Worker    # Separate individual noise model components.
278*b7c941bbSAndroid Build Coastguard Worker    scale_a, scale_b, offset_a, offset_b = zip(*noise_model)
279*b7c941bbSAndroid Build Coastguard Worker
280*b7c941bbSAndroid Build Coastguard Worker    iso_pidx_to_measured_model_dict = {}
281*b7c941bbSAndroid Build Coastguard Worker    num_channels = noise_model.shape[0]
282*b7c941bbSAndroid Build Coastguard Worker    for pidx in range(num_channels):
283*b7c941bbSAndroid Build Coastguard Worker      for iso, s_measured, o_measured in measured_models[pidx]:
284*b7c941bbSAndroid Build Coastguard Worker        iso_pidx_to_measured_model_dict[(iso, pidx)] = (s_measured, o_measured)
285*b7c941bbSAndroid Build Coastguard Worker
286*b7c941bbSAndroid Build Coastguard Worker    isos = np.asarray(sorted(iso_to_stats_dict.keys()))
287*b7c941bbSAndroid Build Coastguard Worker    digital_gains = noise_model_utils.compute_digital_gains(
288*b7c941bbSAndroid Build Coastguard Worker        isos, sens_max_analog
289*b7c941bbSAndroid Build Coastguard Worker    )
290*b7c941bbSAndroid Build Coastguard Worker
291*b7c941bbSAndroid Build Coastguard Worker    x_range = [0, _MAX_SIGNAL_VALUE]
292*b7c941bbSAndroid Build Coastguard Worker    for iso, digital_gain in zip(isos, digital_gains):
293*b7c941bbSAndroid Build Coastguard Worker      logging.info('Plotting stats and noise model for ISO %d.', iso)
294*b7c941bbSAndroid Build Coastguard Worker      fig, subplots = noise_model_utils.create_stats_figure(
295*b7c941bbSAndroid Build Coastguard Worker          iso, _COLOR_CHANNEL_NAMES
296*b7c941bbSAndroid Build Coastguard Worker      )
297*b7c941bbSAndroid Build Coastguard Worker
298*b7c941bbSAndroid Build Coastguard Worker      xmax = 0
299*b7c941bbSAndroid Build Coastguard Worker      stats_per_plane = [[] for _ in range(num_channels)]
300*b7c941bbSAndroid Build Coastguard Worker      for exposure_ms, means, vars_ in iso_to_stats_dict[iso]:
301*b7c941bbSAndroid Build Coastguard Worker        exposure_norm = noise_model_constants.COLOR_NORM(np.log2(exposure_ms))
302*b7c941bbSAndroid Build Coastguard Worker        exposure_color = noise_model_constants.RAINBOW_CMAP(exposure_norm)
303*b7c941bbSAndroid Build Coastguard Worker        for pidx in range(num_channels):
304*b7c941bbSAndroid Build Coastguard Worker          means_p = means[pidx]
305*b7c941bbSAndroid Build Coastguard Worker          vars_p = vars_[pidx]
306*b7c941bbSAndroid Build Coastguard Worker
307*b7c941bbSAndroid Build Coastguard Worker          if means_p.size > 0 and vars_p.size > 0:
308*b7c941bbSAndroid Build Coastguard Worker            subplots[pidx].plot(
309*b7c941bbSAndroid Build Coastguard Worker                means_p,
310*b7c941bbSAndroid Build Coastguard Worker                vars_p,
311*b7c941bbSAndroid Build Coastguard Worker                color=exposure_color,
312*b7c941bbSAndroid Build Coastguard Worker                marker='.',
313*b7c941bbSAndroid Build Coastguard Worker                markeredgecolor=exposure_color,
314*b7c941bbSAndroid Build Coastguard Worker                markersize=1,
315*b7c941bbSAndroid Build Coastguard Worker                linestyle='None',
316*b7c941bbSAndroid Build Coastguard Worker                alpha=0.5,
317*b7c941bbSAndroid Build Coastguard Worker            )
318*b7c941bbSAndroid Build Coastguard Worker
319*b7c941bbSAndroid Build Coastguard Worker            stats_per_plane[pidx].extend(list(zip(means_p, vars_p)))
320*b7c941bbSAndroid Build Coastguard Worker            xmax = max(xmax, max(means_p))
321*b7c941bbSAndroid Build Coastguard Worker
322*b7c941bbSAndroid Build Coastguard Worker      iso_sq = iso ** 2
323*b7c941bbSAndroid Build Coastguard Worker      digital_gain_sq = digital_gain ** 2
324*b7c941bbSAndroid Build Coastguard Worker      for pidx in range(num_channels):
325*b7c941bbSAndroid Build Coastguard Worker        # Add the final noise model to subplots.
326*b7c941bbSAndroid Build Coastguard Worker        s_model = scale_a[pidx] * iso * digital_gain + scale_b[pidx]
327*b7c941bbSAndroid Build Coastguard Worker        o_model = (offset_a[pidx] * iso_sq + offset_b[pidx]) * digital_gain_sq
328*b7c941bbSAndroid Build Coastguard Worker
329*b7c941bbSAndroid Build Coastguard Worker        plot_color = _PLOT_COLORS[pidx]
330*b7c941bbSAndroid Build Coastguard Worker        subplots[pidx].plot(
331*b7c941bbSAndroid Build Coastguard Worker            x_range,
332*b7c941bbSAndroid Build Coastguard Worker            [o_model, s_model * _MAX_SIGNAL_VALUE + o_model],
333*b7c941bbSAndroid Build Coastguard Worker            color=plot_color,
334*b7c941bbSAndroid Build Coastguard Worker            linestyle='-',
335*b7c941bbSAndroid Build Coastguard Worker            label='Model',
336*b7c941bbSAndroid Build Coastguard Worker            alpha=0.5,
337*b7c941bbSAndroid Build Coastguard Worker        )
338*b7c941bbSAndroid Build Coastguard Worker
339*b7c941bbSAndroid Build Coastguard Worker        # Add the noise model measured by captures with current iso to subplots.
340*b7c941bbSAndroid Build Coastguard Worker        if (iso, pidx) not in iso_pidx_to_measured_model_dict:
341*b7c941bbSAndroid Build Coastguard Worker          continue
342*b7c941bbSAndroid Build Coastguard Worker
343*b7c941bbSAndroid Build Coastguard Worker        s_measured, o_measured = iso_pidx_to_measured_model_dict[(iso, pidx)]
344*b7c941bbSAndroid Build Coastguard Worker
345*b7c941bbSAndroid Build Coastguard Worker        subplots[pidx].plot(
346*b7c941bbSAndroid Build Coastguard Worker            x_range,
347*b7c941bbSAndroid Build Coastguard Worker            [o_measured, s_measured * _MAX_SIGNAL_VALUE + o_measured],
348*b7c941bbSAndroid Build Coastguard Worker            color=plot_color,
349*b7c941bbSAndroid Build Coastguard Worker            linestyle='--',
350*b7c941bbSAndroid Build Coastguard Worker            label='Linear fit',
351*b7c941bbSAndroid Build Coastguard Worker        )
352*b7c941bbSAndroid Build Coastguard Worker
353*b7c941bbSAndroid Build Coastguard Worker        ymax = (o_measured + s_measured * xmax) * _MAX_SCALE_FUDGE
354*b7c941bbSAndroid Build Coastguard Worker        subplots[pidx].set_xlim(xmin=0, xmax=xmax)
355*b7c941bbSAndroid Build Coastguard Worker        subplots[pidx].set_ylim(ymin=0, ymax=ymax)
356*b7c941bbSAndroid Build Coastguard Worker        subplots[pidx].legend()
357*b7c941bbSAndroid Build Coastguard Worker
358*b7c941bbSAndroid Build Coastguard Worker      fig.savefig(
359*b7c941bbSAndroid Build Coastguard Worker          f'{folder_path_prefix}_samples_iso{iso:04d}.png', dpi=_FIG_DPI
360*b7c941bbSAndroid Build Coastguard Worker      )
361*b7c941bbSAndroid Build Coastguard Worker
362*b7c941bbSAndroid Build Coastguard Worker  def _plot_noise_model_single_plane(
363*b7c941bbSAndroid Build Coastguard Worker      self, pidx, plot, sens, measured_params, modeled_params):
364*b7c941bbSAndroid Build Coastguard Worker    """Plots the noise model for one color plane specified by pidx.
365*b7c941bbSAndroid Build Coastguard Worker
366*b7c941bbSAndroid Build Coastguard Worker    Args:
367*b7c941bbSAndroid Build Coastguard Worker      pidx: The index of the color plane in Bayer pattern.
368*b7c941bbSAndroid Build Coastguard Worker      plot: The ax to plot on.
369*b7c941bbSAndroid Build Coastguard Worker      sens: The sensitivity of the sensor.
370*b7c941bbSAndroid Build Coastguard Worker      measured_params:  The measured parameters.
371*b7c941bbSAndroid Build Coastguard Worker      modeled_params: The modeled parameters.
372*b7c941bbSAndroid Build Coastguard Worker    """
373*b7c941bbSAndroid Build Coastguard Worker    color_channel = _COLOR_CHANNEL_NAMES[pidx]
374*b7c941bbSAndroid Build Coastguard Worker    measured_label = f'{color_channel}-Measured'
375*b7c941bbSAndroid Build Coastguard Worker    model_label = f'{color_channel}-Model'
376*b7c941bbSAndroid Build Coastguard Worker
377*b7c941bbSAndroid Build Coastguard Worker    plot_color = _PLOT_COLORS[pidx]
378*b7c941bbSAndroid Build Coastguard Worker    # Plot the measured parameters.
379*b7c941bbSAndroid Build Coastguard Worker    plot.loglog(
380*b7c941bbSAndroid Build Coastguard Worker        sens,
381*b7c941bbSAndroid Build Coastguard Worker        measured_params,
382*b7c941bbSAndroid Build Coastguard Worker        color=plot_color,
383*b7c941bbSAndroid Build Coastguard Worker        marker='+',
384*b7c941bbSAndroid Build Coastguard Worker        markeredgecolor=plot_color,
385*b7c941bbSAndroid Build Coastguard Worker        linestyle='None',
386*b7c941bbSAndroid Build Coastguard Worker        base=10,
387*b7c941bbSAndroid Build Coastguard Worker        label=measured_label,
388*b7c941bbSAndroid Build Coastguard Worker    )
389*b7c941bbSAndroid Build Coastguard Worker    # Plot the modeled parameters.
390*b7c941bbSAndroid Build Coastguard Worker    plot.loglog(
391*b7c941bbSAndroid Build Coastguard Worker        sens,
392*b7c941bbSAndroid Build Coastguard Worker        modeled_params,
393*b7c941bbSAndroid Build Coastguard Worker        color=plot_color,
394*b7c941bbSAndroid Build Coastguard Worker        marker='o',
395*b7c941bbSAndroid Build Coastguard Worker        markeredgecolor=plot_color,
396*b7c941bbSAndroid Build Coastguard Worker        linestyle='None',
397*b7c941bbSAndroid Build Coastguard Worker        base=10,
398*b7c941bbSAndroid Build Coastguard Worker        label=model_label,
399*b7c941bbSAndroid Build Coastguard Worker        alpha=0.3,
400*b7c941bbSAndroid Build Coastguard Worker    )
401*b7c941bbSAndroid Build Coastguard Worker
402*b7c941bbSAndroid Build Coastguard Worker  def _plot_noise_model(self, isos, measured_models, noise_model,
403*b7c941bbSAndroid Build Coastguard Worker                        sens_max_analog, name_with_log_path):
404*b7c941bbSAndroid Build Coastguard Worker    """Plot the noise model for a given set of ISO values.
405*b7c941bbSAndroid Build Coastguard Worker
406*b7c941bbSAndroid Build Coastguard Worker    The read noise model is defined by the following equation:
407*b7c941bbSAndroid Build Coastguard Worker      f(x) = s_model * x + o_model
408*b7c941bbSAndroid Build Coastguard Worker    where we have:
409*b7c941bbSAndroid Build Coastguard Worker    s_model = scale_a * analog_gain * digital_gain + scale_b is the
410*b7c941bbSAndroid Build Coastguard Worker    multiplicative factor,
411*b7c941bbSAndroid Build Coastguard Worker    o_model = (offset_a * analog_gain^2 + offset_b) * digital_gain^2
412*b7c941bbSAndroid Build Coastguard Worker    is the offset term.
413*b7c941bbSAndroid Build Coastguard Worker
414*b7c941bbSAndroid Build Coastguard Worker    Args:
415*b7c941bbSAndroid Build Coastguard Worker      isos: A list of ISO values.
416*b7c941bbSAndroid Build Coastguard Worker      measured_models: A list of measured models, each of which is a tuple of
417*b7c941bbSAndroid Build Coastguard Worker        (sens, s_measured, o_measured).
418*b7c941bbSAndroid Build Coastguard Worker      noise_model: Noise model parameters of each plane, each of which is a
419*b7c941bbSAndroid Build Coastguard Worker        tuple of (scale_a, scale_b, offset_a, offset_b).
420*b7c941bbSAndroid Build Coastguard Worker      sens_max_analog: The maximum analog gain.
421*b7c941bbSAndroid Build Coastguard Worker      name_with_log_path: The name of the file to save the logs to.
422*b7c941bbSAndroid Build Coastguard Worker    """
423*b7c941bbSAndroid Build Coastguard Worker    noise_model_utils.check_noise_model_shape(noise_model)
424*b7c941bbSAndroid Build Coastguard Worker
425*b7c941bbSAndroid Build Coastguard Worker    # Plot noise model parameters.
426*b7c941bbSAndroid Build Coastguard Worker    fig, axes = plt.subplots(4, 2, figsize=(22, 17))
427*b7c941bbSAndroid Build Coastguard Worker    s_plots, o_plots = axes[:, 0], axes[:, 1]
428*b7c941bbSAndroid Build Coastguard Worker    num_channels = noise_model.shape[0]
429*b7c941bbSAndroid Build Coastguard Worker    is_quad_bayer = (
430*b7c941bbSAndroid Build Coastguard Worker        num_channels == noise_model_constants.NUM_QUAD_BAYER_CHANNELS
431*b7c941bbSAndroid Build Coastguard Worker    )
432*b7c941bbSAndroid Build Coastguard Worker    for pidx, measured_model in enumerate(measured_models):
433*b7c941bbSAndroid Build Coastguard Worker      # Grab the sensitivities and line parameters of each sensitivity.
434*b7c941bbSAndroid Build Coastguard Worker      sens, s_measured, o_measured = zip(*measured_model)
435*b7c941bbSAndroid Build Coastguard Worker      sens = np.asarray(sens)
436*b7c941bbSAndroid Build Coastguard Worker      sens_sq = np.square(sens)
437*b7c941bbSAndroid Build Coastguard Worker      scale_a, scale_b, offset_a, offset_b = noise_model[pidx]
438*b7c941bbSAndroid Build Coastguard Worker      # Plot noise model components with the values predicted by the model.
439*b7c941bbSAndroid Build Coastguard Worker      digital_gains = noise_model_utils.compute_digital_gains(
440*b7c941bbSAndroid Build Coastguard Worker          sens, sens_max_analog
441*b7c941bbSAndroid Build Coastguard Worker      )
442*b7c941bbSAndroid Build Coastguard Worker
443*b7c941bbSAndroid Build Coastguard Worker      # s_model = scale_a * analog_gain * digital_gain + scale_b,
444*b7c941bbSAndroid Build Coastguard Worker      # o_model = (offset_a * analog_gain^2 + offset_b) * digital_gain^2.
445*b7c941bbSAndroid Build Coastguard Worker      s_model = scale_a * sens * digital_gains + scale_b
446*b7c941bbSAndroid Build Coastguard Worker      o_model = (offset_a * sens_sq + offset_b) * np.square(digital_gains)
447*b7c941bbSAndroid Build Coastguard Worker      if is_quad_bayer:
448*b7c941bbSAndroid Build Coastguard Worker        s_plot, o_plot = s_plots[pidx // 4], o_plots[pidx // 4]
449*b7c941bbSAndroid Build Coastguard Worker      else:
450*b7c941bbSAndroid Build Coastguard Worker        s_plot, o_plot = s_plots[pidx], o_plots[pidx]
451*b7c941bbSAndroid Build Coastguard Worker
452*b7c941bbSAndroid Build Coastguard Worker      self._plot_noise_model_single_plane(
453*b7c941bbSAndroid Build Coastguard Worker          pidx, s_plot, sens, s_measured, s_model)
454*b7c941bbSAndroid Build Coastguard Worker      self._plot_noise_model_single_plane(
455*b7c941bbSAndroid Build Coastguard Worker          pidx, o_plot, sens, o_measured, o_model)
456*b7c941bbSAndroid Build Coastguard Worker
457*b7c941bbSAndroid Build Coastguard Worker    # Set figure attributes after plotting noise model parameters.
458*b7c941bbSAndroid Build Coastguard Worker    for s_plot, o_plot in zip(s_plots, o_plots):
459*b7c941bbSAndroid Build Coastguard Worker      s_plot.set_xlabel('ISO')
460*b7c941bbSAndroid Build Coastguard Worker      s_plot.set_ylabel('S')
461*b7c941bbSAndroid Build Coastguard Worker
462*b7c941bbSAndroid Build Coastguard Worker      o_plot.set_xlabel('ISO')
463*b7c941bbSAndroid Build Coastguard Worker      o_plot.set_ylabel('O')
464*b7c941bbSAndroid Build Coastguard Worker
465*b7c941bbSAndroid Build Coastguard Worker      for sub_plot in (s_plot, o_plot):
466*b7c941bbSAndroid Build Coastguard Worker        sub_plot.set_xticks(isos)
467*b7c941bbSAndroid Build Coastguard Worker        # No minor ticks.
468*b7c941bbSAndroid Build Coastguard Worker        sub_plot.xaxis.set_minor_locator(matplotlib.ticker.NullLocator())
469*b7c941bbSAndroid Build Coastguard Worker        sub_plot.xaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
470*b7c941bbSAndroid Build Coastguard Worker        sub_plot.legend()
471*b7c941bbSAndroid Build Coastguard Worker
472*b7c941bbSAndroid Build Coastguard Worker    fig.suptitle('Noise model: N(x) = sqrt(Sx + O)', x=0.54, y=0.99)
473*b7c941bbSAndroid Build Coastguard Worker    plt.tight_layout()
474*b7c941bbSAndroid Build Coastguard Worker    fig.savefig(f'{name_with_log_path}.png', dpi=_FIG_DPI)
475*b7c941bbSAndroid Build Coastguard Worker
476*b7c941bbSAndroid Build Coastguard Worker  def test_dng_noise_model_generation(self):
477*b7c941bbSAndroid Build Coastguard Worker    """Calibrates standard Bayer or quad Bayer noise model.
478*b7c941bbSAndroid Build Coastguard Worker
479*b7c941bbSAndroid Build Coastguard Worker    def requires 'test' in name to actually run.
480*b7c941bbSAndroid Build Coastguard Worker    This function:
481*b7c941bbSAndroid Build Coastguard Worker    * Calibrates read noise (optional).
482*b7c941bbSAndroid Build Coastguard Worker    * Captures stats images of different ISO values and exposure times.
483*b7c941bbSAndroid Build Coastguard Worker    * Measures linear fittings for each ISO value.
484*b7c941bbSAndroid Build Coastguard Worker    * Computes and validates overall noise model parameters.
485*b7c941bbSAndroid Build Coastguard Worker    * Plots noise model parameters figures.
486*b7c941bbSAndroid Build Coastguard Worker    * Plots stats samples, linear fittings and model fittings.
487*b7c941bbSAndroid Build Coastguard Worker    * Saves the read noise plot and csv data (optional).
488*b7c941bbSAndroid Build Coastguard Worker    * Generates noise model and noise profile code.
489*b7c941bbSAndroid Build Coastguard Worker    """
490*b7c941bbSAndroid Build Coastguard Worker    read_noise_file_path = capture_read_noise_utils.calibrate_read_noise(
491*b7c941bbSAndroid Build Coastguard Worker        self.dut.serial,
492*b7c941bbSAndroid Build Coastguard Worker        self.camera_id,
493*b7c941bbSAndroid Build Coastguard Worker        self.hidden_physical_id,
494*b7c941bbSAndroid Build Coastguard Worker        _NAME_READ_NOISE,
495*b7c941bbSAndroid Build Coastguard Worker        _NAME_READ_NOISE_FILE,
496*b7c941bbSAndroid Build Coastguard Worker        _READ_NOISE_STEPS_PER_STOP,
497*b7c941bbSAndroid Build Coastguard Worker        raw_format=_READ_NOISE_RAW_FORMAT,
498*b7c941bbSAndroid Build Coastguard Worker        is_two_stage_model=_TWO_STAGE_MODEL,
499*b7c941bbSAndroid Build Coastguard Worker    )
500*b7c941bbSAndroid Build Coastguard Worker
501*b7c941bbSAndroid Build Coastguard Worker    # Begin DNG Noise Model Calibration
502*b7c941bbSAndroid Build Coastguard Worker    with its_session_utils.ItsSession(
503*b7c941bbSAndroid Build Coastguard Worker        device_id=self.dut.serial,
504*b7c941bbSAndroid Build Coastguard Worker        camera_id=self.camera_id,
505*b7c941bbSAndroid Build Coastguard Worker        hidden_physical_id=self.hidden_physical_id) as cam:
506*b7c941bbSAndroid Build Coastguard Worker      props = cam.get_camera_properties()
507*b7c941bbSAndroid Build Coastguard Worker      props = cam.override_with_hidden_physical_camera_props(props)
508*b7c941bbSAndroid Build Coastguard Worker      log_path = self.log_path
509*b7c941bbSAndroid Build Coastguard Worker      name_with_log_path = os.path.join(log_path, _NAME)
510*b7c941bbSAndroid Build Coastguard Worker      logging.info('Starting %s for camera %s', _NAME, cam.get_camera_name())
511*b7c941bbSAndroid Build Coastguard Worker
512*b7c941bbSAndroid Build Coastguard Worker      # Get basic properties we need.
513*b7c941bbSAndroid Build Coastguard Worker      sens_min, sens_max = props['android.sensor.info.sensitivityRange']
514*b7c941bbSAndroid Build Coastguard Worker      sens_max_analog = props['android.sensor.maxAnalogSensitivity']
515*b7c941bbSAndroid Build Coastguard Worker      # Maximum sensitivity for measuring noise model.
516*b7c941bbSAndroid Build Coastguard Worker      sens_max_meas = sens_max_analog
517*b7c941bbSAndroid Build Coastguard Worker
518*b7c941bbSAndroid Build Coastguard Worker      # Change the ISO min and/or max values if specified
519*b7c941bbSAndroid Build Coastguard Worker      if _ISO_MIN_VALUE is not None:
520*b7c941bbSAndroid Build Coastguard Worker        sens_min = _ISO_MIN_VALUE
521*b7c941bbSAndroid Build Coastguard Worker      if _ISO_MAX_VALUE is not None:
522*b7c941bbSAndroid Build Coastguard Worker        sens_max_meas = _ISO_MAX_VALUE
523*b7c941bbSAndroid Build Coastguard Worker
524*b7c941bbSAndroid Build Coastguard Worker      logging.info('Sensitivity range: [%d, %d]', sens_min, sens_max)
525*b7c941bbSAndroid Build Coastguard Worker      logging.info('Max analog sensitivity: %d', sens_max_analog)
526*b7c941bbSAndroid Build Coastguard Worker      logging.info(
527*b7c941bbSAndroid Build Coastguard Worker          'Sensitivity range for measurement: [%d, %d]',
528*b7c941bbSAndroid Build Coastguard Worker          sens_min, sens_max_meas,
529*b7c941bbSAndroid Build Coastguard Worker      )
530*b7c941bbSAndroid Build Coastguard Worker
531*b7c941bbSAndroid Build Coastguard Worker      offset_a, offset_b = None, None
532*b7c941bbSAndroid Build Coastguard Worker      read_noise_data = None
533*b7c941bbSAndroid Build Coastguard Worker      if _TWO_STAGE_MODEL:
534*b7c941bbSAndroid Build Coastguard Worker        # Check if read noise results exist for this device and camera
535*b7c941bbSAndroid Build Coastguard Worker        if not os.path.exists(read_noise_file_path):
536*b7c941bbSAndroid Build Coastguard Worker          raise AssertionError(
537*b7c941bbSAndroid Build Coastguard Worker              'Read noise results file does not exist for this device. Run'
538*b7c941bbSAndroid Build Coastguard Worker              ' capture_read_noise_file_path script to gather read noise data'
539*b7c941bbSAndroid Build Coastguard Worker              ' for current sensor'
540*b7c941bbSAndroid Build Coastguard Worker          )
541*b7c941bbSAndroid Build Coastguard Worker
542*b7c941bbSAndroid Build Coastguard Worker        with open(read_noise_file_path, 'rb') as f:
543*b7c941bbSAndroid Build Coastguard Worker          read_noise_data = pickle.load(f)
544*b7c941bbSAndroid Build Coastguard Worker
545*b7c941bbSAndroid Build Coastguard Worker        offset_a, offset_b = (
546*b7c941bbSAndroid Build Coastguard Worker            capture_read_noise_utils.get_read_noise_coefficients(
547*b7c941bbSAndroid Build Coastguard Worker                read_noise_data,
548*b7c941bbSAndroid Build Coastguard Worker                sens_min,
549*b7c941bbSAndroid Build Coastguard Worker                sens_max_meas,
550*b7c941bbSAndroid Build Coastguard Worker            )
551*b7c941bbSAndroid Build Coastguard Worker        )
552*b7c941bbSAndroid Build Coastguard Worker
553*b7c941bbSAndroid Build Coastguard Worker      iso_to_stats_dict = noise_model_utils.capture_stats_images(
554*b7c941bbSAndroid Build Coastguard Worker          cam,
555*b7c941bbSAndroid Build Coastguard Worker          props,
556*b7c941bbSAndroid Build Coastguard Worker          _STATS_CONFIG,
557*b7c941bbSAndroid Build Coastguard Worker          sens_min,
558*b7c941bbSAndroid Build Coastguard Worker          sens_max_meas,
559*b7c941bbSAndroid Build Coastguard Worker          _ZOOM_RATIO,
560*b7c941bbSAndroid Build Coastguard Worker          _TILE_CROP_N,
561*b7c941bbSAndroid Build Coastguard Worker          _MAX_SIGNAL_VALUE,
562*b7c941bbSAndroid Build Coastguard Worker          _ISO_MULTIPLIER,
563*b7c941bbSAndroid Build Coastguard Worker          _BRACKET_MAX,
564*b7c941bbSAndroid Build Coastguard Worker          _BRACKET_FACTOR,
565*b7c941bbSAndroid Build Coastguard Worker          self.log_path,
566*b7c941bbSAndroid Build Coastguard Worker          stats_file_name=_STATS_FILE_NAME,
567*b7c941bbSAndroid Build Coastguard Worker          is_remove_var_outliers=_REMOVE_VAR_OUTLIERS,
568*b7c941bbSAndroid Build Coastguard Worker          outlier_median_abs_deviations=_OUTLIER_MEDIAN_ABS_DEVS,
569*b7c941bbSAndroid Build Coastguard Worker          is_debug_mode=self.debug_mode,
570*b7c941bbSAndroid Build Coastguard Worker      )
571*b7c941bbSAndroid Build Coastguard Worker
572*b7c941bbSAndroid Build Coastguard Worker    measured_models, samples = noise_model_utils.measure_linear_noise_models(
573*b7c941bbSAndroid Build Coastguard Worker        iso_to_stats_dict,
574*b7c941bbSAndroid Build Coastguard Worker        _COLOR_CHANNEL_NAMES,
575*b7c941bbSAndroid Build Coastguard Worker    )
576*b7c941bbSAndroid Build Coastguard Worker
577*b7c941bbSAndroid Build Coastguard Worker    noise_model = noise_model_utils.compute_noise_model(
578*b7c941bbSAndroid Build Coastguard Worker        samples,
579*b7c941bbSAndroid Build Coastguard Worker        sens_max_analog,
580*b7c941bbSAndroid Build Coastguard Worker        offset_a,
581*b7c941bbSAndroid Build Coastguard Worker        offset_b,
582*b7c941bbSAndroid Build Coastguard Worker        _TWO_STAGE_MODEL,
583*b7c941bbSAndroid Build Coastguard Worker    )
584*b7c941bbSAndroid Build Coastguard Worker
585*b7c941bbSAndroid Build Coastguard Worker    noise_model_utils.validate_noise_model(
586*b7c941bbSAndroid Build Coastguard Worker        noise_model,
587*b7c941bbSAndroid Build Coastguard Worker        _COLOR_CHANNEL_NAMES,
588*b7c941bbSAndroid Build Coastguard Worker        sens_min,
589*b7c941bbSAndroid Build Coastguard Worker    )
590*b7c941bbSAndroid Build Coastguard Worker
591*b7c941bbSAndroid Build Coastguard Worker    self._plot_noise_model(
592*b7c941bbSAndroid Build Coastguard Worker        sorted(iso_to_stats_dict.keys()),
593*b7c941bbSAndroid Build Coastguard Worker        measured_models,
594*b7c941bbSAndroid Build Coastguard Worker        noise_model,
595*b7c941bbSAndroid Build Coastguard Worker        sens_max_analog,
596*b7c941bbSAndroid Build Coastguard Worker        name_with_log_path,
597*b7c941bbSAndroid Build Coastguard Worker    )
598*b7c941bbSAndroid Build Coastguard Worker
599*b7c941bbSAndroid Build Coastguard Worker    self._plot_stats_and_noise_model_fittings(
600*b7c941bbSAndroid Build Coastguard Worker        iso_to_stats_dict,
601*b7c941bbSAndroid Build Coastguard Worker        measured_models,
602*b7c941bbSAndroid Build Coastguard Worker        noise_model,
603*b7c941bbSAndroid Build Coastguard Worker        sens_max_analog,
604*b7c941bbSAndroid Build Coastguard Worker        name_with_log_path,
605*b7c941bbSAndroid Build Coastguard Worker    )
606*b7c941bbSAndroid Build Coastguard Worker
607*b7c941bbSAndroid Build Coastguard Worker    # If 2-Stage model is enabled, save the read noise graph and csv data
608*b7c941bbSAndroid Build Coastguard Worker    if _TWO_STAGE_MODEL:
609*b7c941bbSAndroid Build Coastguard Worker      # Save the linear plot of the read noise data
610*b7c941bbSAndroid Build Coastguard Worker      filename = f'{pathlib.Path(_NAME_READ_NOISE_FILE).stem}.png'
611*b7c941bbSAndroid Build Coastguard Worker      file_path = os.path.join(log_path, filename)
612*b7c941bbSAndroid Build Coastguard Worker      capture_read_noise_utils.plot_read_noise_data(
613*b7c941bbSAndroid Build Coastguard Worker          read_noise_data,
614*b7c941bbSAndroid Build Coastguard Worker          sens_min,
615*b7c941bbSAndroid Build Coastguard Worker          sens_max_meas,
616*b7c941bbSAndroid Build Coastguard Worker          file_path,
617*b7c941bbSAndroid Build Coastguard Worker          _COLOR_CHANNEL_NAMES,
618*b7c941bbSAndroid Build Coastguard Worker          _PLOT_COLORS,
619*b7c941bbSAndroid Build Coastguard Worker      )
620*b7c941bbSAndroid Build Coastguard Worker
621*b7c941bbSAndroid Build Coastguard Worker      # Save the data as a csv file
622*b7c941bbSAndroid Build Coastguard Worker      filename = f'{pathlib.Path(_NAME_READ_NOISE_FILE).stem}.csv'
623*b7c941bbSAndroid Build Coastguard Worker      file_path = os.path.join(log_path, filename)
624*b7c941bbSAndroid Build Coastguard Worker      capture_read_noise_utils.save_read_noise_data_as_csv(
625*b7c941bbSAndroid Build Coastguard Worker          read_noise_data,
626*b7c941bbSAndroid Build Coastguard Worker          sens_min,
627*b7c941bbSAndroid Build Coastguard Worker          sens_max_meas,
628*b7c941bbSAndroid Build Coastguard Worker          file_path,
629*b7c941bbSAndroid Build Coastguard Worker          _COLOR_CHANNEL_NAMES,
630*b7c941bbSAndroid Build Coastguard Worker      )
631*b7c941bbSAndroid Build Coastguard Worker
632*b7c941bbSAndroid Build Coastguard Worker    # Generate the noise model file.
633*b7c941bbSAndroid Build Coastguard Worker    self._create_noise_model_and_profile_code(
634*b7c941bbSAndroid Build Coastguard Worker        noise_model,
635*b7c941bbSAndroid Build Coastguard Worker        sens_min,
636*b7c941bbSAndroid Build Coastguard Worker        sens_max,
637*b7c941bbSAndroid Build Coastguard Worker        sens_max_analog,
638*b7c941bbSAndroid Build Coastguard Worker        log_path,
639*b7c941bbSAndroid Build Coastguard Worker    )
640*b7c941bbSAndroid Build Coastguard Worker
641*b7c941bbSAndroid Build Coastguard Worker
642*b7c941bbSAndroid Build Coastguard Workerif __name__ == '__main__':
643*b7c941bbSAndroid Build Coastguard Worker  test_runner.main()
644