CameraITS: remove cv2 dependcy from image.py

So that only tests require opencv needs to import opencv

Test: python tools/run_all_tests.py scenes=0,3 ...
Bug: 31995717
Change-Id: I08ff1ddd8b0508220180e8c1188c48adba66f26d
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
new file mode 100644
index 0000000..83e654e
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -0,0 +1,197 @@
+# Copyright 2016 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 matplotlib
+matplotlib.use('Agg')
+
+import its.error
+from matplotlib import pylab
+import sys
+from PIL import Image
+import numpy
+import math
+import unittest
+import cStringIO
+import scipy.stats
+import copy
+import cv2
+import os
+
+def scale_img(img, scale=1.0):
+    """Scale and image based on a real number scale factor."""
+    dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
+    return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
+
+class Chart(object):
+    """Definition for chart object.
+
+    Defines PNG reference file, chart size and distance, and scaling range.
+    """
+
+    def __init__(self, chart_file, height, distance, scale_start, scale_stop,
+                 scale_step):
+        """Initial constructor for class.
+
+        Args:
+            chart_file:     str; absolute path to png file of chart
+            height:         float; height in cm of displayed chart
+            distance:       float; distance in cm from camera of displayed chart
+            scale_start:    float; start value for scaling for chart search
+            scale_stop:     float; stop value for scaling for chart search
+            scale_step:     float; step value for scaling for chart search
+        """
+        self._file = chart_file
+        self._height = height
+        self._distance = distance
+        self._scale_start = scale_start
+        self._scale_stop = scale_stop
+        self._scale_step = scale_step
+
+    def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
+        """Take an image with s, e, & fd to find the chart location.
+
+        Args:
+            cam:            An open device session.
+            props:          Properties of cam
+            fmt:            Image format for the capture
+            s:              Sensitivity for the AF request as defined in
+                            android.sensor.sensitivity
+            e:              Exposure time for the AF request as defined in
+                            android.sensor.exposureTime
+            fd:             float; autofocus lens position
+        Returns:
+            template:       numpy array; chart template for locator
+            img_3a:         numpy array; RGB image for chart location
+            scale_factor:   float; scaling factor for chart search
+        """
+        req = its.objects.manual_capture_request(s, e)
+        req['android.lens.focusDistance'] = fd
+        cap_chart = its.image.stationary_lens_cap(cam, req, fmt)
+        img_3a = its.image.convert_capture_to_rgb_image(cap_chart, props)
+        img_3a = its.image.flip_mirror_img_per_argv(img_3a)
+        its.image.write_image(img_3a, 'af_scene.jpg')
+        template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
+        focal_l = cap_chart['metadata']['android.lens.focalLength']
+        pixel_pitch = (props['android.sensor.info.physicalSize']['height'] /
+                       img_3a.shape[0])
+        print ' Chart distance: %.2fcm' % self._distance
+        print ' Chart height: %.2fcm' % self._height
+        print ' Focal length: %.2fmm' % focal_l
+        print ' Pixel pitch: %.2fum' % (pixel_pitch*1E3)
+        print ' Template height: %dpixels' % template.shape[0]
+        chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
+        scale_factor = template.shape[0] / chart_pixel_h
+        print 'Chart/image scale factor = %.2f' % scale_factor
+        return template, img_3a, scale_factor
+
+    def locate(self, cam, props, fmt, s, e, fd):
+        """Find the chart in the image.
+
+        Args:
+            cam:            An open device session
+            props:          Properties of cam
+            fmt:            Image format for the capture
+            s:              Sensitivity for the AF request as defined in
+                            android.sensor.sensitivity
+            e:              Exposure time for the AF request as defined in
+                            android.sensor.exposureTime
+            fd:             float; autofocus lens position
+
+        Returns:
+            xnorm:          float; [0, 1] left loc of chart in scene
+            ynorm:          float; [0, 1] top loc of chart in scene
+            wnorm:          float; [0, 1] width of chart in scene
+            hnorm:          float; [0, 1] height of chart in scene
+        """
+        chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
+                                                          s, e, fd)
+        scale_start = self._scale_start * s_factor
+        scale_stop = self._scale_stop * s_factor
+        scale_step = self._scale_step * s_factor
+        max_match = []
+        # check for normalized image
+        if numpy.amax(scene) <= 1.0:
+            scene = (scene * 255.0).astype(numpy.uint8)
+        if len(scene.shape) == 2:
+            scene_gray = scene.copy()
+        elif len(scene.shape) == 3:
+            if scene.shape[2] == 1:
+                scene_gray = scene[:, :, 0]
+            else:
+                scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
+        print 'Finding chart in scene...'
+        for scale in numpy.arange(scale_start, scale_stop, scale_step):
+            scene_scaled = scale_img(scene_gray, scale)
+            result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
+            _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
+            # print out scale and match
+            print ' scale factor: %.3f, opt val: %.f' % (scale, opt_val)
+            max_match.append((opt_val, top_left_scaled))
+
+        # determine if optimization results are valid
+        opt_values = [x[0] for x in max_match]
+        if 2.0*min(opt_values) > max(opt_values):
+            estring = ('Unable to find chart in scene!\n'
+                       'Check camera distance and self-reported '
+                       'pixel pitch, focal length and hyperfocal distance.')
+            raise its.error.Error(estring)
+        # find max and draw bbox
+        match_index = max_match.index(max(max_match, key=lambda x: x[0]))
+        scale = scale_start + scale_step * match_index
+        print 'Optimum scale factor: %.3f' %  scale
+        top_left_scaled = max_match[match_index][1]
+        h, w = chart.shape
+        bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
+        top_left = (int(top_left_scaled[0]/scale),
+                    int(top_left_scaled[1]/scale))
+        bottom_right = (int(bottom_right_scaled[0]/scale),
+                        int(bottom_right_scaled[1]/scale))
+        wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
+        hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
+        xnorm = float(top_left[0]) / scene.shape[1]
+        ynorm = float(top_left[1]) / scene.shape[0]
+        return xnorm, ynorm, wnorm, hnorm
+
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+
+    def test_compute_image_sharpness(self):
+        """Unit test for compute_img_sharpness.
+
+        Test by using PNG of ISO12233 chart and blurring intentionally.
+        'sharpness' should drop off by sqrt(2) for 2x blur of image.
+
+        We do one level of blur as PNG image is not perfect.
+        """
+        yuv_full_scale = 1023.0
+        chart_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules',
+                                  'its', 'test_images', 'ISO12233.png')
+        chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
+        white_level = numpy.amax(chart).astype(float)
+        sharpness = {}
+        for j in [2, 4, 8]:
+            blur = cv2.blur(chart, (j, j))
+            blur = blur[:, :, numpy.newaxis]
+            sharpness[j] = (yuv_full_scale *
+                    its.image.compute_image_sharpness(blur / white_level))
+        self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
+                                      numpy.sqrt(2), atol=0.1))
+        self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
+                                      numpy.sqrt(2), atol=0.1))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index d62e610..4d98f9c 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -25,7 +25,6 @@
 import cStringIO
 import scipy.stats
 import copy
