Squashed mnc-dev changes:

This contains all of the changes from b54aa51 to
791e51a on mnc-dev, except the changes
to tests/tests/security.

Bug: 24846656
Change-Id: I01f53a1a238ac49f86928e0e22796dc73e0e34af
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 756f959..835a4a4 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -448,8 +448,8 @@
 
         The out_surfaces field can specify the width(s), height(s), and
         format(s) of the captured image. The formats may be "yuv", "jpeg",
-        "dng", "raw", "raw10", or "raw12". The default is a YUV420 frame ("yuv")
-        corresponding to a full sensor frame.
+        "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
+        frame ("yuv") corresponding to a full sensor frame.
 
         Note that one or more surfaces can be specified, allowing a capture to
         request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
@@ -536,6 +536,25 @@
             yuv_caps           = do_capture( [req1,req2], yuv_fmt           )
             yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
 
+        The "rawStats" format processes the raw image and returns a new image
+        of statistics from the raw image. The format takes additional keys,
+        "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
+        of the raw image. For each grid cell, the mean and variance of each raw
+        channel is computed, and the do_capture call returns two 4-element float
+        images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
+        concatenated back-to-back, where the first iamge contains the 4-channel
+        means and the second contains the 4-channel variances.
+
+        For the rawStats format, if the gridWidth is not provided then the raw
+        image width is used as the default, and similarly for gridHeight. With
+        this, the following is an example of a output description that computes
+        the mean and variance across each image row:
+
+            {
+                "gridHeight": 1,
+                "format": "rawStats"
+            }
+
         Args:
             cap_request: The Python dict/list specifying the capture(s), which
                 will be converted to JSON and sent to the device.
@@ -550,7 +569,8 @@
             * data: the image data as a numpy array of bytes.
             * width: the width of the captured image.
             * height: the height of the captured image.
