mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	
						commit
						25375a86f8
					
				
							
								
								
									
										236
									
								
								PIL/ImageMorph.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								PIL/ImageMorph.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,236 @@ | ||||||
|  | # A binary morphology add-on for the Python Imaging Library | ||||||
|  | # | ||||||
|  | # History: | ||||||
|  | #   2014-06-04 Initial version. | ||||||
|  | # | ||||||
|  | # Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com> | ||||||
|  | 
 | ||||||
|  | from PIL import Image | ||||||
|  | from PIL import _imagingmorph | ||||||
|  | import re | ||||||
|  | 
 | ||||||
|  | LUT_SIZE = 1<<9 | ||||||
|  | class LutBuilder: | ||||||
|  |     """A class for building MorphLut's from a descriptive language | ||||||
|  | 
 | ||||||
|  |       The input patterns is a list of a strings sequences like these: | ||||||
|  | 
 | ||||||
|  |       4:(... | ||||||
|  |          .1. | ||||||
|  |          111)->1 | ||||||
|  | 
 | ||||||
|  |       (whitespaces including linebreaks are ignored). The option 4 | ||||||
|  |       descibes a series of symmetry operations (in this case a | ||||||
|  |       4-rotation), the pattern is decribed by: | ||||||
|  | 
 | ||||||
|  |          . or X - Ignore | ||||||
|  |          1 - Pixel is on | ||||||
|  |          0 - Pixel is off | ||||||
|  | 
 | ||||||
|  |       The result of the operation is described after "->" string. | ||||||
|  | 
 | ||||||
|  |       The default is to return the current pixel value, which is | ||||||
|  |       returned if no other match is found. | ||||||
|  | 
 | ||||||
|  |       Operations: | ||||||
|  |          4 - 4 way rotation | ||||||
|  |          N - Negate | ||||||
|  |          1 - Dummy op for no other operation (an op must always be given) | ||||||
|  |          M - Mirroring | ||||||
|  | 
 | ||||||
|  |       Example: | ||||||
|  | 
 | ||||||
|  |       lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) | ||||||
|  |       lut = lb.build_lut() | ||||||
|  |           | ||||||
|  |     """ | ||||||
|  |     def __init__(self,patterns = None,op_name=None): | ||||||
|  |         if patterns is not None: | ||||||
|  |             self.patterns = patterns | ||||||
|  |         else: | ||||||
|  |             self.patterns = [] | ||||||
|  |         self.lut = None | ||||||
|  |         if op_name is not None: | ||||||
|  |             known_patterns = { | ||||||
|  |                 'corner' : ['1:(... ... ...)->0', | ||||||
|  |                             '4:(00. 01. ...)->1'], | ||||||
|  |                 'dilation4' : ['4:(... .0. .1.)->1'], | ||||||
|  |                 'dilation8' : ['4:(... .0. .1.)->1', | ||||||
|  |                                '4:(... .0. ..1)->1'], | ||||||
|  |                 'erosion4' : ['4:(... .1. .0.)->0'], | ||||||
|  |                 'erosion8' : ['4:(... .1. .0.)->0', | ||||||
|  |                               '4:(... .1. ..0)->0'], | ||||||
|  |                 'edge' : ['1:(... ... ...)->0', | ||||||
|  |                           '4:(.0. .1. ...)->1', | ||||||
|  |                           '4:(01. .1. ...)->1'] | ||||||
|  |             } | ||||||
|  |             if not op_name in known_patterns: | ||||||
|  |                 raise Exception('Unknown pattern '+op_name+'!') | ||||||
|  | 
 | ||||||
|  |             self.patterns = known_patterns[op_name] | ||||||
|  | 
 | ||||||
|  |     def add_patterns(self, patterns): | ||||||
|  |         self.patterns += patterns | ||||||
|  | 
 | ||||||
|  |     def build_default_lut(self): | ||||||
|  |         symbols = [0, 1] | ||||||
|  |         m = 1 << 4  # pos of current pixel | ||||||
|  |         self.lut = bytearray([symbols[(i & m)>0] for i in range(LUT_SIZE)]) | ||||||
|  | 
 | ||||||
|  |     def get_lut(self): | ||||||
|  |         return self.lut | ||||||
|  | 
 | ||||||