-import cv2
 import os
 
 DEFAULT_YUV_TO_RGB_CCM = numpy.matrix([
@@ -747,13 +746,6 @@
     [gy, gx] = numpy.gradient(luma)
     return numpy.average(numpy.sqrt(gy*gy + gx*gx))
 
-
-def scale_img(img, scale=1.0):
-    """Scale and image based on a real number scale factor."""
-    dim = (int(img.shape[1]*scale), int(img.shape[0]*scale))
-    return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
-
-
 def normalize_img(img):
     """Normalize the image values to between 0 and 1.
 
@@ -764,7 +756,6 @@
     """
     return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img))
 
-
 def flip_mirror_img_per_argv(img):
     """Flip/mirror an image if "flip" or "mirror" is in argv
 
@@ -804,138 +795,6 @@
             raise its.error.Error('Cannot settle lens after %d trys!' % trys)
     return cap[NUM_FRAMES-1]
 
-
-class Chart(object):
-    """Definition for chart object.
-
-    Defines PNG reference file, chart size and distance, and scaling range.
-    """
-
-    def __init__(self, chart_file, height, distance, scale_start, scale_stop,
-                 scale_step):
-        """Initial constructor for class.
-
-        Args:
-            chart_file:     str; absolute path to png file of chart
-            height:         float; height in cm of displayed chart
-            distance:       float; distance in cm from camera of displayed chart
-            scale_start:    float; start value for scaling for chart search
-            scale_stop:     float; stop value for scaling for chart search
-            scale_step:     float; step value for scaling for chart search
-        """
-        self._file = chart_file
-        self._height = height
-        self._distance = distance
-        self._scale_start = scale_start
-        self._scale_stop = scale_stop
-        self._scale_step = scale_step
-
-    def _calc_scale_factors(self, cam, props, fmt, s, e, fd):
-        """Take an image with s, e, & fd to find the chart location.
-
-        Args:
-            cam:            An open device session.
-            props:          Properties of cam
-            fmt:            Image format for the capture
-            s:              Sensitivity for the AF request as defined in
-                            android.sensor.sensitivity
-            e:              Exposure time for the AF request as defined in
-                            android.sensor.exposureTime
-            fd:             float; autofocus lens position
-        Returns:
-            template:       numpy array; chart template for locator
-            img_3a:         numpy array; RGB image for chart location
-            scale_factor:   float; scaling factor for chart search
-        """
-        req = its.objects.manual_capture_request(s, e)
-        req['android.lens.focusDistance'] = fd
-        cap_chart = stationary_lens_cap(cam, req, fmt)
-        img_3a = convert_capture_to_rgb_image(cap_chart, props)
-        img_3a = flip_mirror_img_per_argv(img_3a)
-        write_image(img_3a, 'af_scene.jpg')
-        template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
-        focal_l = cap_chart['metadata']['android.lens.focalLength']
-        pixel_pitch = (props['android.sensor.info.physicalSize']['height'] /
-                       img_3a.shape[0])
-        print ' Chart distance: %.2fcm' % self._distance
-        print ' Chart height: %.2fcm' % self._height
-        print ' Focal length: %.2fmm' % focal_l
-        print ' Pixel pitch: %.2fum' % (pixel_pitch*1E3)
-        print ' Template height: %dpixels' % template.shape[0]
-        chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
-        scale_factor = template.shape[0] / chart_pixel_h
-        print 'Chart/image scale factor = %.2f' % scale_factor
-        return template, img_3a, scale_factor
-
-    def locate(self, cam, props, fmt, s, e, fd):
-        """Find the chart in the image.
-
-        Args:
-            cam:            An open device session
-            props:          Properties of cam
-            fmt:            Image format for the capture
-            s:              Sensitivity for the AF request as defined in
-                            android.sensor.sensitivity
-            e:              Exposure time for the AF request as defined in
-                            android.sensor.exposureTime
-            fd:             float; autofocus lens position
-
-        Returns:
-            xnorm:          float; [0, 1] left loc of chart in scene
-            ynorm:          float; [0, 1] top loc of chart in scene
-            wnorm:          float; [0, 1] width of chart in scene
-            hnorm:          float; [0, 1] height of chart in scene
-        """
-        chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt,
-                                                          s, e, fd)
-        scale_start = self._scale_start * s_factor
-        scale_stop = self._scale_stop * s_factor
-        scale_step = self._scale_step * s_factor
-        max_match = []
-        # check for normalized image
-        if numpy.amax(scene) <= 1.0:
-            scene = (scene * 255.0).astype(numpy.uint8)
-        if len(scene.shape) == 2:
-            scene_gray = scene.copy()
-        elif len(scene.shape) == 3:
-            if scene.shape[2] == 1:
-                scene_gray = scene[:, :, 0]
-            else:
-                scene_gray = cv2.cvtColor(scene.copy(), cv2.COLOR_RGB2GRAY)
-        print 'Finding chart in scene...'
-        for scale in numpy.arange(scale_start, scale_stop, scale_step):
-            scene_scaled = scale_img(scene_gray, scale)
-            result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
-            _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
-            # print out scale and match
-            print ' scale factor: %.3f, opt val: %.f' % (scale, opt_val)
-            max_match.append((opt_val, top_left_scaled))
-
-        # determine if optimization results are valid
-        opt_values = [x[0] for x in max_match]
-        if 2.0*min(opt_values) > max(opt_values):
-            estring = ('Unable to find chart in scene!\n'
-                       'Check camera distance and self-reported '
-                       'pixel pitch, focal length and hyperfocal distance.')
-            raise its.error.Error(estring)
-        # find max and draw bbox
-        match_index = max_match.index(max(max_match, key=lambda x: x[0]))
-        scale = scale_start + scale_step * match_index
-        print 'Optimum scale factor: %.3f' %  scale
-        top_left_scaled = max_match[match_index][1]
-        h, w = chart.shape
-        bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
-        top_left = (int(top_left_scaled[0]/scale),
-                    int(top_left_scaled[1]/scale))
-        bottom_right = (int(bottom_right_scaled[0]/scale),
-                        int(bottom_right_scaled[1]/scale))
-        wnorm = float((bottom_right[0]) - top_left[0]) / scene.shape[1]
-        hnorm = float((bottom_right[1]) - top_left[1]) / scene.shape[0]
-        xnorm = float(top_left[0]) / scene.shape[1]
-        ynorm = float(top_left[1]) / scene.shape[0]
-        return xnorm, ynorm, wnorm, hnorm
-
-
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
@@ -974,30 +833,5 @@
         passed = all([math.fabs(y[i] - y_ref[i]) < 0.001 for i in xrange(3)])
         self.assertTrue(passed)
 
-    def test_compute_image_sharpness(self):
-        """Unit test for compute_img_sharpness.
-
-        Test by using PNG of ISO12233 chart and blurring intentionally.
-        'sharpness' should drop off by sqrt(2) for 2x blur of image.
-
-        We do one level of blur as PNG image is not perfect.
-        """
-        yuv_full_scale = 1023.0
-        chart_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'pymodules',
-                                  'its', 'test_images', 'ISO12233.png')
-        chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
-        white_level = numpy.amax(chart).astype(float)
-        sharpness = {}
-        for j in [2, 4, 8]:
-            blur = cv2.blur(chart, (j, j))
-            blur = blur[:, :, numpy.newaxis]
-            sharpness[j] = yuv_full_scale * compute_image_sharpness(blur /
-                                                                    white_level)
-        self.assertTrue(numpy.isclose(sharpness[2]/sharpness[4],
-                                      numpy.sqrt(2), atol=0.1))
-        self.assertTrue(numpy.isclose(sharpness[4]/sharpness[8],
-                                      numpy.sqrt(2), atol=0.1))
-
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index a54eae8..cd563be 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -15,6 +15,7 @@
 import os
 
 import its.caps
+import its.cv2image
 import its.device
 import its.image
 import its.objects
@@ -57,9 +58,9 @@
     """
 
     # initialize chart class
-    chart = its.image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                            CHART_SCALE_START, CHART_SCALE_STOP,
-                            CHART_SCALE_STEP)
+    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+                               CHART_SCALE_START, CHART_SCALE_STOP,
+                               CHART_SCALE_STEP)
 
     # find chart location
     xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index e2bd040..f850e3d 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -15,6 +15,7 @@
 import os
 
 import its.caps
+import its.cv2image
 import its.device
 import its.image
 import its.objects
@@ -58,9 +59,9 @@
     """
 
     # initialize chart class
-    chart = its.image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
-                            CHART_SCALE_START, CHART_SCALE_STOP,
-                            CHART_SCALE_STEP)
+    chart = its.cv2image.Chart(CHART_FILE, CHART_HEIGHT, CHART_DISTANCE,
+                                CHART_SCALE_START, CHART_SCALE_STOP,
+                                CHART_SCALE_STEP)
 
     # find chart location
     xnorm, ynorm, wnorm, hnorm = chart.locate(cam, props, fmt, sensitivity,