mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-05 06:00:58 +03:00
The previous test configuration made it difficult to run a single test with the pytest CLI. There were two major issues: - The Tests directory was not a package. It now includes a __init__.py file and imports from other tests modules are done with relative imports. - setup.cfg always specified the Tests directory. So even if a specific test were specified as a CLI arg, this configuration would also always include all tests. This configuration has been removed to allow specifying a single test on the command line. Contributors can now run specific tests with a single command such as: $ tox -e py37 -- Tests/test_file_pdf.py::TestFilePdf.test_rgb This makes it easy and faster to iterate on a single test failure and is very familiar to those that have previously used tox and pytest. When running tox or pytest with no arguments, they still discover and runs all tests in the Tests directory.
328 lines
11 KiB
Python
328 lines
11 KiB
Python
# Test the ImageMorphology functionality
|
|
from .helper import unittest, PillowTestCase, hopper
|
|
|
|
from PIL import Image, ImageMorph, _imagingmorph
|
|
|
|
|
|
class MorphTests(PillowTestCase):
|
|
|
|
def setUp(self):
|
|
self.A = self.string_to_img(
|
|
"""
|
|
.......
|
|
.......
|
|
..111..
|
|
..111..
|
|
..111..
|
|
.......
|
|
.......
|
|
"""
|
|
)
|
|
|
|
def img_to_string(self, im):
|
|
"""Turn a (small) binary image into a string representation"""
|
|
chars = '.1'
|
|
width, height = im.size
|
|
return '\n'.join(
|
|
''.join(chars[im.getpixel((c, r)) > 0] for c in range(width))
|
|
for r in range(height))
|
|
|
|
def string_to_img(self, image_string):
|
|
"""Turn a string image representation into a binary image"""
|
|
rows = [s for s in image_string.replace(' ', '').split('\n')
|
|
if len(s)]
|
|
height = len(rows)
|
|
width = len(rows[0])
|
|
im = Image.new('L', (width, height))
|
|
for i in range(width):
|
|
for j in range(height):
|
|
c = rows[j][i]
|
|
v = c in 'X1'
|
|
im.putpixel((i, j), v)
|
|
|
|
return im
|
|
|
|
def img_string_normalize(self, im):
|
|
return self.img_to_string(self.string_to_img(im))
|
|
|
|
def assert_img_equal(self, A, B):
|
|
self.assertEqual(self.img_to_string(A), self.img_to_string(B))
|
|
|
|
def assert_img_equal_img_string(self, A, Bstring):
|
|
self.assertEqual(
|
|
self.img_to_string(A),
|
|
self.img_string_normalize(Bstring))
|
|
|
|
def test_str_to_img(self):
|
|
im = Image.open('Tests/images/morph_a.png')
|
|
self.assert_image_equal(self.A, im)
|
|
|
|
def create_lut(self):
|
|
for op in (
|
|
'corner', 'dilation4', 'dilation8',
|
|
'erosion4', 'erosion8', 'edge'):
|
|
lb = ImageMorph.LutBuilder(op_name=op)
|
|
lut = lb.build_lut()
|
|
with open('Tests/images/%s.lut' % op, 'wb') as f:
|
|
f.write(lut)
|
|
|
|
# create_lut()
|
|
def test_lut(self):
|
|
for op in (
|
|
'corner', 'dilation4', 'dilation8',
|
|
'erosion4', 'erosion8', 'edge'):
|
|
lb = ImageMorph.LutBuilder(op_name=op)
|
|
self.assertIsNone(lb.get_lut())
|
|
|
|
lut = lb.build_lut()
|
|
with open('Tests/images/%s.lut' % op, 'rb') as f:
|
|
self.assertEqual(lut, bytearray(f.read()))
|
|
|
|
def test_no_operator_loaded(self):
|
|
mop = ImageMorph.MorphOp()
|
|
with self.assertRaises(Exception) as e:
|
|
mop.apply(None)
|
|
self.assertEqual(str(e.exception), 'No operator loaded')
|
|
with self.assertRaises(Exception) as e:
|
|
mop.match(None)
|
|
self.assertEqual(str(e.exception), 'No operator loaded')
|
|
with self.assertRaises(Exception) as e:
|
|
mop.save_lut(None)
|
|
self.assertEqual(str(e.exception), 'No operator loaded')
|
|
|
|
# Test the named patterns
|
|
def test_erosion8(self):
|
|
# erosion8
|
|
mop = ImageMorph.MorphOp(op_name='erosion8')
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 8)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.......
|
|
.......
|
|
...1...
|
|
.......
|
|
.......
|
|
.......
|
|
""")
|
|
|
|
def test_dialation8(self):
|
|
# dialation8
|
|
mop = ImageMorph.MorphOp(op_name='dilation8')
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 16)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.11111.
|
|
.11111.
|
|
.11111.
|
|
.11111.
|
|
.11111.
|
|
.......
|
|
""")
|
|
|
|
def test_erosion4(self):
|
|
# erosion4
|
|
mop = ImageMorph.MorphOp(op_name='dilation4')
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 12)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
..111..
|
|
.11111.
|
|
.11111.
|
|
.11111.
|
|
..111..
|
|
.......
|
|
""")
|
|
|
|
def test_edge(self):
|
|
# edge
|
|
mop = ImageMorph.MorphOp(op_name='edge')
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 1)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.......
|
|
..111..
|
|
..1.1..
|
|
..111..
|
|
.......
|
|
.......
|
|
""")
|
|
|
|
def test_corner(self):
|
|
# Create a corner detector pattern
|
|
mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0',
|
|
'4:(00. 01. ...)->1'])
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 5)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.......
|
|
..1.1..
|
|
.......
|
|
..1.1..
|
|
.......
|
|
.......
|
|
""")
|
|
|
|
# Test the coordinate counting with the same operator
|
|
coords = mop.match(self.A)
|
|
self.assertEqual(len(coords), 4)
|
|
self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4)))
|
|
|
|
coords = mop.get_on_pixels(Aout)
|
|
self.assertEqual(len(coords), 4)
|
|
self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4)))
|
|
|
|
def test_mirroring(self):
|
|
# Test 'M' for mirroring
|
|
mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0',
|
|
'M:(00. 01. ...)->1'])
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 7)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.......
|
|
..1.1..
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
""")
|
|
|
|
def test_negate(self):
|
|
# Test 'N' for negate
|
|
mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0',
|
|
'N:(00. 01. ...)->1'])
|
|
count, Aout = mop.apply(self.A)
|
|
self.assertEqual(count, 8)
|
|
self.assert_img_equal_img_string(Aout,
|
|
"""
|
|
.......
|
|
.......
|
|
..1....
|
|
.......
|
|
.......
|
|
.......
|
|
.......
|
|
""")
|
|
|
|
def test_non_binary_images(self):
|
|
im = hopper('RGB')
|
|
mop = ImageMorph.MorphOp(op_name="erosion8")
|
|
|
|
with self.assertRaises(Exception) as e:
|
|
mop.apply(im)
|
|
self.assertEqual(str(e.exception),
|
|
'Image must be binary, meaning it must use mode L')
|
|
with self.assertRaises(Exception) as e:
|
|
mop.match(im)
|
|
self.assertEqual(str(e.exception),
|
|
'Image must be binary, meaning it must use mode L')
|
|
with self.assertRaises(Exception) as e:
|
|
mop.get_on_pixels(im)
|
|
self.assertEqual(str(e.exception),
|
|
'Image must be binary, meaning it must use mode L')
|
|
|
|
def test_add_patterns(self):
|
|
# Arrange
|
|
lb = ImageMorph.LutBuilder(op_name='corner')
|
|
self.assertEqual(lb.patterns, ['1:(... ... ...)->0',
|
|
'4:(00. 01. ...)->1'])
|
|
new_patterns = ['M:(00. 01. ...)->1',
|
|
'N:(00. 01. ...)->1']
|
|
|
|
# Act
|
|
lb.add_patterns(new_patterns)
|
|
|
|
# Assert
|
|
self.assertEqual(
|
|
lb.patterns,
|
|
['1:(... ... ...)->0',
|
|
'4:(00. 01. ...)->1',
|
|
'M:(00. 01. ...)->1',
|
|
'N:(00. 01. ...)->1'])
|
|
|
|
def test_unknown_pattern(self):
|
|
self.assertRaises(
|
|
Exception,
|
|
ImageMorph.LutBuilder, op_name='unknown')
|
|
|
|
def test_pattern_syntax_error(self):
|
|
# Arrange
|
|
lb = ImageMorph.LutBuilder(op_name='corner')
|
|
new_patterns = ['a pattern with a syntax error']
|
|
lb.add_patterns(new_patterns)
|
|
|
|
# Act / Assert
|
|
with self.assertRaises(Exception) as e:
|
|
lb.build_lut()
|
|
self.assertEqual(
|
|
str(e.exception),
|
|
'Syntax error in pattern "a pattern with a syntax error"')
|
|
|
|
def test_load_invalid_mrl(self):
|
|
# Arrange
|
|
invalid_mrl = 'Tests/images/hopper.png'
|
|
mop = ImageMorph.MorphOp()
|
|
|
|
# Act / Assert
|
|
with self.assertRaises(Exception) as e:
|
|
mop.load_lut(invalid_mrl)
|
|
self.assertEqual(str(e.exception),
|
|
'Wrong size operator file!')
|
|
|
|
def test_roundtrip_mrl(self):
|
|
# Arrange
|
|
tempfile = self.tempfile('temp.mrl')
|
|
mop = ImageMorph.MorphOp(op_name='corner')
|
|
initial_lut = mop.lut
|
|
|
|
# Act
|
|
mop.save_lut(tempfile)
|
|
mop.load_lut(tempfile)
|
|
|
|
# Act / Assert
|
|
self.assertEqual(mop.lut, initial_lut)
|
|
|
|
def test_set_lut(self):
|
|
# Arrange
|
|
lb = ImageMorph.LutBuilder(op_name='corner')
|
|
lut = lb.build_lut()
|
|
mop = ImageMorph.MorphOp()
|
|
|
|
# Act
|
|
mop.set_lut(lut)
|
|
|
|
# Assert
|
|
self.assertEqual(mop.lut, lut)
|
|
|
|
def test_wrong_mode(self):
|
|
lut = ImageMorph.LutBuilder(op_name='corner').build_lut()
|
|
imrgb = Image.new('RGB', (10, 10))
|
|
iml = Image.new('L', (10, 10))
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
_imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id)
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
_imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id)
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
_imagingmorph.match(bytes(lut), imrgb.im.id)
|
|
|
|
# Should not raise
|
|
_imagingmorph.match(bytes(lut), iml.im.id)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|