CameraITS: add face detection test

Add a new test scene for face detection.
Also fix typo in CTS face detection test.

Bug: 21616680
Change-Id: I8f6674e8402f21c3f6fae2325c84c0a575a3e025
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 2430420..3866930 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/inprog/test_faces.py b/apps/CameraITS/tests/inprog/test_faces.py
deleted file mode 100644
index 228dac8..0000000
--- a/apps/CameraITS/tests/inprog/test_faces.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2014 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.device
-import its.objects
-import os.path
-
-def main():
-    """Test face detection.
-    """
-    NAME = os.path.basename(__file__).split(".")[0]
-
-    with its.device.ItsSession() as cam:
-        cam.do_3a()
-        req = its.objects.auto_capture_request()
-        req['android.statistics.faceDetectMode'] = 2
-        caps = cam.do_capture([req]*5)
-        for i,cap in enumerate(caps):
-            md = cap['metadata']
-            print "Frame %d face metadata:" % i
-            print "  Ids:", md['android.statistics.faceIds']
-            print "  Landmarks:", md['android.statistics.faceLandmarks']
-            print "  Rectangles:", md['android.statistics.faceRectangles']
-            print "  Scores:", md['android.statistics.faceScores']
-            print ""
-
-if __name__ == '__main__':
-    main()
-
diff --git a/apps/CameraITS/tests/scene2/SampleTarget.jpg b/apps/CameraITS/tests/scene2/SampleTarget.jpg
new file mode 100644
index 0000000..c054f7e
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/SampleTarget.jpg
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2/test_faces.py
new file mode 100644
index 0000000..cce74e7
--- /dev/null
+++ b/apps/CameraITS/tests/scene2/test_faces.py
@@ -0,0 +1,102 @@
+# Copyright 2014 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.device
+import its.objects
+import os.path
+
+def main():
+    """Test face detection.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+    NUM_TEST_FRAMES = 20
+    FD_MODE_OFF = 0
+    FD_MODE_SIMPLE = 1
+    FD_MODE_FULL = 2
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        fd_modes = props['android.statistics.info.availableFaceDetectModes']
+        a = props['android.sensor.info.activeArraySize']
+        aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+        cam.do_3a()
+        for fd_mode in fd_modes:
+            assert(FD_MODE_OFF <= fd_mode <= FD_MODE_FULL)
+            req = its.objects.auto_capture_request()
+            req['android.statistics.faceDetectMode'] = fd_mode
+            caps = cam.do_capture([req]*NUM_TEST_FRAMES)
+            for i,cap in enumerate(caps):
+                md = cap['metadata']
+                assert(md['android.statistics.faceDetectMode'] == fd_mode)
+                faces = md['android.statistics.faces']
+
+                # 0 faces should be returned for OFF mode
+                if fd_mode == FD_MODE_OFF:
+                    assert(len(faces) == 0)
+                    continue
+                # Face detection could take several frames to warm up,
+                # but it should detect at least one face in last frame
+                if i == NUM_TEST_FRAMES - 1:
+                    if len(faces) == 0:
+                        print "Error: no face detected in mode", fd_mode
+                        assert(0)
+                if len(faces) == 0:
+                    continue
+
+                print "Frame %d face metadata:" % i
+                print "  Faces:", faces
+                print ""
+
+                face_scores = [face['score'] for face in faces]
+                face_rectangles = [face['bounds'] for face in faces]
+                for score in face_scores:
+                    assert(score >= 1 and score <= 100)
+                # Face bounds should be within active array
+                for rect in face_rectangles:
+                    assert(rect['top'] < rect['bottom'])
+                    assert(rect['left'] < rect['right'])
+                    assert(0 <= rect['top'] <= ah)
+                    assert(0 <= rect['bottom'] <= ah)
+                    assert(0 <= rect['left'] <= aw)
+                    assert(0 <= rect['right'] <= aw)
+
+                # Face landmarks are reported if and only if fd_mode is FULL
+                # Face ID should be -1 for SIMPLE and unique for FULL
+                if fd_mode == FD_MODE_SIMPLE:
+                    for face in faces:
+                        assert('leftEye' not in face)
+                        assert('rightEye' not in face)
+                        assert('mouth' not in face)
+                        assert(face['id'] == -1)
+                elif fd_mode == FD_MODE_FULL:
+                    face_ids = [face['id'] for face in faces]
+                    assert(len(face_ids) == len(set(face_ids)))
+                    # Face landmarks should be within face bounds
+                    for face in faces:
+                        left_eye = face['leftEye']
+                        right_eye = face['rightEye']
+                        mouth = face['mouth']
+                        l, r = face['bounds']['left'], face['bounds']['right']
+                        t, b = face['bounds']['top'], face['bounds']['bottom']
+                        assert(l <= left_eye['x'] <= r)
+                        assert(t <= left_eye['y'] <= b)
+                        assert(l <= right_eye['x'] <= r)
+                        assert(t <= right_eye['y'] <= b)
+                        assert(l <= mouth['x'] <= r)
+                        assert(t <= mouth['y'] <= b)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 2bbd387..c065f12 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -41,12 +41,18 @@
             "test_ev_compensation_advanced",
             "test_ev_compensation_basic",
             "test_yuv_plus_jpeg"
-        ]
+        ],
+        "scene2":[]
     }
 
     # Get all the scene0 and scene1 tests, which can be run using the same
     # physical setup.
-    scenes = ["scene0", "scene1"]
+    scenes = ["scene0", "scene1", "scene2"]
+    scene_req = {
+        "scene0" : None,
+        "scene1" : "A grey card covering at least the middle 30% of the scene",
+        "scene2" : "A picture containing human faces"
+    }
     tests = []
     for d in scenes:
         tests += [(d,s[:-3],os.path.join("tests", d, s))
@@ -86,14 +92,6 @@
         for d in scenes:
             os.mkdir(os.path.join(topdir, camera_id, d))
 
-        out_path = os.path.join(topdir, camera_id, "scene.jpg")
-        out_arg = "out=" + out_path
-        cmd = ['python',
-               os.path.join(os.getcwd(),"tools/validate_scene.py"),
-               camera_id_arg, out_arg]
-        retcode = subprocess.call(cmd,cwd=topdir)
-        assert(retcode == 0)
-
         print "Start running ITS on camera: ", camera_id
         # Run each test, capturing stdout and stderr.
         summary = "ITS test result summary for camera " + camera_id + "\n"
@@ -102,7 +100,19 @@
         numnotmandatedfail = 0
         numfail = 0
 
+        prev_scene = ""
         for (scene,testname,testpath) in tests:
+            if scene != prev_scene and scene_req[scene] != None:
+                out_path = os.path.join(topdir, camera_id, scene+".jpg")
+                out_arg = "out=" + out_path
+                scene_arg = "scene=" + scene_req[scene]
+                cmd = ['python',
+                        os.path.join(os.getcwd(),"tools/validate_scene.py"),
+                        camera_id_arg, out_arg, scene_arg]
+                retcode = subprocess.call(cmd,cwd=topdir)
+                assert(retcode == 0)
+                print "Start running tests for", scene
+            prev_scene = scene
             cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
                   sys.argv[1:] + [camera_id_arg]
             outdir = os.path.join(topdir,camera_id,scene)
diff --git a/apps/CameraITS/tools/validate_scene.py b/apps/CameraITS/tools/validate_scene.py
index ea851b7..1f35163 100644
--- a/apps/CameraITS/tools/validate_scene.py
+++ b/apps/CameraITS/tools/validate_scene.py
@@ -17,17 +17,25 @@
 import its.objects
 import its.image
 import its.caps
+import re
 
 def main():
     """capture a yuv image and save it to argv[1]
     """
     camera_id = -1
     out_path = ""
+    scene_name = ""
+    scene_desc = "No requirement"
     for s in sys.argv[1:]:
         if s[:7] == "camera=" and len(s) > 7:
             camera_id = s[7:]
         elif s[:4] == "out=" and len(s) > 4:
             out_path = s[4:]
+        elif s[:6] == "scene=" and len(s) > 6:
+            scene_desc = s[6:]
+
+    if out_path != "":
+        scene_name = re.split("/|\.", out_path)[-2]
 
     if camera_id == -1:
         print "Error: need to specify which camera to use"
@@ -35,7 +43,8 @@
 
     with its.device.ItsSession() as cam:
         raw_input("Press Enter after placing camera " + camera_id +
-                " to frame the test scene")
+                " to frame the test scene: " + scene_name +
+                "\nThe scene setup should be: " + scene_desc )
         # Converge 3A prior to capture.
         cam.do_3a(do_af=True, lock_ae=True, lock_awb=True)
         props = cam.get_camera_properties()
@@ -52,7 +61,8 @@
                 its.image.write_image(img, out_path)
             print "Please check scene setup in", out_path
             choice = raw_input(
-                "Is the image okay for ITS scene1? (Y/N)").lower()
+                "Is the image okay for ITS " + scene_name +\
+                "? (Y/N)").lower()
             if choice == "y":
                 break
             else:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java
index cf8365a..57d0c8f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java
@@ -116,9 +116,15 @@
         faceObj.put("bounds", serializeRect(face.getBounds()));
         faceObj.put("score", face.getScore());
         faceObj.put("id", face.getId());
-        faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
-        faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
-        faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+        if (face.getLeftEyePosition() != null) {
+            faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+        }
+        if (face.getRightEyePosition() != null) {
+            faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+        }
+        if (face.getMouthPosition() != null) {
+            faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+        }
         return faceObj;
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index f3acf4c..964e6b6 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -1375,7 +1375,7 @@
                     }
                 }
             }
-            mCollector.expectValuesInRange("Face scores are invalid", faceIds,
+            mCollector.expectValuesInRange("Face scores are invalid", faceScores,
                     Face.SCORE_MIN, Face.SCORE_MAX);
             mCollector.expectValuesUnique("Face ids are invalid", faceIds);
         }