|  |     def _string_permute(self, pattern, permutation): | ||||||
|  |         """string_permute takes a pattern and a permutation and returns the | ||||||
|  |         string permuted accordinging to the permutation list. | ||||||
|  |         """ | ||||||
|  |         assert(len(permutation)==9) | ||||||
|  |         return ''.join([pattern[p] for p in permutation]) | ||||||
|  | 
 | ||||||
|  |     def _pattern_permute(self, basic_pattern, options, basic_result): | ||||||
|  |         """pattern_permute takes a basic pattern and its result and clones | ||||||
|  |         the mattern according to the modifications described in the $options | ||||||
|  |         parameter. It returns a list of all cloned patterns.""" | ||||||
|  |         patterns = [(basic_pattern, basic_result)] | ||||||
|  | 
 | ||||||
|  |         # rotations | ||||||
|  |         if '4' in options: | ||||||
|  |             res = patterns[-1][1] | ||||||
|  |             for i in range(4): | ||||||
|  |                 patterns.append( | ||||||
|  |                     (self._string_permute(patterns[-1][0], | ||||||
|  |                                     [6,3,0, | ||||||
|  |                                      7,4,1, | ||||||
|  |                                      8,5,2]), res)) | ||||||
|  |         # mirror | ||||||
|  |         if 'M' in options: | ||||||
|  |             n = len(patterns) | ||||||
|  |             for pattern,res in patterns[0:n]: | ||||||
|  |                 patterns.append( | ||||||
|  |                     (self._string_permute(pattern, [2,1,0, | ||||||
|  | 					      5,4,3, | ||||||
|  | 					      8,7,6]), res)) | ||||||
|  | 
 | ||||||
|  |         # negate | ||||||
|  |         if 'N' in options: | ||||||
|  |             n = len(patterns) | ||||||
|  |             for pattern,res in patterns[0:n]: | ||||||
|  |                 # Swap 0 and 1 | ||||||
|  |                 pattern = (pattern | ||||||
|  |                            .replace('0','Z') | ||||||
|  |                            .replace('1','0') | ||||||
|  |                            .replace('Z','1')) | ||||||
|  |                 res = '%d'%(1-int(res)) | ||||||
|  |                 patterns.append((pattern, res)) | ||||||
|  | 
 | ||||||
|  |         return patterns | ||||||
|  | 
 | ||||||
|  |     def build_lut(self): | ||||||
|  |         """Compile all patterns into a morphology lut. | ||||||
|  | 
 | ||||||
|  |         TBD :Build based on (file) morphlut:modify_lut | ||||||
|  |         """ | ||||||
|  |         self.build_default_lut() | ||||||
|  |         patterns = [] | ||||||
|  | 
 | ||||||
|  |         # Parse and create symmetries of the patterns strings | ||||||
|  |         for p in self.patterns: | ||||||
|  |             m = re.search(r'(\w*):?\s*\((.+?)\)\s*->\s*(\d)', p.replace('\n','')) | ||||||
|  |             if not m: | ||||||
|  |                 raise Exception('Syntax error in pattern "'+p+'"') | ||||||
|  |             options = m.group(1) | ||||||
|  |             pattern = m.group(2) | ||||||
|  |             result = int(m.group(3)) | ||||||
|  | 
 | ||||||
|  |             # Get rid of spaces | ||||||
|  |             pattern= pattern.replace(' ','').replace('\n','') | ||||||
|  | 
 | ||||||
|  |             patterns += self._pattern_permute(pattern, options, result) | ||||||
|  | 
 | ||||||
|  | #        # Debugging | ||||||
|  | #        for p,r in patterns: | ||||||
|  | #            print p,r | ||||||
|  | #        print '--' | ||||||
|  | 
 | ||||||
|  |         # compile the patterns into regular expressions for speed | ||||||
|  |         for i in range(len(patterns)): | ||||||
|  |             p = patterns[i][0].replace('.','X').replace('X','[01]') | ||||||
|  |             p = re.compile(p) | ||||||
|  |             patterns[i] = (p, patterns[i][1]) | ||||||
|  | 
 | ||||||
|  |         # Step through table and find patterns that match. | ||||||
|  |         # Note that all the patterns are searched. The last one | ||||||
|  |         # caught overrides | ||||||
|  |         for i in range(LUT_SIZE): | ||||||
|  |             # Build the bit pattern | ||||||
|  |             bitpattern = bin(i)[2:] | ||||||
|  |             bitpattern = ('0'*(9-len(bitpattern)) + bitpattern)[::-1] | ||||||
|  | 
 | ||||||
