CameraITS: Add reprocess noise reduction test

Add doReprocessCapture() to submit regular capture requests and use
the regular capture requests as the input for submitting reprocess
capture requests.

Add a test to verify noise reduction modes work as expected for
reprocess requests.

Bug: 20920487
Change-Id: I8c28ca103991c4b84d081c182d81e548c9c3b2e1
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index 70bb2ca..b6d398f 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -283,6 +283,30 @@
             "android.shading.availableModes") and \
         0 in props["android.shading.availableModes"]
 
+def yuv_reprocess(props):
+    """Returns whether a device supports YUV reprocessing.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.request.availableCapabilities") and \
+           7 in props["android.request.availableCapabilities"]
+
+def private_reprocess(props):
+    """Returns whether a device supports PRIVATE reprocessing.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.request.availableCapabilities") and \
+           4 in props["android.request.availableCapabilities"]
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index e396483..8773335 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -359,7 +359,7 @@
             raise its.error.Error('3A failed to converge')
         return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
 
-    def do_capture(self, cap_request, out_surfaces=None):
+    def do_capture(self, cap_request, out_surfaces=None, reprocess_format=None):
         """Issue capture request(s), and read back the image(s) and metadata.
 
         The main top-level function for capturing one or more images using the
@@ -378,6 +378,18 @@
         surface. At most one output surface can be specified for a given format,
         and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
 
+        If reprocess_format is not None, for each request, an intermediate
+        buffer of the given reprocess_format will be captured from camera and
+        the intermediate buffer will be reprocessed to the output surfaces. The
+        following settings will be turned off when capturing the intermediate
+        buffer and will be applied when reprocessing the intermediate buffer.
+            1. android.noiseReduction.mode
+            2. android.edge.mode
+            3. android.reprocess.effectiveExposureFactor
+
+        Supported reprocess format are "yuv" and "private". Supported output
+        surface formats when reprocessing is enabled are "yuv" and "jpeg".
+
         Example of a single capture request:
 
             {
@@ -449,6 +461,8 @@
                 will be converted to JSON and sent to the device.
             out_surfaces: (Optional) specifications of the output image formats
                 and sizes to use for each capture.
+            reprocess_format: (Optional) The reprocessing format. If not None,
+                reprocessing will be enabled.
 
         Returns:
             An object, list of objects, or list of lists of objects, where each
@@ -460,7 +474,11 @@
             * metadata: the capture result object (Python dictionary).
         """
         cmd = {}
-        cmd["cmdName"] = "doCapture"
+        if reprocess_format != None:
+            cmd["cmdName"] = "doReprocessCapture"
+            cmd["reprocessFormat"] = reprocess_format
+        else:
+            cmd["cmdName"] = "doCapture"
         if not isinstance(cap_request, list):
             cmd["captureRequests"] = [cap_request]
         else:
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
new file mode 100644
index 0000000..e9240ba
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -0,0 +1,112 @@
+# Copyright 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import math
+import matplotlib
+import matplotlib.pyplot
+import os.path
+import pylab
+
+def main():
+    """Test that the android.noiseReduction.mode param is applied when set for
+       reprocessing requests.
+
+    Capture reprocessed images with the camera dimly lit. Uses a high analog
+    gain to ensure the captured image is noisy.
+
+    Captures three reprocessed images, for NR off, "fast", and "high quality".
+    Also captures a reprocessed image with low gain and NR off, and uses the
+    variance of this as the baseline.
+    """
+
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props) and
+                             (its.caps.yuv_reprocess(props) or
+                              its.caps.private_reprocess(props)))
+
+        reprocess_formats = []
+        if (its.caps.yuv_reprocess(props)):
+            reprocess_formats.append("yuv")
+        if (its.caps.private_reprocess(props)):
+            reprocess_formats.append("private")
+
+        for reprocess_format in reprocess_formats:
+            # List of variances for R, G, B.
+            variances = []
+            nr_modes_reported = []
+
+            # NR mode 0 with low gain
+            e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+            req = its.objects.manual_capture_request(s, e)
+            req["android.noiseReduction.mode"] = 0
+
+            # Test reprocess_format->JPEG reprocessing
+            # TODO: Switch to reprocess_format->YUV when YUV reprocessing is
+            #       supported.
+            size = its.objects.get_available_output_sizes("jpg", props)[0]
+            out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+            cap = cam.do_capture(req, out_surface, reprocess_format)
+            img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+            its.image.write_image(img, "%s_low_gain_fmt=jpg.jpg" % (NAME))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            ref_variance = its.image.compute_image_variances(tile)
+            print "Ref variances:", ref_variance
+
+            for nr_mode in range(3):
+                # NR modes 0, 1, 2 with high gain
+                e, s = its.target.get_target_exposure_combos(cam) \
+                    ["maxSensitivity"]
+                req = its.objects.manual_capture_request(s, e)
+                req["android.noiseReduction.mode"] = nr_mode
+                cap = cam.do_capture(req, out_surface, reprocess_format)
+                nr_modes_reported.append(
+                    cap["metadata"]["android.noiseReduction.mode"])
+
+                img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+                its.image.write_image(
+                    img, "%s_high_gain_nr=%d_fmt=jpg.jpg" % (NAME, nr_mode))
+                tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+                # Get the variances for R, G, and B channels
+                variance = its.image.compute_image_variances(tile)
+                variances.append(
+                    [variance[chan] / ref_variance[chan] for chan in range(3)])
+            print "Variances with NR mode [0,1,2]:", variances
+
+            # Draw a plot.
+            for nr_mode in range(3):
+                pylab.plot(range(3), variances[nr_mode], "rgb"[nr_mode])
+            matplotlib.pyplot.savefig("%s_plot_%s_variances.png" %
+                                      (NAME, reprocess_format))
+
+            assert(nr_modes_reported == [0,1,2])
+
+            # Check that the variance of the NR=0 image is higher than for the
+            # NR=1 and NR=2 images.
+            for j in range(3):
+                for i in range(1,3):
+                    assert(variances[i][j] < variances[0][j])
+
+if __name__ == '__main__':
+    main()
+