CameraITS: add aspect ratio detection and crop test

Test aspect ratio and check if images are cropped correctly under
each output size.

The test image is a black circle inside a black square. When raw
capture is available, set the height vs. width ratio of the circle
in the full-frame raw as ground truth. Then compare with images of
request combinations of different formats ("jpeg" and "yuv") and
sizes. If raw capture is unavailable, take a picture of the test
image right in front to eliminate shooting angle effect.
The height vs. width ratio for the circle should be close to 1.
Considering shooting position error, aspect ratio greater than 1.05
or smaller than 0.95 will fail the test.

Change-Id: I051890e5aa43e2ffa66e563b989223b03de1571f
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 835a4a4..008f095 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -595,8 +595,20 @@
             formats = ['yuv']
         ncap = len(cmd["captureRequests"])
         nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
+        # Only allow yuv output to multiple targets
+        yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"]
+        n_yuv = len(yuv_surfaces)
+        # Compute the buffer size of YUV targets
+        yuv_sizes = [c["width"]*c["height"]*3/2 for c in yuv_surfaces]
+        # Currently we don't pass enough metadta from ItsService to distinguish
+        # different yuv stream of same buffer size
+        if len(yuv_sizes) != len(set(yuv_sizes)):
+            raise its.error.Error(
+                    'ITS does not support yuv outputs of same buffer size')
         if len(formats) > len(set(formats)):
-            raise its.error.Error('Duplicate format requested')
+            if n_yuv != len(formats) - len(set(formats)) + 1:
+                raise its.error.Error('Duplicate format requested')
+
         raw_formats = 0;
         raw_formats += 1 if "dng" in formats else 0
         raw_formats += 1 if "raw" in formats else 0
@@ -627,19 +639,24 @@
         # the burst, however individual images of different formats can come
         # out in any order for that capture.
         nbufs = 0
-        bufs = {"yuv":[], "raw":[], "raw10":[], "raw12":[],
+        bufs = {"raw":[], "raw10":[], "raw12":[],
                 "rawStats":[], "dng":[], "jpeg":[]}
+        yuv_bufs = {size:[] for size in yuv_sizes}
         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', \
+            if jsonObj['tag'] in ['jpegImage', 'rawImage', \
                     'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
                     and buf is not None:
                 fmt = jsonObj['tag'][:-5]
                 bufs[fmt].append(buf)
                 nbufs += 1
+            elif jsonObj['tag'] == 'yuvImage':
+                buf_size = numpy.product(buf.shape)
+                yuv_bufs[buf_size].append(buf)
+                nbufs += 1
             elif jsonObj['tag'] == 'captureResults':
                 mds.append(jsonObj['objValue']['captureResult'])
                 outputs = jsonObj['objValue']['outputs']
@@ -653,11 +670,15 @@
             objs = []
             for i in range(ncap):
                 obj = {}
-                obj["data"] = bufs[fmt][i]
                 obj["width"] = widths[j]
                 obj["height"] = heights[j]
                 obj["format"] = fmt
                 obj["metadata"] = mds[i]
+                if fmt == 'yuv':
+                    buf_size = widths[j] * heights[j] * 3 / 2
+                    obj["data"] = yuv_bufs[buf_size][i]
+                else:
+                    obj["data"] = bufs[fmt][i]
                 objs.append(obj)
             rets.append(objs if ncap>1 else objs[0])
         self.sock.settimeout(self.SOCK_TIMEOUT)