|  |             for p,r in patterns: | ||||||
|  |                 if p.match(bitpattern): | ||||||
|  |                     self.lut[i] = [0, 1][r] | ||||||
|  | 
 | ||||||
|  |         return self.lut | ||||||
|  |          | ||||||
|  | class MorphOp: | ||||||
|  |     """A class for binary morphological operators""" | ||||||
|  | 
 | ||||||
|  |     def __init__(self, | ||||||
|  |                  lut=None, | ||||||
|  |                  op_name = None, | ||||||
|  |                  patterns = None): | ||||||
|  |         """Create a binary morphological operator""" | ||||||
|  |         self.lut = lut | ||||||
|  |         if op_name is not None: | ||||||
|  |             self.lut = LutBuilder(op_name = op_name).build_lut() | ||||||
|  |         elif patterns is not None: | ||||||
|  |             self.lut = LutBuilder(patterns = patterns).build_lut() | ||||||
|  | 
 | ||||||
|  |     def apply(self, image): | ||||||
|  |         """Run a single morphological operation on an image | ||||||
|  | 
 | ||||||
|  |         Returns a tuple of the number of changed pixels and the | ||||||
|  |         morphed image""" | ||||||
|  |         if self.lut is None: | ||||||
|  |             raise Exception('No operator loaded') | ||||||
|  |          | ||||||
|  |         outimage = Image.new(image.mode, image.size, None) | ||||||
|  |         count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) | ||||||
|  |         return count, outimage | ||||||
|  |      | ||||||
|  |     def match(self, image): | ||||||
|  |         """Get a list of coordinates matching the morphological operation on an image | ||||||
|  | 
 | ||||||
|  |         Returns a list of tuples of (x,y) coordinates of all matching pixels.""" | ||||||
|  |         if self.lut is None: | ||||||
|  |             raise Exception('No operator loaded') | ||||||
|  |          | ||||||
|  |         return  _imagingmorph.match(bytes(self.lut), image.im.id) | ||||||
|  | 
 | ||||||
|  |     def get_on_pixels(self, image): | ||||||
|  |         """Get a list of all turned on pixels in a binary image | ||||||
|  |         Returns a list of tuples of (x,y) coordinates of all matching pixels.""" | ||||||
|  |          | ||||||
|  |         return  _imagingmorph.get_on_pixels(image.im.id) | ||||||
|  | 
 | ||||||
|  |     def load_lut(self, filename): | ||||||
|  |         """Load an operator from an mrl file""" | ||||||
|  |         with open(filename,'rb') as f: | ||||||
|  |             self.lut = bytearray(f.read()) | ||||||
|  |              | ||||||
|  |         if len(self.lut)!= 8192: | ||||||
|  |             self.lut = None | ||||||
|  |             raise Exception('Wrong size operator file!') | ||||||
|  | 
 | ||||||
|  |     def save_lut(self, filename): | ||||||
|  |         """Load an operator save mrl file""" | ||||||
|  |         if self.lut is None: | ||||||
|  |             raise Exception('No operator loaded') | ||||||
|  |         with open(filename,'wb') as f: | ||||||
|  |             f.write(self.lut) | ||||||
|  | 
 | ||||||
|  |     def set_lut(self, lut): | ||||||
|  |         """Set the lut from an external source""" | ||||||
|  |         self.lut = lut | ||||||
|  | 
 | ||||||
|  |      | ||||||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/corner.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/corner.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/dilation4.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/dilation4.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/dilation8.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/dilation8.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/edge.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/edge.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/erosion4.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/erosion4.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/erosion8.lut
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/erosion8.lut
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/morph_a.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/morph_a.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 83 B | 
							
								
								
									
										169
									
								
								Tests/test_imagemorph.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								Tests/test_imagemorph.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,169 @@ | ||||||
|  | # Test the ImageMorphology functionality | ||||||
|  | from helper import * | ||||||
|  | 
 | ||||||
