ITS: create noise_profile.cc for tools/dng_noise_model.py

bug: 192413663
Change-Id: Ib129a06551128197dab98ceb115eb9398d9b8d48
diff --git a/apps/CameraITS/tools/dng_noise_model.py b/apps/CameraITS/tools/dng_noise_model.py
index a70ef8f..080c4e1 100644
--- a/apps/CameraITS/tools/dng_noise_model.py
+++ b/apps/CameraITS/tools/dng_noise_model.py
@@ -111,6 +111,38 @@
   text_file.write('%s' % code)
   text_file.close()
 
+  # Creates the noise profile C++ file
+  code = textwrap.dedent(f"""\
+          /* noise_profile.cc
+             Note: gradient_slope --> gradient of API slope parameter
+                   offset_slope --> offset of API slope parameter
+                   gradient_intercept--> gradient of API intercept parameter
+                   offset_intercept --> offset of API intercept parameter
+             Note: SENSOR_NOISE_PROFILE in Android Developers doc uses
+                   N(x) = sqrt(Sx + O), where 'S' is 'slope' & 'O' is 'intercept'
+          */
+          .noise_profile =
+              {{.noise_coefficients_r = {{.gradient_slope = {noise_model_a[0]},
+                                        .offset_slope = {noise_model_b[0]},
+                                        .gradient_intercept = {noise_model_c[0]},
+                                        .offset_intercept = {noise_model_d[0]}}},
+               .noise_coefficients_gr = {{.gradient_slope = {noise_model_a[1]},
+                                         .offset_slope = {noise_model_b[1]},
+                                         .gradient_intercept = {noise_model_c[1]},
+                                         .offset_intercept = {noise_model_d[1]}}},
+               .noise_coefficients_gb = {{.gradient_slope = {noise_model_a[2]},
+                                         .offset_slope = {noise_model_b[2]},
+                                         .gradient_intercept = {noise_model_c[2]},
+                                         .offset_intercept = {noise_model_d[2]}}},
+               .noise_coefficients_b = {{.gradient_slope = {noise_model_a[3]},
+                                        .offset_slope = {noise_model_b[3]},
+                                        .gradient_intercept = {noise_model_c[3]},
+                                        .offset_intercept = {noise_model_d[3]}}}}},
+          """)
+  text_file = open(os.path.join(log_path, 'noise_profile.cc'), 'w')
+  text_file.write('%s' % code)
+  text_file.close()
+
 
 class DngNoiseModel(its_base_test.ItsBaseTest):
   """Create DNG noise model.
@@ -279,17 +311,17 @@
         iso *= math.pow(2, 1.0/_STEPS_PER_STOP)
 
     # Do model plots
-    (fig, (plt_slope, plt_intercept)) = plt.subplots(2, 1, figsize=(11, 8.5))
-    plt_slope.set_title('Noise model')
-    plt_slope.set_ylabel('Slope')
-    plt_intercept.set_xlabel('ISO')
-    plt_intercept.set_ylabel('Intercept')
+    (fig, (plt_s, plt_o)) = plt.subplots(2, 1, figsize=(11, 8.5))
+    plt_s.set_title('Noise model: N(x) = sqrt(Sx + O)')
+    plt_s.set_ylabel('S')
+    plt_o.set_xlabel('ISO')
+    plt_o.set_ylabel('O')
 
     noise_model = []
     for (pidx, p) in enumerate(measured_models):
       # Grab the sensitivities and line parameters from each sensitivity.
-      slp_measured = [e[1] for e in measured_models[pidx]]
-      int_measured = [e[2] for e in measured_models[pidx]]
+      s_measured = [e[1] for e in measured_models[pidx]]
+      o_measured = [e[2] for e in measured_models[pidx]]
       sens = np.asarray([e[0] for e in measured_models[pidx]])
       sens_sq = np.square(sens)
 
@@ -314,30 +346,34 @@
       # Divide the whole system by gains*means.
       f = lambda x, a, b, c, d: (c*(x[0]**2)+d+(x[1])*a*x[0]+(x[1])*b)/(x[0])
       result, _ = scipy.optimize.curve_fit(f, (gains, means), vars_/(gains))
-
-      a_p, b_p, c_p, d_p = result[0:4]
+      # result[0:4] = s_gradient, s_offset, o_gradient, o_offset
+      # Note 'S' and 'O' are the API terms for the 2 model params.
+      # The noise_profile.cc uses 'slope' for 'S' and 'intercept' for 'O'.
+      # 'gradient' and 'offset' are used to describe the linear fit
+      # parameters for 'S' and 'O'.
       noise_model.append(result[0:4])
 
       # Plot noise model components with the values predicted by the model.
-      slp_model = result[0]*sens + result[1]
-      int_model = result[2]*sens_sq + result[3]*np.square(np.maximum(
+      s_model = result[0]*sens + result[1]
+      o_model = result[2]*sens_sq + result[3]*np.square(np.maximum(
           sens/sens_max_analog, 1))
 
-      plt_slope.loglog(sens, slp_measured, 'rgkb'[pidx]+'+', base=10,
-                       label='Measured')
-      plt_slope.loglog(sens, slp_model, 'rgkb'[pidx]+'o', base=10,
-                       label='Model', alpha=0.3)
-      plt_intercept.loglog(sens, int_measured, 'rgkb'[pidx]+'+', base=10,
-                           label='Measured')
-      plt_intercept.loglog(sens, int_model, 'rgkb'[pidx]+'o', base=10,
-                           label='Model', alpha=0.3)
-    plt_slope.legend()
-    plt_slope.set_xticks(isos)
-    plt_slope.set_xticklabels(isos)
+      plt_s.loglog(sens, s_measured, 'rgkb'[pidx]+'+', base=10,
+                   label='Measured')
+      plt_s.loglog(sens, s_model, 'rgkb'[pidx]+'o', base=10,
+                   label='Model', alpha=0.3)
+      plt_o.loglog(sens, o_measured, 'rgkb'[pidx]+'+', base=10,
+                   label='Measured')
+      plt_o.loglog(sens, o_model, 'rgkb'[pidx]+'o', base=10,
+                   label='Model', alpha=0.3)
+    plt_s.legend()
+    plt_s.set_xticks(isos)
+    plt_s.set_xticklabels(isos)
 
-    plt_intercept.set_xticks(isos)
-    plt_intercept.set_xticklabels(isos)
-    plt_intercept.legend()
+    plt_o.set_xticks([])
+    plt_o.set_xticks(isos)
+    plt_o.set_xticklabels(isos)
+    plt_o.legend()
     fig.savefig(f'{name_with_log_path}.png')
 
     # Generate individual noise model components
@@ -345,30 +381,29 @@
         *noise_model)
 
     # Add models to subplots and re-save
-    for [s, fig] in plots:  # re-step through figs...
-      dig_gain = max(s/sens_max_analog, 1)
+    for [iso, fig] in plots:  # re-step through figs...
+      dig_gain = max(iso/sens_max_analog, 1)
       fig.gca()
       for (pidx, p) in enumerate(measured_models):
-        slope = noise_model_a[pidx]*s + noise_model_b[pidx]
-        intercept = noise_model_c[pidx]*s**2 + noise_model_d[pidx]*dig_gain**2
-        color_plane_plots[s][pidx].plot(
-            [0, _MAX_SIGNAL_VALUE],
-            [intercept, intercept+slope*_MAX_SIGNAL_VALUE],
+        s = noise_model_a[pidx]*iso + noise_model_b[pidx]
+        o = noise_model_c[pidx]*iso**2 + noise_model_d[pidx]*dig_gain**2
+        color_plane_plots[iso][pidx].plot(
+            [0, _MAX_SIGNAL_VALUE], [o, o+s*_MAX_SIGNAL_VALUE],
             'rgkb'[pidx]+'-', label='Model', alpha=0.5)
-        color_plane_plots[s][pidx].legend(loc='upper left')
-      fig.savefig(f'{name_with_log_path}_samples_iso{s:04d}.png')
+        color_plane_plots[iso][pidx].legend(loc='upper left')
+      fig.savefig(f'{name_with_log_path}_samples_iso{iso:04d}.png')
 
-    # Validity checks on model: read noise > 0, positive slope.
+    # Validity checks on model: read noise > 0, positive intercept gradient.
     for i, _ in enumerate(_BAYER_LIST):
       read_noise = noise_model_c[i] * sens_min * sens_min + noise_model_d[i]
       if read_noise <= 0:
         raise AssertionError(f'{_BAYER_LIST[i]} model min ISO noise < 0! '
-                             f'C: {noise_model_c[i]:.4e}, '
-                             f'D: {noise_model_d[i]:.4e}, '
+                             f'API intercept gradient: {noise_model_c[i]:.4e}, '
+                             f'API intercept offset: {noise_model_d[i]:.4e}, '
                              f'read_noise: {read_noise:.4e}')
       if noise_model_c[i] <= 0:
-        raise AssertionError(f'{_BAYER_LIST[i]} model slope is negative. '
-                             f' slope={noise_model_c[i]:.4e}')
+        raise AssertionError(f'{_BAYER_LIST[i]} model API intercept gradient '
+                             f'is negative: {noise_model_c[i]:.4e}')
 
     # Generate the noise model file.
     create_noise_model_code(