Pillow/Tests/test_color_lut.py
Jon Dufresne 4de5477b61 Remove unnecessary unittest.main() boilerplate from test files
With the introduction and use of pytest, it is simple and easy to
execute specific tests in isolation through documented command line
arguments. Either by specifying the module path or through the `-k
EXPRESSION` argument. There is no longer any need to provide the
boilerplate:

    if __name__ == '__main__':
        unittest.main()

To every test file. It is simply noise.

The pattern remains in test files that aren't named with `test_*` as
those files are not discovered and executed by pytest by default.
2019-02-03 10:10:16 -08:00

522 lines
22 KiB
Python

from __future__ import division
from array import array
from PIL import Image, ImageFilter
from .helper import unittest, PillowTestCase
try:
import numpy
except ImportError:
numpy = None
class TestColorLut3DCoreAPI(PillowTestCase):
def generate_identity_table(self, channels, size):
if isinstance(size, tuple):
size1D, size2D, size3D = size
else:
size1D, size2D, size3D = (size, size, size)
table = [
[
r / float(size1D-1) if size1D != 1 else 0,
g / float(size2D-1) if size2D != 1 else 0,
b / float(size3D-1) if size3D != 1 else 0,
r / float(size1D-1) if size1D != 1 else 0,
g / float(size2D-1) if size2D != 1 else 0,
][:channels]
for b in range(size3D)
for g in range(size2D)
for r in range(size1D)
]
return (
channels, size1D, size2D, size3D,
[item for sublist in table for item in sublist])
def test_wrong_args(self):
im = Image.new('RGB', (10, 10), 0)
with self.assertRaisesRegex(ValueError, "filter"):
im.im.color_lut_3d('RGB',
Image.CUBIC,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "image mode"):
im.im.color_lut_3d('wrong',
Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
*self.generate_identity_table(5, 3))
with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
*self.generate_identity_table(1, 3))
with self.assertRaisesRegex(ValueError, "table_channels"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
*self.generate_identity_table(2, 3))
with self.assertRaisesRegex(ValueError, "Table size"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
*self.generate_identity_table(3, (1, 3, 3)))
with self.assertRaisesRegex(ValueError, "Table size"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
*self.generate_identity_table(3, (66, 3, 3)))
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
3, 2, 2, 2, [0, 0, 0] * 7)
with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"):
im.im.color_lut_3d('RGB',
Image.LINEAR,
3, 2, 2, 2, [0, 0, 0] * 9)
with self.assertRaises(TypeError):
im.im.color_lut_3d('RGB',
Image.LINEAR,
3, 2, 2, 2, [0, 0, "0"] * 8)
with self.assertRaises(TypeError):
im.im.color_lut_3d('RGB',
Image.LINEAR,
3, 2, 2, 2, 16)
def test_correct_args(self):
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, 3))
im.im.color_lut_3d('CMYK', Image.LINEAR,
*self.generate_identity_table(4, 3))
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (2, 3, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (65, 3, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (3, 65, 3)))
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (3, 3, 65)))
def test_wrong_mode(self):
with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('L', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('L', Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('L', (10, 10), 0)
im.im.color_lut_3d('L', Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 3))
with self.assertRaisesRegex(ValueError, "wrong mode"):
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(4, 3))
def test_correct_mode(self):
im = Image.new('RGBA', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 3))
im = Image.new('RGBA', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 3))
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('HSV', Image.LINEAR,
*self.generate_identity_table(3, 3))
im = Image.new('RGB', (10, 10), 0)
im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 3))
def test_identities(self):
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
# Fast test with small cubes
for size in [2, 3, 5, 7, 11, 16, 17]:
self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, size))))
# Not so fast
self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGB', Image.LINEAR,
*self.generate_identity_table(3, (2, 2, 65)))))
def test_identities_4_channels(self):
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
# Red channel copied to alpha
self.assert_image_equal(
Image.merge('RGBA', (im.split()*2)[:4]),
im._new(im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(4, 17))))
def test_copy_alpha_channel(self):
g = Image.linear_gradient('L')
im = Image.merge('RGBA', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180),
g.transpose(Image.ROTATE_270)])
self.assert_image_equal(im, im._new(
im.im.color_lut_3d('RGBA', Image.LINEAR,
*self.generate_identity_table(3, 17))))
def test_channels_order(self):
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
# Reverse channels by splitting and using table
self.assert_image_equal(
Image.merge('RGB', im.split()[::-1]),
im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2, [
0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 1,
1, 0, 0, 1, 0, 1,
1, 1, 0, 1, 1, 1,
])))
def test_overflow(self):
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2,
[
-1, -1, -1, 2, -1, -1,
-1, 2, -1, 2, 2, -1,
-1, -1, 2, 2, -1, 2,
-1, 2, 2, 2, 2, 2,
])).load()
self.assertEqual(transformed[0, 0], (0, 0, 255))
self.assertEqual(transformed[50, 50], (0, 0, 255))
self.assertEqual(transformed[255, 0], (0, 255, 255))
self.assertEqual(transformed[205, 50], (0, 255, 255))
self.assertEqual(transformed[0, 255], (255, 0, 0))
self.assertEqual(transformed[50, 205], (255, 0, 0))
self.assertEqual(transformed[255, 255], (255, 255, 0))
self.assertEqual(transformed[205, 205], (255, 255, 0))
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
3, 2, 2, 2,
[
-3, -3, -3, 5, -3, -3,
-3, 5, -3, 5, 5, -3,
-3, -3, 5, 5, -3, 5,
-3, 5, 5, 5, 5, 5,
])).load()
self.assertEqual(transformed[0, 0], (0, 0, 255))
self.assertEqual(transformed[50, 50], (0, 0, 255))
self.assertEqual(transformed[255, 0], (0, 255, 255))
self.assertEqual(transformed[205, 50], (0, 255, 255))
self.assertEqual(transformed[0, 255], (255, 0, 0))
self.assertEqual(transformed[50, 205], (255, 0, 0))
self.assertEqual(transformed[255, 255], (255, 255, 0))
self.assertEqual(transformed[205, 205], (255, 255, 0))
class TestColorLut3DFilter(PillowTestCase):
def test_wrong_args(self):
with self.assertRaisesRegex(ValueError, "should be either an integer"):
ImageFilter.Color3DLUT("small", [1])
with self.assertRaisesRegex(ValueError, "should be either an integer"):
ImageFilter.Color3DLUT((11, 11), [1])
with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"):
ImageFilter.Color3DLUT((11, 11, 1), [1])
with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"):
ImageFilter.Color3DLUT((11, 11, 66), [1])
with self.assertRaisesRegex(ValueError, "table should have .+ items"):
ImageFilter.Color3DLUT((3, 3, 3), [1, 1, 1])
with self.assertRaisesRegex(ValueError, "table should have .+ items"):
ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 2)
with self.assertRaisesRegex(ValueError, "should have a length of 4"):
ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 27, channels=4)
with self.assertRaisesRegex(ValueError, "should have a length of 3"):
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8)
with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"):
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2)
def test_convert_table(self):
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
self.assertEqual(tuple(lut.size), (2, 2, 2))
self.assertEqual(lut.name, "Color 3D LUT")
lut = ImageFilter.Color3DLUT((2, 2, 2), [
(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11),
(12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)])
self.assertEqual(tuple(lut.size), (2, 2, 2))
self.assertEqual(lut.table, list(range(24)))
lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8,
channels=4)
self.assertEqual(tuple(lut.size), (2, 2, 2))
self.assertEqual(lut.table, list(range(4)) * 8)
@unittest.skipIf(numpy is None, "Numpy is not installed")
def test_numpy_sources(self):
table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16)
with self.assertRaisesRegex(ValueError, "should have either channels"):
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
table = numpy.ones((7, 6, 5, 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
self.assertIsInstance(lut.table, numpy.ndarray)
self.assertEqual(lut.table.dtype, table.dtype)
self.assertEqual(lut.table.shape, (table.size,))
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
self.assertEqual(lut.table.shape, (table.size,))
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
self.assertEqual(lut.table.shape, (table.size,))
# Check application
Image.new('RGB', (10, 10), 0).filter(lut)
# Check copy
table[0] = 33
self.assertEqual(lut.table[0], 1)
# Check not copy
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table, _copy_table=False)
table[0] = 33
self.assertEqual(lut.table[0], 33)
@unittest.skipIf(numpy is None, "Numpy is not installed")
def test_numpy_formats(self):
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1]
with self.assertRaisesRegex(ValueError, "should have table_channels"):
im.filter(lut)
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = (numpy.array(lut.table, dtype=numpy.float32)
.reshape((7 * 9 * 11), 3))
with self.assertRaisesRegex(ValueError, "should have table_channels"):
im.filter(lut)
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = numpy.array(lut.table, dtype=numpy.float16)
self.assert_image_equal(im, im.filter(lut))
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = numpy.array(lut.table, dtype=numpy.float32)
self.assert_image_equal(im, im.filter(lut))
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = numpy.array(lut.table, dtype=numpy.float64)
self.assert_image_equal(im, im.filter(lut))
lut = ImageFilter.Color3DLUT.generate((7, 9, 11),
lambda r, g, b: (r, g, b))
lut.table = numpy.array(lut.table, dtype=numpy.int32)
im.filter(lut)
lut.table = numpy.array(lut.table, dtype=numpy.int8)
im.filter(lut)
def test_repr(self):
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
self.assertEqual(repr(lut),
"<Color3DLUT from list size=2x2x2 channels=3>")
lut = ImageFilter.Color3DLUT(
(3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)),
channels=4, target_mode='YCbCr', _copy_table=False)
self.assertEqual(
repr(lut),
"<Color3DLUT from array size=3x4x5 channels=4 target_mode=YCbCr>")
class TestGenerateColorLut3D(PillowTestCase):
def test_wrong_channels_count(self):
with self.assertRaisesRegex(ValueError, "3 or 4 output channels"):
ImageFilter.Color3DLUT.generate(
5, channels=2, callback=lambda r, g, b: (r, g, b))
with self.assertRaisesRegex(ValueError, "should have either channels"):
ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r))
with self.assertRaisesRegex(ValueError, "should have either channels"):
ImageFilter.Color3DLUT.generate(
5, channels=4, callback=lambda r, g, b: (r, g, b))
def test_3_channels(self):
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
self.assertEqual(tuple(lut.size), (5, 5, 5))
self.assertEqual(lut.name, "Color 3D LUT")
self.assertEqual(lut.table[:24], [
0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0,
1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0])
def test_4_channels(self):
lut = ImageFilter.Color3DLUT.generate(
5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2))
self.assertEqual(tuple(lut.size), (5, 5, 5))
self.assertEqual(lut.name, "Color 3D LUT")
self.assertEqual(lut.table[:24], [
0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25,
0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125
])
def test_apply(self):
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
g = Image.linear_gradient('L')
im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180)])
self.assertEqual(im, im.filter(lut))
class TestTransformColorLut3D(PillowTestCase):
def test_wrong_args(self):
source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"):
source.transform(lambda r, g, b: (r, g, b), channels=8)
with self.assertRaisesRegex(ValueError, "should have either channels"):
source.transform(lambda r, g, b: (r, g, b), channels=4)
with self.assertRaisesRegex(ValueError, "should have either channels"):
source.transform(lambda r, g, b: (r, g, b, 1))
with self.assertRaises(TypeError):
source.transform(lambda r, g, b, a: (r, g, b))
def test_target_mode(self):
source = ImageFilter.Color3DLUT.generate(
2, lambda r, g, b: (r, g, b), target_mode='HSV')
lut = source.transform(lambda r, g, b: (r, g, b))
self.assertEqual(lut.mode, 'HSV')
lut = source.transform(lambda r, g, b: (r, g, b), target_mode='RGB')
self.assertEqual(lut.mode, 'RGB')
def test_3_to_3_channels(self):
source = ImageFilter.Color3DLUT.generate(
(3, 4, 5), lambda r, g, b: (r, g, b))
lut = source.transform(lambda r, g, b: (r*r, g*g, b*b))
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:10], [
0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
def test_3_to_4_channels(self):
source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r, g, b))
lut = source.transform(lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertNotEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:16], [
0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1,
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1])
def test_4_to_3_channels(self):
source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4)
lut = source.transform(lambda r, g, b, a: (a - r*r, a - g*g, a - b*b),
channels=3)
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertNotEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:18], [
1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0,
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0])
def test_4_to_4_channels(self):
source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4)
lut = source.transform(lambda r, g, b, a: (r*r, g*g, b*b, a - 0.5))
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:16], [
0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5,
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5])
def test_with_normals_3_channels(self):
source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r*r, g*g, b*b))
lut = source.transform(
lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b),
with_normals=True)
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:18], [
0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0,
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0])
def test_with_normals_4_channels(self):
source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
lut = source.transform(
lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a-0.5),
with_normals=True)
self.assertEqual(tuple(lut.size), tuple(source.size))
self.assertEqual(len(lut.table), len(source.table))
self.assertNotEqual(lut.table, source.table)
self.assertEqual(lut.table[0:16], [
0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5,
0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5])