|  | from PIL import Image | ||||||
|  | from PIL import ImageMorph | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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(self) | ||||||
|  |             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) | ||||||
|  |             lut = lb.build_lut() | ||||||
|  |             with open('Tests/images/%s.lut' % op , 'rb') as f: | ||||||
|  |                 self.assertEqual(lut, bytearray(f.read())) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     # 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))) | ||||||
							
								
								
									
										303
									
								
								_imagingmorph.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								_imagingmorph.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,303 @@ | ||||||
|  | /*
 | ||||||
|  |  * The Python Imaging Library | ||||||
|  |  * | ||||||
|  |  * A binary morphology add-on for the Python Imaging Library | ||||||
|  |  * | ||||||
|  |  * History: | ||||||
|  |  *   2014-06-04 Initial version. | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com> | ||||||
|  |  * | ||||||
|  |  * See the README file for information on usage and redistribution. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "Python.h" | ||||||
|  | #include "Imaging.h" | ||||||
|  | #include "py3.h" | ||||||
|  | 
 | ||||||
|  | #define LUT_SIZE (1<<9) | ||||||
|  | 
 | ||||||
|  | /* Apply a morphologic LUT to a binary image. Outputs a
 | ||||||
|  |    a new binary image. | ||||||
|  | 
 | ||||||
|  |    Expected parameters: | ||||||
|  | 
 | ||||||
|  |       1. a LUT - a 512 byte size lookup table. | ||||||
|  |       2. an input Imaging image id. | ||||||
|  |       3. an output Imaging image id | ||||||
|  | 
 | ||||||
|  |    Returns number of changed pixels. | ||||||
|  | */ | ||||||
|  | static PyObject* | ||||||
|  | apply(PyObject *self, PyObject* args) | ||||||
|  | { | ||||||
|  |     const char *lut; | ||||||
|  |     PyObject *py_lut; | ||||||
|  |     Py_ssize_t lut_len, i0, i1; | ||||||
|  |     Imaging imgin, imgout; | ||||||
|  |     int width, height; | ||||||
|  |     int row_idx, col_idx; | ||||||
|  |     UINT8 **inrows, **outrows; | ||||||
|  |     int num_changed_pixels = 0; | ||||||
|  | 
 | ||||||
|  |     if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!PyBytes_Check(py_lut)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lut_len = PyBytes_Size(py_lut); | ||||||
|  | 
 | ||||||
|  |     if (lut_len < LUT_SIZE) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lut = PyBytes_AsString(py_lut); | ||||||
|  | 
 | ||||||
|  |     imgin = (Imaging) i0; | ||||||
|  |     imgout = (Imaging) i1; | ||||||
|  |     width = imgin->xsize; | ||||||
|  |     height = imgin->ysize; | ||||||
|  | 
 | ||||||
|  |     if (imgin->type != IMAGING_TYPE_UINT8 && | ||||||
|  |         imgin->bands != 1) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |     if (imgout->type != IMAGING_TYPE_UINT8 && | ||||||
|  |         imgout->bands != 1) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     inrows = imgin->image8; | ||||||
|  |     outrows = imgout->image8; | ||||||
|  | 
 | ||||||
|  |     for (row_idx=0; row_idx < height; row_idx++) { | ||||||
|  |         UINT8 *outrow = outrows[row_idx]; | ||||||
|  |         UINT8 *inrow = inrows[row_idx]; | ||||||
|  |         UINT8 *prow, *nrow; /* Previous and next row */ | ||||||
|  | 
 | ||||||
|  |         /* zero boundary conditions. TBD support other modes */ | ||||||
|  |         outrow[0] = outrow[width-1] = 0; | ||||||
|  |         if (row_idx==0 || row_idx == height-1) { | ||||||
|  |             for(col_idx=0; col_idx<width; col_idx++) | ||||||
|  |                 outrow[col_idx] = 0; | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         prow = inrows[row_idx-1]; | ||||||
|  |         nrow = inrows[row_idx+1]; | ||||||
|  | 
 | ||||||
|  |         for (col_idx=1; col_idx<width-1; col_idx++) { | ||||||
|  |             int cim = col_idx-1; | ||||||
|  |             int cip = col_idx+1; | ||||||
|  |             unsigned char b0 = prow[cim] &1; | ||||||
|  |             unsigned char b1 = prow[col_idx]&1; | ||||||
|  |             unsigned char b2 = prow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             unsigned char b3 = inrow[cim]&1; | ||||||
|  |             unsigned char b4 = inrow[col_idx]&1; | ||||||
|  |             unsigned char b5 = inrow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             unsigned char b6 = nrow[cim]&1; | ||||||
|  |             unsigned char b7 = nrow[col_idx]&1; | ||||||
|  |             unsigned char b8 = nrow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             int lut_idx = (b0  | ||||||
|  |                            |(b1 << 1) | ||||||
|  |                            |(b2 << 2) | ||||||
|  |                            |(b3 << 3) | ||||||
|  |                            |(b4 << 4) | ||||||
|  |                            |(b5 << 5) | ||||||
|  |                            |(b6 << 6) | ||||||
|  |                            |(b7 << 7) | ||||||
|  |                            |(b8 << 8)); | ||||||
|  |             outrow[col_idx] = 255*(lut[lut_idx]&1); | ||||||
|  |             num_changed_pixels += ((b4&1)!=(outrow[col_idx]&1)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return Py_BuildValue("i",num_changed_pixels); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Match a morphologic LUT to a binary image and return a list
 | ||||||
|  |    of the coordinates of all all matching pixels. | ||||||
|  | 
 | ||||||
|  |    Expected parameters: | ||||||
|  | 
 | ||||||
|  |       1. a LUT - a 512 byte size lookup table. | ||||||
|  |       2. an input Imaging image id. | ||||||
|  | 
 | ||||||
|  |    Returns list of matching pixels. | ||||||
|  | */ | ||||||
|  | static PyObject* | ||||||
|  | match(PyObject *self, PyObject* args) | ||||||
|  | { | ||||||
|  |     const char *lut; | ||||||
|  |     PyObject *py_lut; | ||||||
|  |     Py_ssize_t lut_len, i0; | ||||||
|  |     Imaging imgin; | ||||||
|  |     int width, height; | ||||||
|  |     int row_idx, col_idx; | ||||||
|  |     UINT8 **inrows; | ||||||
|  |     PyObject *ret = PyList_New(0); | ||||||
|  | 
 | ||||||
|  |     if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!PyBytes_Check(py_lut)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lut_len = PyBytes_Size(py_lut); | ||||||
|  | 
 | ||||||
|  |     if (lut_len < LUT_SIZE) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     lut = PyBytes_AsString(py_lut); | ||||||
|  |     imgin = (Imaging) i0; | ||||||
|  | 
 | ||||||
|  |     if (imgin->type != IMAGING_TYPE_UINT8 && | ||||||
|  |         imgin->bands != 1) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     inrows = imgin->image8; | ||||||
|  |     width = imgin->xsize; | ||||||
|  |     height = imgin->ysize; | ||||||
|  | 
 | ||||||
|  |     for (row_idx=1; row_idx < height-1; row_idx++) { | ||||||
|  |         UINT8 *inrow = inrows[row_idx]; | ||||||
|  |         UINT8 *prow, *nrow; | ||||||
|  | 
 | ||||||
|  |         prow = inrows[row_idx-1]; | ||||||
|  |         nrow = inrows[row_idx+1]; | ||||||
|  | 
 | ||||||
|  |         for (col_idx=1; col_idx<width-1; col_idx++) { | ||||||
|  |             int cim = col_idx-1; | ||||||
|  |             int cip = col_idx+1; | ||||||
|  |             unsigned char b0 = prow[cim] &1; | ||||||
|  |             unsigned char b1 = prow[col_idx]&1; | ||||||
|  |             unsigned char b2 = prow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             unsigned char b3 = inrow[cim]&1; | ||||||
|  |             unsigned char b4 = inrow[col_idx]&1; | ||||||
|  |             unsigned char b5 = inrow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             unsigned char b6 = nrow[cim]&1; | ||||||
|  |             unsigned char b7 = nrow[col_idx]&1; | ||||||
|  |             unsigned char b8 = nrow[cip]&1; | ||||||
|  | 
 | ||||||
|  |             int lut_idx = (b0  | ||||||
|  |                            |(b1 << 1) | ||||||
|  |                            |(b2 << 2) | ||||||
|  |                            |(b3 << 3) | ||||||
|  |                            |(b4 << 4) | ||||||
|  |                            |(b5 << 5) | ||||||
|  |                            |(b6 << 6) | ||||||
|  |                            |(b7 << 7) | ||||||
|  |                            |(b8 << 8)); | ||||||
|  |             if (lut[lut_idx]) { | ||||||
|  |                 PyObject *coordObj = Py_BuildValue("(nn)",col_idx,row_idx); | ||||||
|  |                 PyList_Append(ret, coordObj); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Return a list of the coordinates of all turned on pixels in an image.
 | ||||||
|  |    May be used to extract features after a sequence of MorphOp's were applied. | ||||||
|  |    This is faster than match as only 1x1 lookup is made. | ||||||
|  | */ | ||||||
|  | static PyObject* | ||||||
|  | get_on_pixels(PyObject *self, PyObject* args) | ||||||
|  | { | ||||||
|  |     Py_ssize_t i0; | ||||||
|  |     Imaging img; | ||||||
|  |     UINT8 **rows; | ||||||
|  |     int row_idx, col_idx; | ||||||
|  |     int width, height; | ||||||
|  |     PyObject *ret = PyList_New(0); | ||||||
|  |    | ||||||
|  |     if (!PyArg_ParseTuple(args, "n", &i0)) { | ||||||
|  |         PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); | ||||||
|  | 
 | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |     img = (Imaging) i0; | ||||||
|  |     rows = img->image8; | ||||||
|  |     width = img->xsize; | ||||||
|  |     height = img->ysize; | ||||||
|  |    | ||||||
|  |     for (row_idx=0; row_idx < height; row_idx++) { | ||||||
|  |         UINT8 *row = rows[row_idx]; | ||||||
|  |         for (col_idx=0; col_idx<width; col_idx++) { | ||||||
|  |             if (row[col_idx]) { | ||||||
|  |                 PyObject *coordObj = Py_BuildValue("(nn)",col_idx,row_idx); | ||||||
|  |                 PyList_Append(ret, coordObj); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | static int | ||||||
|  | setup_module(PyObject* m) | ||||||
|  | { | ||||||
|  |     PyObject* d = PyModule_GetDict(m); | ||||||
|  | 
 | ||||||
|  |     PyDict_SetItemString(d, "__version", PyUnicode_FromString("0.1")); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static PyMethodDef functions[] = { | ||||||
|  |     /* Functions */ | ||||||
|  |     {"apply", (PyCFunction)apply, 1}, | ||||||
|  |     {"get_on_pixels", (PyCFunction)get_on_pixels, 1}, | ||||||
|  |     {"match", (PyCFunction)match, 1}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #if PY_VERSION_HEX >= 0x03000000 | ||||||
|  | PyMODINIT_FUNC | ||||||
|  | PyInit__imagingmorph(void) { | ||||||
|  |     PyObject* m; | ||||||
|  | 
 | ||||||
|  |     static PyModuleDef module_def = { | ||||||
|  |         PyModuleDef_HEAD_INIT, | ||||||
|  |         "_imagingmorph",         /* m_name */ | ||||||
|  |         "A module for doing image morphology",               /* m_doc */ | ||||||
|  |         -1,                 /* m_size */ | ||||||
|  |         functions,          /* m_methods */ | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     m = PyModule_Create(&module_def); | ||||||
|  | 
 | ||||||
|  |     if (setup_module(m) < 0) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     return m; | ||||||
|  | } | ||||||
|  | #else | ||||||
|  | PyMODINIT_FUNC | ||||||
|  | init_imagingmorph(void) | ||||||
|  | { | ||||||
|  |     PyObject* m = Py_InitModule("_imagingmorph", functions); | ||||||
|  |     setup_module(m); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
							
								
								
									
										3
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -571,6 +571,9 @@ class pil_build_ext(build_ext): | ||||||
|         if os.path.isfile("_imagingmath.c"): |         if os.path.isfile("_imagingmath.c"): | ||||||
|             exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) |             exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) | ||||||
| 
 | 
 | ||||||
|  |         if os.path.isfile("_imagingmorph.c"): | ||||||
|  |             exts.append(Extension("PIL._imagingmorph", ["_imagingmorph.c"])) | ||||||
|  | 
 | ||||||
|         self.extensions[:] = exts |         self.extensions[:] = exts | ||||||
| 
 | 
 | ||||||
|         build_ext.build_extensions(self) |         build_ext.build_extensions(self) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user