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