-            * format: image the format, in ["yuv","jpeg","raw","raw10","dng"].
+            * format: image the format, in [
+                        "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
             * metadata: the capture result object (Python dictionary).
         """
         cmd = {}
@@ -577,9 +597,13 @@
         nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
         if len(formats) > len(set(formats)):
             raise its.error.Error('Duplicate format requested')
-        if "dng" in formats and "raw" in formats or \
-                "dng" in formats and "raw10" in formats or \
-                "raw" in formats and "raw10" in formats:
+        raw_formats = 0;
+        raw_formats += 1 if "dng" in formats else 0
+        raw_formats += 1 if "raw" in formats else 0
+        raw_formats += 1 if "raw10" in formats else 0
+        raw_formats += 1 if "raw12" in formats else 0
+        raw_formats += 1 if "rawStats" in formats else 0
+        if raw_formats > 1:
             raise its.error.Error('Different raw formats not supported')
 
         # Detect long exposure time and set timeout accordingly
@@ -603,14 +627,16 @@
         # the burst, however individual images of different formats can come
         # out in any order for that capture.
         nbufs = 0
-        bufs = {"yuv":[], "raw":[], "raw10":[], "dng":[], "jpeg":[]}
+        bufs = {"yuv":[], "raw":[], "raw10":[], "raw12":[],
+                "rawStats":[], "dng":[], "jpeg":[]}
         mds = []
         widths = None
         heights = None
         while nbufs < ncap*nsurf or len(mds) < ncap:
             jsonObj,buf = self.__read_response_from_socket()
             if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \
-                    'raw10Image', 'dngImage'] and buf is not None:
+                    'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
+                    and buf is not None:
                 fmt = jsonObj['tag'][:-5]
                 bufs[fmt].append(buf)
                 nbufs += 1
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index ea01a3e..a5ac60b 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -81,6 +81,25 @@
     else:
         raise its.error.Error('Invalid format %s' % (cap["format"]))
 
+def unpack_rawstats_capture(cap):
+    """Unpack a rawStats capture to the mean and variance images.
+
+    Args:
+        cap: A capture object as returned by its.device.do_capture.
+
+    Returns:
+        Tuple (mean_image var_image) of float-4 images, with non-normalized
+        pixel values computed from the RAW16 images on the device
+    """
+    assert(cap["format"] == "rawStats")
+    w = cap["width"]
+    h = cap["height"]
+    img = numpy.ndarray(shape=(2*h*w*4,), dtype='<f', buffer=cap["data"])
+    analysis_image = img.reshape(2,h,w,4)
+    mean_image = analysis_image[0,:,:,:].reshape(h,w,4)
+    var_image = analysis_image[1,:,:,:].reshape(h,w,4)
+    return mean_image, var_image
+
 def unpack_raw10_capture(cap, props):
     """Unpack a raw-10 capture to a raw-16 capture.
 
@@ -604,6 +623,21 @@
         variances.append(numpy.var(img[:,:,i], dtype=numpy.float64))
     return variances
 
+def compute_image_snrs(img):
+    """Calculate the SNR (db) of each color channel in the image.
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+
+    Returns:
+        A list of SNR value, one per color channel in the image.
+    """
+    means = compute_image_means(img)
+    variances = compute_image_variances(img)
+    std_devs = [math.sqrt(v) for v in variances]
+    snr = [20 * math.log10(m/s) for m,s in zip(means, std_devs)]
+    return snr
+
 def write_image(img, fname, apply_gamma=False):
     """Save a float-3 numpy array image to a file.
 
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 82346ec..ac384fb 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -152,24 +152,37 @@
 
     return req
 
-def get_available_output_sizes(fmt, props):
+def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None):
     """Return a sorted list of available output sizes for a given format.
 
     Args:
         fmt: the output format, as a string in
             ["jpg", "yuv", "raw", "raw10", "raw12"].
         props: the object returned from its.device.get_camera_properties().
+        max_size: (Optional) A (w,h) tuple.
+            Sizes larger than max_size (either w or h)  will be discarded.
+        match_ar_size: (Optional) A (w,h) tuple.
+            Sizes not matching the aspect ratio of match_ar_size will be
+            discarded.
 
     Returns:
         A sorted list of (w,h) tuples (sorted large-to-small).
     """
-    fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26, "yuv":0x23,
+    AR_TOLERANCE = 0.03
+    fmt_codes = {"raw":0x20, "raw10":0x25, "raw12":0x26,"yuv":0x23,
                  "jpg":0x100, "jpeg":0x100}
     configs = props['android.scaler.streamConfigurationMap']\
                    ['availableStreamConfigurations']
     fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
     out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False]
     out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs]
+    if max_size:
+        out_sizes = [s for s in out_sizes if
+                s[0] <= max_size[0] and s[1] <= max_size[1]]
+    if match_ar_size:
+        ar = match_ar_size[0] / float(match_ar_size[1])
+        out_sizes = [s for s in out_sizes if
+                abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE]
     out_sizes.sort(reverse=True)
     return out_sizes
 
diff --git a/apps/CameraITS/tests/inprog/test_rawstats.py b/apps/CameraITS/tests/inprog/test_rawstats.py
new file mode 100644
index 0000000..8083f0b
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_rawstats.py
@@ -0,0 +1,48 @@
+# 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 os.path
+import math
+
+def main():
+    """Test capturing some rawstats data.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+
+        cam.do_3a(do_af=False);
+        req = its.objects.auto_capture_request()
+
+        for (gw,gh) in [(16,16)]:#,(4080,1)]:
+            cap = cam.do_capture(req,
+                {"format":"rawStats","gridWidth":gw,"gridHeight":gh})
+            mean_image, var_image = its.image.unpack_rawstats_capture(cap)
+
+            if gw > 1 and gh > 1:
+                h,w,_ = mean_image.shape
+                for ch in range(4):
+                    m = mean_image[:,:,ch].reshape(h,w,1)/1023.0
+                    v = var_image[:,:,ch].reshape(h,w,1)
+                    its.image.write_image(m, "%s_mean_ch%d.jpg" % (NAME,ch), True)
+                    its.image.write_image(v, "%s_var_ch%d.jpg" % (NAME,ch), True)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index 26c398d..89bc724 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -35,8 +35,10 @@
     THRESHOLD_MAX_OUTLIER_DIFF = 0.1
     THRESHOLD_MIN_LEVEL = 0.1
     THRESHOLD_MAX_LEVEL = 0.9
-    THRESHOLD_MAX_LEVEL_DIFF = 0.025
+    THRESHOLD_MAX_LEVEL_DIFF = 0.03
     THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.05
+    THRESHOLD_ROUND_DOWN_GAIN = 0.1
+    THRESHOLD_ROUND_DOWN_EXP = 0.05
 
     mults = []
     r_means = []
@@ -50,21 +52,33 @@
                              its.caps.per_frame_control(props))
 
         e,s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        s_e_product = s*e
         expt_range = props['android.sensor.info.exposureTimeRange']
         sens_range = props['android.sensor.info.sensitivityRange']
 
