1# Copyright 2023 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Tests for image_processing_utils."""
15
16
17import math
18import os
19import random
20import unittest
21
22import cv2
23import numpy
24from PIL import Image
25
26import image_processing_utils
27
28
29class ImageProcessingUtilsTest(unittest.TestCase):
30  """Unit tests for this module."""
31  _BLUR_LEVEL = 10  # level to see the visible blur in img
32  _CH_FULL_SCALE = 255
33  _SQRT_2 = numpy.sqrt(2)
34  _YUV_FULL_SCALE = 1023
35
36  def test_unpack_raw10_image(self):
37    """Unit test for unpack_raw10_image.
38
39    RAW10 bit packing format
40            bit 7   bit 6   bit 5   bit 4   bit 3   bit 2   bit 1   bit 0
41    Byte 0: P0[9]   P0[8]   P0[7]   P0[6]   P0[5]   P0[4]   P0[3]   P0[2]
42    Byte 1: P1[9]   P1[8]   P1[7]   P1[6]   P1[5]   P1[4]   P1[3]   P1[2]
43    Byte 2: P2[9]   P2[8]   P2[7]   P2[6]   P2[5]   P2[4]   P2[3]   P2[2]
44    Byte 3: P3[9]   P3[8]   P3[7]   P3[6]   P3[5]   P3[4]   P3[3]   P3[2]
45    Byte 4: P3[1]   P3[0]   P2[1]   P2[0]   P1[1]   P1[0]   P0[1]   P0[0]
46    """
47    # Test using a random 4x4 10-bit image
48    img_w, img_h = 4, 4
49    check_list = random.sample(range(0, 1024), img_h*img_w)
50    img_check = numpy.array(check_list).reshape(img_h, img_w)
51
52    # Pack bits
53    for row_start in range(0, len(check_list), img_w):
54      msbs = []
55      lsbs = ''
56      for pixel in range(img_w):
57        val = numpy.binary_repr(check_list[row_start+pixel], 10)
58        msbs.append(int(val[:8], base=2))
59        lsbs = val[8:] + lsbs
60      packed = msbs
61      packed.append(int(lsbs, base=2))
62      chunk_raw10 = numpy.array(packed, dtype='uint8').reshape(1, 5)
63      if row_start == 0:
64        img_raw10 = chunk_raw10
65      else:
66        img_raw10 = numpy.vstack((img_raw10, chunk_raw10))
67
68    # Unpack and check against original
69    self.assertTrue(numpy.array_equal(
70        image_processing_utils.unpack_raw10_image(img_raw10),
71        img_check))
72
73  def test_compute_image_sharpness(self):
74    """Unit test for compute_img_sharpness.
75
76    Tests by using PNG of ISO12233 chart and blurring intentionally.
77    'sharpness' should drop off by sqrt(2) for 2x blur of image.
78
79    We do one level of initial blur as PNG image is not perfect.
80    """
81    blur_levels = [2, 4, 8]
82    chart_file = os.path.join(
83        image_processing_utils.TEST_IMG_DIR, 'ISO12233.png')
84    chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
85    white_level = numpy.amax(chart).astype(float)
86    sharpness = {}
87    for blur in blur_levels:
88      chart_blurred = cv2.blur(chart, (blur, blur))
89      chart_blurred = chart_blurred[:, :, numpy.newaxis]
90      sharpness[blur] = (self._YUV_FULL_SCALE
91                         * image_processing_utils.compute_image_sharpness(
92                             chart_blurred / white_level))
93
94    for i in range(len(blur_levels)-1):
95      self.assertTrue(math.isclose(
96          sharpness[blur_levels[i]]/sharpness[blur_levels[i+1]], self._SQRT_2,
97          abs_tol=0.1))
98
99  def test_apply_lut_to_image(self):
100    """Unit test for apply_lut_to_image.
101
102    Test by using a canned set of values on a 1x1 pixel image.
103    The look-up table should double the value of the index: lut[x] = x*2
104    """
105    ref_image = [0.1, 0.2, 0.3]
106    lut_max = 65536
107    lut = numpy.array([i*2 for i in range(lut_max)])
108    x = numpy.array(ref_image).reshape((1, 1, 3))
109    y = image_processing_utils.apply_lut_to_image(x, lut).reshape(3).tolist()
110    y_ref = [i*2 for i in ref_image]
111    self.assertTrue(numpy.allclose(y, y_ref, atol=1/lut_max))
112
113  def test_p3_img_has_wide_gamut(self):
114    # (255, 0, 0) and (0, 255, 0) in sRGB converted to Display P3
115    srgb_red = numpy.array([[[234, 51, 35]]], dtype='uint8')
116    srgb_green = numpy.array([[[117, 252, 76]]], dtype='uint8')
117
118    # Maximum blue is the same in both sRGB and Display P3
119    blue = numpy.array([[[0, 0, 255]]], dtype='uint8')
120
121    # Max red and green in Display P3
122    p3_red = numpy.array([[[255, 0, 0]]], dtype='uint8')
123    p3_green = numpy.array([[[0, 255, 0]]], dtype='uint8')
124
125    self.assertFalse(image_processing_utils.p3_img_has_wide_gamut(
126        Image.fromarray(srgb_red)))
127
128    self.assertFalse(image_processing_utils.p3_img_has_wide_gamut(
129        Image.fromarray(srgb_green)))
130
131    self.assertFalse(image_processing_utils.p3_img_has_wide_gamut(
132        Image.fromarray(blue)))
133
134    self.assertTrue(image_processing_utils.p3_img_has_wide_gamut(
135        Image.fromarray(p3_red)))
136
137    self.assertTrue(image_processing_utils.p3_img_has_wide_gamut(
138        Image.fromarray(p3_green)))
139
140  def test_compute_slanted_edge_image_sharpness(self):
141    """Unit test for computing slanted edge img sharpness.
142
143    Tests by using PNG of slanted edge and blurring intentionally.
144    'sharpness' should drop off for the blurred image.
145    """
146    chart_file = os.path.join(
147        image_processing_utils.TEST_IMG_DIR, 'slanted_edge.png')
148    chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
149    chart_3d = chart[:, :, numpy.newaxis]
150    sharpness = image_processing_utils.compute_image_sharpness(
151        chart_3d) * self._CH_FULL_SCALE
152    # blurring the chart
153    chart_blurred = cv2.blur(chart, (self._BLUR_LEVEL, self._BLUR_LEVEL))
154    chart_blurred_3d = chart_blurred[:, :, numpy.newaxis]
155    sharpness_blurred = image_processing_utils.compute_image_sharpness(
156        chart_blurred_3d) * self._CH_FULL_SCALE
157    self.assertGreater(sharpness, sharpness_blurred)
158
159
160if __name__ == '__main__':
161  unittest.main()
162