Pillow/Tests/test_image_reduce.py

239 lines
8.5 KiB
Python
Raw Normal View History

import pytest
2019-12-05 22:16:27 +03:00
from PIL import Image, ImageMath, ImageMode
2019-11-26 03:36:58 +03:00
2019-12-05 22:16:27 +03:00
from .helper import PillowTestCase, convert_to_comparable
2019-11-26 03:36:58 +03:00
class TestImageReduce(PillowTestCase):
# There are several internal implementations
remarkable_factors = [
2019-12-05 22:10:44 +03:00
# special implementations
1,
2,
3,
4,
5,
6,
# 1xN implementation
(1, 2),
(1, 3),
(1, 4),
(1, 7),
# Nx1 implementation
(2, 1),
(3, 1),
(4, 1),
(7, 1),
2019-11-26 03:36:58 +03:00
# general implementation with different paths
2019-12-05 22:10:44 +03:00
(4, 6),
(5, 6),
(4, 7),
(5, 7),
(19, 17),
2019-11-26 03:36:58 +03:00
]
2019-12-01 19:12:57 +03:00
@classmethod
def setUpClass(cls):
cls.gradients_image = Image.open("Tests/images/radial_gradients.png")
cls.gradients_image.load()
2019-12-02 04:32:08 +03:00
def test_args_factor(self):
2019-12-01 19:12:57 +03:00
im = Image.new("L", (10, 10))
2019-12-05 22:10:44 +03:00
assert (4, 4) == im.reduce(3).size
assert (4, 10) == im.reduce((3, 1)).size
assert (10, 4) == im.reduce((1, 3)).size
2019-11-26 03:36:58 +03:00
with pytest.raises(ValueError):
2019-11-26 03:36:58 +03:00
im.reduce(0)
with pytest.raises(TypeError):
2019-11-26 03:36:58 +03:00
im.reduce(2.0)
with pytest.raises(ValueError):
2019-11-26 03:36:58 +03:00
im.reduce((0, 10))
2019-12-05 03:24:38 +03:00
def test_args_box(self):
im = Image.new("L", (10, 10))
assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size
assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size
2019-12-05 03:24:38 +03:00
with pytest.raises(TypeError):
2019-12-05 03:24:38 +03:00
im.reduce(2, "stri")
with pytest.raises(TypeError):
2019-12-05 03:24:38 +03:00
im.reduce(2, 2)
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (0, 0, 11, 10))
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (0, 0, 10, 11))
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (-1, 0, 10, 10))
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (0, -1, 10, 10))
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (0, 5, 10, 5))
with pytest.raises(ValueError):
2019-12-05 03:24:38 +03:00
im.reduce(2, (5, 0, 5, 10))
2019-12-01 20:34:05 +03:00
def test_unsupported_modes(self):
im = Image.new("P", (10, 10))
with pytest.raises(ValueError):
2019-12-01 20:34:05 +03:00
im.reduce(3)
im = Image.new("1", (10, 10))
with pytest.raises(ValueError):
2019-12-01 20:34:05 +03:00
im.reduce(3)
im = Image.new("I;16", (10, 10))
with pytest.raises(ValueError):
2019-12-01 20:34:05 +03:00
im.reduce(3)
2019-12-01 19:12:57 +03:00
def get_image(self, mode):
2019-12-01 22:28:16 +03:00
mode_info = ImageMode.getmode(mode)
2019-12-05 22:10:44 +03:00
if mode_info.basetype == "L":
2019-12-01 22:28:16 +03:00
bands = [self.gradients_image]
for _ in mode_info.bands[1:]:
# rotate previous image
band = bands[-1].transpose(Image.ROTATE_90)
bands.append(band)
2019-12-27 14:27:37 +03:00
# Correct alpha channel by transforming completely transparent pixels.
2019-12-01 22:28:16 +03:00
# Low alpha values also emphasize error after alpha multiplication.
2019-12-05 22:10:44 +03:00
if mode.endswith("A"):
2019-12-01 22:28:16 +03:00
bands[-1] = bands[-1].point(lambda x: int(85 + x / 1.5))
2019-12-05 14:30:17 +03:00
im = Image.merge(mode, bands)
2019-12-01 22:28:16 +03:00
else:
assert len(mode_info.bands) == 1
2019-12-05 14:30:17 +03:00
im = self.gradients_image.convert(mode)
2019-12-27 14:27:37 +03:00
# change the height to make a not-square image
2019-12-05 14:30:17 +03:00
return im.crop((0, 0, im.width, im.height - 5))
2019-12-01 19:12:57 +03:00
2019-12-02 04:32:08 +03:00
def compare_reduce_with_box(self, im, factor):
box = (11, 13, 146, 164)
reduced = im.reduce(factor, box=box)
reference = im.crop(box).reduce(factor)
assert reduced == reference
2019-12-02 04:32:08 +03:00
2019-12-01 19:12:57 +03:00
def compare_reduce_with_reference(self, im, factor, average_diff=0.4, max_diff=1):
"""Image.reduce() should look very similar to Image.resize(BOX).
A reference image is compiled from a large source area
and possible last column and last row.
+-----------+
|..........c|
|..........c|
|..........c|
|rrrrrrrrrrp|
+-----------+
"""
reduced = im.reduce(factor)
2019-11-26 03:36:58 +03:00
if not isinstance(factor, (list, tuple)):
factor = (factor, factor)
2019-12-01 19:12:57 +03:00
reference = Image.new(im.mode, reduced.size)
area_size = (im.size[0] // factor[0], im.size[1] // factor[1])
area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1])
area = im.resize(area_size, Image.BOX, area_box)
reference.paste(area, (0, 0))
2019-11-26 03:36:58 +03:00
2019-12-01 19:12:57 +03:00
if area_size[0] < reduced.size[0]:
assert reduced.size[0] - area_size[0] == 1
2019-12-01 19:12:57 +03:00
last_column_box = (area_box[2], 0, im.size[0], area_box[3])
last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box)
reference.paste(last_column, (area_size[0], 0))
2019-11-26 03:36:58 +03:00
2019-12-01 19:12:57 +03:00
if area_size[1] < reduced.size[1]:
assert reduced.size[1] - area_size[1] == 1
2019-12-01 19:12:57 +03:00
last_row_box = (0, area_box[3], area_box[2], im.size[1])
last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box)
reference.paste(last_row, (0, area_size[1]))
2019-11-26 03:36:58 +03:00
2019-12-01 19:57:15 +03:00
if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]:
last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1])
last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box)
reference.paste(last_pixel, area_size)
2019-12-01 22:52:43 +03:00
self.assert_compare_images(reduced, reference, average_diff, max_diff)
2019-12-01 19:12:57 +03:00
def assert_compare_images(self, a, b, max_average_diff, max_diff=255):
assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode)
assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size)
2019-12-01 19:12:57 +03:00
a, b = convert_to_comparable(a, b)
bands = ImageMode.getmode(a.mode).bands
for band, ach, bch in zip(bands, a.split(), b.split()):
ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch)
ch_hist = ch_diff.histogram()
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
2019-12-05 22:10:44 +03:00
a.size[0] * a.size[1]
)
msg = "average pixel value difference {:.4f} > expected {:.4f} "
"for '{}' band".format(average_diff, max_average_diff, band)
assert max_average_diff >= average_diff, msg
2019-12-01 19:12:57 +03:00
last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1]
assert (
max_diff >= last_diff
), "max pixel value difference {} > expected {} for '{}' band".format(
last_diff, max_diff, band
2019-12-01 19:12:57 +03:00
)
2019-11-26 03:36:58 +03:00
2019-12-01 19:57:15 +03:00
def test_mode_L(self):
im = self.get_image("L")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 19:57:15 +03:00
2019-11-26 03:36:58 +03:00
def test_mode_LA(self):
2019-12-01 19:12:57 +03:00
im = self.get_image("LA")
2019-11-26 03:36:58 +03:00
for factor in self.remarkable_factors:
2019-12-01 19:12:57 +03:00
self.compare_reduce_with_reference(im, factor, 0.8, 5)
2019-12-05 22:10:44 +03:00
2019-12-27 14:27:37 +03:00
# With opaque alpha, an error should be way smaller.
2019-12-05 22:10:44 +03:00
im.putalpha(Image.new("L", im.size, 255))
2019-12-01 19:12:57 +03:00
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 19:12:57 +03:00
2019-12-01 19:57:15 +03:00
def test_mode_La(self):
im = self.get_image("La")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 19:57:15 +03:00
2019-12-01 19:12:57 +03:00
def test_mode_RGB(self):
im = self.get_image("RGB")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-11-26 03:36:58 +03:00
def test_mode_RGBA(self):
2019-12-01 19:12:57 +03:00
im = self.get_image("RGBA")
2019-11-26 03:36:58 +03:00
for factor in self.remarkable_factors:
2019-12-01 19:12:57 +03:00
self.compare_reduce_with_reference(im, factor, 0.8, 5)
2019-11-26 03:36:58 +03:00
2019-12-27 14:27:37 +03:00
# With opaque alpha, an error should be way smaller.
2019-12-05 22:10:44 +03:00
im.putalpha(Image.new("L", im.size, 255))
2019-12-01 19:12:57 +03:00
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 19:12:57 +03:00
2019-12-01 19:57:15 +03:00
def test_mode_RGBa(self):
im = self.get_image("RGBa")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 19:57:15 +03:00
2019-12-01 22:52:43 +03:00
def test_mode_I(self):
im = self.get_image("I")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)
2019-12-01 22:52:43 +03:00
2019-12-01 22:28:16 +03:00
def test_mode_F(self):
im = self.get_image("F")
2019-12-01 19:12:57 +03:00
for factor in self.remarkable_factors:
2019-12-01 22:52:43 +03:00
self.compare_reduce_with_reference(im, factor, 0, 0)
2019-12-02 04:32:08 +03:00
self.compare_reduce_with_box(im, factor)