-        m = 1
+        m = 1.0
         while s*m < sens_range[1] and e/m > expt_range[0]:
             mults.append(m)
-            req = its.objects.manual_capture_request(s*m, e/m)
+            s_test = round(s*m)
+            e_test = s_e_product / s_test
+            print "Testsing s:", s_test, "e:", e_test
+            req = its.objects.manual_capture_request(s_test, e_test)
             cap = cam.do_capture(req)
+            s_res = cap["metadata"]["android.sensor.sensitivity"]
+            e_res = cap["metadata"]["android.sensor.exposureTime"]
+            assert(0 <= s_test - s_res < s_test * THRESHOLD_ROUND_DOWN_GAIN)
+            assert(0 <= e_test - e_res < e_test * THRESHOLD_ROUND_DOWN_EXP)
+            s_e_product_res = s_res * e_res
+            request_result_ratio = s_e_product / s_e_product_res
+            print "Capture result s:", s_test, "e:", e_test
             img = its.image.convert_capture_to_rgb_image(cap)
             its.image.write_image(img, "%s_mult=%3.2f.jpg" % (NAME, m))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
-            r_means.append(rgb_means[0])
-            g_means.append(rgb_means[1])
-            b_means.append(rgb_means[2])
+            # Adjust for the difference between request and result
+            r_means.append(rgb_means[0] * request_result_ratio)
+            g_means.append(rgb_means[1] * request_result_ratio)
+            b_means.append(rgb_means[2] * request_result_ratio)
             # Test 3 steps per 2x gain
             m = m * pow(2, 1.0 / 3)
 
@@ -73,9 +87,9 @@
             threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
 
     # Draw a plot.
-    pylab.plot(mults, r_means, 'r')
-    pylab.plot(mults, g_means, 'g')
-    pylab.plot(mults, b_means, 'b')
+    pylab.plot(mults, r_means, 'r.-')
+    pylab.plot(mults, g_means, 'g.-')
+    pylab.plot(mults, b_means, 'b.-')
     pylab.ylim([0,1])
     matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
 
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
index 35cfc07..1072684 100644
--- a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -35,12 +35,13 @@
     """
     NAME = os.path.basename(__file__).split(".")[0]
 
-    RELATIVE_ERROR_TOLERANCE = 0.1
-    # List of variances for R,G,B.
-    variances = [[],[],[]]
+    NUM_SAMPLES_PER_MODE = 4
+    SNR_TOLERANCE = 3 # unit in db
+    # List of SNRs for R,G,B.
+    snrs = [[], [], []]
 
-    # Reference (baseline) variance for each of R,G,B.
-    ref_variance = []
+    # Reference (baseline) SNR for each of R,G,B.
+    ref_snr = []
 
     nr_modes_reported = []
 
@@ -60,8 +61,8 @@
                 rgb_image,
                 "%s_low_gain.jpg" % (NAME))
         rgb_tile = its.image.get_image_patch(rgb_image, 0.45, 0.45, 0.1, 0.1)
-        ref_variance = its.image.compute_image_variances(rgb_tile)
-        print "Ref variances:", ref_variance
+        ref_snr = its.image.compute_image_snrs(rgb_tile)
+        print "Ref SNRs:", ref_snr
 
         e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
         # NR modes 0, 1, 2, 3, 4 with high gain
@@ -70,58 +71,74 @@
             if not its.caps.noise_reduction_mode(props, mode):
                 nr_modes_reported.append(mode)
                 for channel in range(3):
-                    variances[channel].append(0)
+                    snrs[channel].append(0)
                 continue;
 
-            req = its.objects.manual_capture_request(s, e)
-            req["android.noiseReduction.mode"] = mode
-            cap = cam.do_capture(req)
-            rgb_image = its.image.convert_capture_to_rgb_image(cap)
-            nr_modes_reported.append(
-                    cap["metadata"]["android.noiseReduction.mode"])
-            its.image.write_image(
-                    rgb_image,
-                    "%s_high_gain_nr=%d.jpg" % (NAME, mode))
-            rgb_tile = its.image.get_image_patch(
-                    rgb_image, 0.45, 0.45, 0.1, 0.1)
-            rgb_vars = its.image.compute_image_variances(rgb_tile)
+            rgb_snr_list = []
+            # Capture several images to account for per frame noise variations
+            for n in range(NUM_SAMPLES_PER_MODE):
+                req = its.objects.manual_capture_request(s, e)
+                req["android.noiseReduction.mode"] = mode
+                cap = cam.do_capture(req)
+                rgb_image = its.image.convert_capture_to_rgb_image(cap)
+                if n == 0:
+                    nr_modes_reported.append(
+                            cap["metadata"]["android.noiseReduction.mode"])
+                    its.image.write_image(
+                            rgb_image,
+                            "%s_high_gain_nr=%d.jpg" % (NAME, mode))
+                rgb_tile = its.image.get_image_patch(
+                        rgb_image, 0.45, 0.45, 0.1, 0.1)
+                rgb_snrs = its.image.compute_image_snrs(rgb_tile)
+                rgb_snr_list.append(rgb_snrs)
+
+            r_snrs = [rgb[0] for rgb in rgb_snr_list]
+            g_snrs = [rgb[1] for rgb in rgb_snr_list]
+            b_snrs = [rgb[2] for rgb in rgb_snr_list]
+            rgb_snrs = [numpy.mean(r_snrs), numpy.mean(g_snrs), numpy.mean(b_snrs)]
+            print "NR mode", mode, "SNRs:"
+            print "    R SNR:", rgb_snrs[0],\
+                    "Min:", min(r_snrs), "Max:", max(r_snrs)
+            print "    G SNR:", rgb_snrs[1],\
+                    "Min:", min(g_snrs), "Max:", max(g_snrs)
+            print "    B SNR:", rgb_snrs[2],\
+                    "Min:", min(b_snrs), "Max:", max(b_snrs)
+
             for chan in range(3):
-                variance = rgb_vars[chan]
-                variances[chan].append(variance / ref_variance[chan])
-        print "Variances with NR mode [0,1,2]:", variances
+                snrs[chan].append(rgb_snrs[chan])
 
     # Draw a plot.
     for j in range(3):
-        pylab.plot(range(5), variances[j], "rgb"[j])
-    matplotlib.pyplot.savefig("%s_plot_variances.png" % (NAME))
+        pylab.plot(range(5), snrs[j], "rgb"[j])
+    matplotlib.pyplot.savefig("%s_plot_SNRs.png" % (NAME))
 
     assert(nr_modes_reported == [0,1,2,3,4])
 
     for j in range(3):
-        # Smaller variance is better
+        # Larger SNR is better
         # Verify OFF(0) is not better than FAST(1)
-        assert(variances[j][0] >
-               variances[j][1] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+        assert(snrs[j][0] <
+               snrs[j][1] + SNR_TOLERANCE)
         # Verify FAST(1) is not better than HQ(2)
-        assert(variances[j][1] >
-               variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+        assert(snrs[j][1] <
+               snrs[j][2] + SNR_TOLERANCE)
         # Verify HQ(2) is better than OFF(0)
-        assert(variances[j][0] > variances[j][2])
+        assert(snrs[j][0] < snrs[j][2])
         if its.caps.noise_reduction_mode(props, 3):
             # Verify OFF(0) is not better than MINIMAL(3)
-            assert(variances[j][0] >
-                   variances[j][3] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+            assert(snrs[j][0] <
+                   snrs[j][3] + SNR_TOLERANCE)
             # Verify MINIMAL(3) is not better than HQ(2)
-            assert(variances[j][3] >
-                   variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+            assert(snrs[j][3] <
+                   snrs[j][2] + SNR_TOLERANCE)
             if its.caps.noise_reduction_mode(props, 4):
                 # Verify ZSL(4) is close to MINIMAL(3)
-                assert(numpy.isclose(variances[j][4], variances[j][3],
-                                     RELATIVE_ERROR_TOLERANCE))
+                assert(numpy.isclose(snrs[j][4], snrs[j][3],
+                                     atol=SNR_TOLERANCE))
         elif its.caps.noise_reduction_mode(props, 4):
             # Verify ZSL(4) is close to OFF(0)
-            assert(numpy.isclose(variances[j][4], variances[j][0],
-                                 RELATIVE_ERROR_TOLERANCE))
+            assert(numpy.isclose(snrs[j][4], snrs[j][0],
+                                 atol=SNR_TOLERANCE))
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
index 6c2b5c1..e176312 100644
--- a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
@@ -44,6 +44,8 @@
 
         # Expose for the scene with min sensitivity
         sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        # Digital gains might not be visible on RAW data
+        sens_max = props['android.sensor.maxAnalogSensitivity']
         sens_step = (sens_max - sens_min) / NUM_STEPS
         s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
         s_e_prod = s_ae * e_ae
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
index 14c5eb0..cc0ce14 100644
--- a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
@@ -42,6 +42,8 @@
 
         # Expose for the scene with min sensitivity
         sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        # Digital gains might not be visible on RAW data
+        sens_max = props['android.sensor.maxAnalogSensitivity']
         sens_step = (sens_max - sens_min) / NUM_STEPS
         s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
         s_e_prod = s_ae * e_ae
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
index 757dfeb..f0a6fbe 100644
--- a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -38,7 +38,8 @@
 
     NAME = os.path.basename(__file__).split(".")[0]
 
-    RELATIVE_ERROR_TOLERANCE = 0.1
+    NUM_SAMPLES_PER_MODE = 4
+    SNR_TOLERANCE = 3 # unit in db
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -60,7 +61,7 @@
 
         for reprocess_format in reprocess_formats:
             # List of variances for R, G, B.
-            variances = []
+            snrs = [[], [], []]
             nr_modes_reported = []
 
             # NR mode 0 with low gain
@@ -77,71 +78,90 @@
             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
+            ref_snr = its.image.compute_image_snrs(tile)
+            print "Ref SNRs:", ref_snr
 
+            e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
             for nr_mode in range(5):
                 # Skip unavailable modes
                 if not its.caps.noise_reduction_mode(props, nr_mode):
                     nr_modes_reported.append(nr_mode)
-                    variances.append(0)
+                    for channel in range(3):
+                        snrs[channel].append(0)
                     continue
 
-                # NR modes 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"])
+                rgb_snr_list = []
+                # Capture several images to account for per frame noise
+                # variations
+                for n in range(NUM_SAMPLES_PER_MODE):
+                    req = its.objects.manual_capture_request(s, e)
+                    req["android.noiseReduction.mode"] = nr_mode
+                    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_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,3,4]:", variances
+                    img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+                    if n == 0:
+                        its.image.write_image(
+                                img,
+                                "%s_high_gain_nr=%d_fmt=jpg.jpg"
+                                        %(NAME, nr_mode))
+                        nr_modes_reported.append(
+                                cap["metadata"]["android.noiseReduction.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
+                    rgb_snrs = its.image.compute_image_snrs(tile)
+                    rgb_snr_list.append(rgb_snrs)
+
+                r_snrs = [rgb[0] for rgb in rgb_snr_list]
+                g_snrs = [rgb[1] for rgb in rgb_snr_list]
+                b_snrs = [rgb[2] for rgb in rgb_snr_list]
+                rgb_snrs = [numpy.mean(r_snrs),
+                            numpy.mean(g_snrs),
+                            numpy.mean(b_snrs)]
+                print "NR mode", nr_mode, "SNRs:"
+                print "    R SNR:", rgb_snrs[0],\
+                        "Min:", min(r_snrs), "Max:", max(r_snrs)
+                print "    G SNR:", rgb_snrs[1],\
+                        "Min:", min(g_snrs), "Max:", max(g_snrs)
+                print "    B SNR:", rgb_snrs[2],\
+                        "Min:", min(b_snrs), "Max:", max(b_snrs)
+
+                for chan in range(3):
+                    snrs[chan].append(rgb_snrs[chan])
 
             # Draw a plot.
-            for chan in range(3):
-                line = []
-                for nr_mode in range(5):
-                    line.append(variances[nr_mode][chan])
-                pylab.plot(range(5), line, "rgb"[chan])
+            for channel in range(3):
+                pylab.plot(range(5), snrs[channel], "rgb"[channel])
 
-            matplotlib.pyplot.savefig("%s_plot_%s_variances.png" %
+            matplotlib.pyplot.savefig("%s_plot_%s_SNRs.png" %
                                       (NAME, reprocess_format))
 
             assert(nr_modes_reported == [0,1,2,3,4])
 
             for j in range(3):
-                # Smaller variance is better
+                # Larger is better
                 # Verify OFF(0) is not better than FAST(1)
-                assert(variances[0][j] >
-                       variances[1][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                assert(snrs[j][0] <
+                       snrs[j][1] + SNR_TOLERANCE)
                 # Verify FAST(1) is not better than HQ(2)
-                assert(variances[1][j] >
-                       variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                assert(snrs[j][1] <
+                       snrs[j][2] + SNR_TOLERANCE)
                 # Verify HQ(2) is better than OFF(0)
-                assert(variances[0][j] > variances[2][j])
+                assert(snrs[j][0] < snrs[j][2])
                 if its.caps.noise_reduction_mode(props, 3):
                     # Verify OFF(0) is not better than MINIMAL(3)
-                    assert(variances[0][j] >
-                           variances[3][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                    assert(snrs[j][0] <
+                           snrs[j][3] + SNR_TOLERANCE)
                     # Verify MINIMAL(3) is not better than HQ(2)
-                    assert(variances[3][j] >
-                           variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                    assert(snrs[j][3] <
+                           snrs[j][2] + SNR_TOLERANCE)
                     # Verify ZSL(4) is close to MINIMAL(3)
-                    assert(numpy.isclose(variances[4][j], variances[3][j],
-                                         RELATIVE_ERROR_TOLERANCE))
+                    assert(numpy.isclose(snrs[j][4], snrs[j][3],
+                                         atol=SNR_TOLERANCE))
                 else:
                     # Verify ZSL(4) is close to OFF(0)
-                    assert(numpy.isclose(variances[4][j], variances[0][j],
-                                         RELATIVE_ERROR_TOLERANCE))
+                    assert(numpy.isclose(snrs[j][4], snrs[j][0],
+                                         atol=SNR_TOLERANCE))
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
index 33e7763..268b64a 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
@@ -31,6 +31,12 @@
         cam.do_3a()
 
         req = its.objects.auto_capture_request()
+        max_dng_size = \
+                its.objects.get_available_output_sizes("raw", props)[0]
+        w,h = its.objects.get_available_output_sizes(
+                "yuv", props, (1920, 1080), max_dng_size)[0]
+        out_surfaces = [{"format":"dng"},
+                        {"format":"yuv", "width":w, "height":h}]
         cap_dng, cap_yuv = cam.do_capture(req, cam.CAP_DNG_YUV)
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
index 9ce8d76..78378eb 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
@@ -27,13 +27,17 @@
 
     THRESHOLD_MAX_RMS_DIFF = 0.01
 
-    fmt_yuv =  {"format":"yuv"}
-    fmt_jpeg = {"format":"jpeg"}
-
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.compute_target_exposure(props))
 
+        max_jpeg_size = \
+                its.objects.get_available_output_sizes("jpeg", props)[0]
+        w,h = its.objects.get_available_output_sizes(
+                "yuv", props, (1920, 1080), max_jpeg_size)[0]
+        fmt_yuv =  {"format":"yuv", "width":w, "height":h}
+        fmt_jpeg = {"format":"jpeg"}
+
         # Use a manual request with a linear tonemap so that the YUV and JPEG
         # should look the same (once converted by the its.image module).
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
index f13801b..bfa6a28 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -38,7 +38,13 @@
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, True, props)
 
-        cap_raw, cap_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+        max_raw_size = \
+                its.objects.get_available_output_sizes("raw", props)[0]
+        w,h = its.objects.get_available_output_sizes(
+                "yuv", props, (1920, 1080), max_raw_size)[0]
+        out_surfaces = [{"format":"raw"},
+                        {"format":"yuv", "width":w, "height":h}]
+        cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
         its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
index e52946d..322af10 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -38,8 +38,13 @@
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, True, props)
 
+        max_raw10_size = \
+                its.objects.get_available_output_sizes("raw10", props)[0]
+        w,h = its.objects.get_available_output_sizes(
+                "yuv", props, (1920, 1080), max_raw10_size)[0]
         cap_raw, cap_yuv = cam.do_capture(req,
-                [{"format":"raw10"}, {"format":"yuv"}])
+                [{"format":"raw10"},
+                 {"format":"yuv", "width":w, "height":h}])
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
         its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
index c5c3c73..b3cca0b 100644
--- a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
@@ -38,8 +38,13 @@
         e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
         req = its.objects.manual_capture_request(s, e, True, props)
 
+        max_raw12_size = \
+                its.objects.get_available_output_sizes("raw12", props)[0]
+        w,h = its.objects.get_available_output_sizes(
+                "yuv", props, (1920, 1080), max_raw12_size)[0]
         cap_raw, cap_yuv = cam.do_capture(req,
-                [{"format":"raw12"}, {"format":"yuv"}])
+                [{"format":"raw12"},
+                 {"format":"yuv", "width":w, "height":h}])
 
         img = its.image.convert_capture_to_rgb_image(cap_yuv)
         its.image.write_image(img, "%s_yuv.jpg" % (NAME), True)
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index 73834cb..e96a9ee 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -53,22 +53,28 @@
     """
 
     NAME = os.path.basename(__file__).split(".")[0]
+    NUM_SAMPLES = 4
 
     req = its.objects.manual_capture_request(sensitivity, exp)
     req["android.lens.focusDistance"] = fd
     req["android.edge.mode"] = edge_mode
     if (reprocess_format != None):
         req["android.reprocess.effectiveExposureFactor"] = 1.0
-    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_edge=%d_reprocess_fmt_%s.jpg" %
-        (NAME, edge_mode, reprocess_format))
-    tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+    sharpness_list = []
+    for n in range(NUM_SAMPLES):
+        cap = cam.do_capture(req, out_surface, reprocess_format)
+        img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+        if n == 0:
+            its.image.write_image(img, "%s_reprocess_fmt_%s_edge=%d.jpg" %
+                (NAME, reprocess_format, edge_mode))
+            res_edge_mode = cap["metadata"]["android.edge.mode"]
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        sharpness_list.append(its.image.compute_image_sharpness(tile))
 
     ret = {}
-    ret["edge_mode"] = cap["metadata"]["android.edge.mode"]
-    ret["sharpness"] = its.image.compute_image_sharpness(tile)
+    ret["edge_mode"] = res_edge_mode
+    ret["sharpness"] = numpy.mean(sharpness_list)
 
     return ret