File Coverage

blib/lib/Image/Find/Loops.pm
Criterion Covered Total %
statement 293 295 99.3
branch 71 80 88.7
condition 92 102 90.2
subroutine 34 34 100.0
pod 14 16 87.5
total 504 527 95.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #-------------------------------------------------------------------------------
3             # Image::Find::Loops - Find loops in an image.
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd Inc., 2018
5             #-------------------------------------------------------------------------------
6             # Estimate thickness of loop
7             package Image::Find::Loops;
8             our $VERSION = "20180506";
9             require v5.16;
10 1     1   721 use warnings FATAL => qw(all);
  1         9  
  1         41  
11 1     1   6 use strict;
  1         2  
  1         37  
12 1     1   7 use Carp qw(confess);
  1         2  
  1         89  
13 1     1   565 use Data::Dump qw(dump);
  1         14976  
  1         83  
14 1     1   947 use Data::Table::Text qw(:all);
  1         77315  
  1         667  
15 1     1   11 use utf8;
  1         3  
  1         6  
16              
17 60     60 0 189 sub indent{4} # Print indentation amount
18              
19             #1 Methods # Find loops in an image.
20              
21             sub new($) #S Find loops in an image represented as a string.
22 1     1 1 4 {my ($string) = @_; # String of blanks; non blanks; new lines defining the image
23 1         12 my @lines = split /\n/, $string;
24 1         4 my $count; # Number of active pixels
25             my %image; # {x}{y} of active pixels
26 1         0 my $x; # Image dimension in x
27              
28 1         5 for my $j(0..$#lines) # Load active pixels
29 15         33 {my $line = $lines[$j];
30 15 100 100     70 $x = length($line) if !defined($x) or length($line) > $x; # Longest line
31 15         39 for my $i(0..length($line)-1) # Parse each line
32 904 100       2341 {$image{$i}{$j} = 0, $count++ if substr($line, $i, 1) ne q( ); # Areas not in drawn loops
33             }
34             }
35              
36 1         20 my $d = bless{image=>\%image, x=>$x, y=>scalar(@lines), count=>$count, # Create image of loops
37             bounds=>{}, partitions=>{}, partitionLoop=>{}};
38              
39 1         8 $d->partitionImage(1); # Partition the image ignoring stray pixels
40 1         26 $d->fillPartition(1); # Fill the interior of each parition
41 1         11 $d->removeInteriorOfPartition($_) for 1..$d->numberOfLoops; # Remove the interior of each partition except at the edges to leave the exterior loop
42 1         7 $d->findLongestLoop($_) for 1..$d->numberOfLoops; # Find the longest path in the exterior edge - this must be the loop described by the partition
43 1         16 $d->widthOfLoop($_) for 1..$d->numberOfLoops; # Add Loop width estimate at each point
44 1         47 $d # Return new image with path details
45             } # new
46              
47              
48             sub fillPartition($$) #P Remove any interior voids in a partition.
49 1     1 1 5 {my ($i, $partition) = @_; # Image, partition
50 1         24 my $p = $i->partitions->{$partition}; # Pixels in partition
51 1         29 my $b = $i->bounds->{$partition}; # Rectangular bounding area of partition
52 1         10 my ($x1, $y1, $x2, $y2) = @$b; # Rectangle bounding the partition
53 1         3 my %image; # The image of the inverted partition restricted to the bounding arae
54 1         3 my $count = 0; # Number of pixels in th image of the inverted partition
55              
56 1         5 for my $x($x1..$x2) # Define the image of the inverted partition
57 6         13 {for my $y($y1..$y2)
58 42 100       102 {next if defined $p->{$x}{$y};
59 18         41 $image{$x}{$y} = 0; ++$count;
  18         36  
60             }
61             }
62              
63 1         16 my $I = bless
64             {%$i, image=>\%image, count=>$count, bounds=>{1=>$b}, partitions=>{}}; # Create image of inverted partition
65              
66 1         41 $I->partitionImage(0); # Partition the image of the inverted partition considering partitions of all sizes
67              
68 1         3 my %ignore; # Ignore the partitions of the inverted image that touch the border of the original partition as the touch confirms that they are part of the exterior
69 1         4 for my $invertedPartition(sort keys %{$I->partitions}) # Each partition of the image of the inverted partition
  1         23  
70 5         131 {my $p = $I->partitions->{$invertedPartition};
71             First: # Each pixel in the current partition of the inverted partition
72 5         43 for my $x(keys % $p)
73 6         9 {for my $y(keys %{$p->{$x}})
  6         21  
74 12 100 100     66 {if ($x == $x1 || $x == $x2 and $y == $y1 || $y == $y2) # This partition touches the border so we know that all of this partition is part of the exterior
      100        
      100        
75 4         11 {$ignore{$invertedPartition}++;
76 4         14 last First;
77             }
78             }
79             }
80             }
81              
82 1         4 for my $invertedPartition(sort keys %{$I->partitions}) # Fill in the internal partitions in the original partition so that any internal voids become filled
  1         24  
83 5 100       43 {next if $ignore{$invertedPartition}; # Ignore exterior partitions to leave just the interior partitions
84 1         22 my $P = $I->partitions->{$invertedPartition};
85 1         9 for my $x(keys % $P) # Each pixel of an interior partition of the inverted original partition
86 2         5 {for my $y(keys %{$P->{$x}})
  2         6  
87 6         18 {$p->{$x}{$y} = $partition; # Fill in an interior pixel that was void in the original partition
88             }
89             }
90             }
91             } # fillPartition
92              
93             sub clone($) #P Clone an image.
94 1     1 1 4 {my ($i) = @_; # Image
95              
96 1         2 my %partitions; # Clone partitions
97 1         1 for my $p(keys %{$i->partitions})
  1         17  
98 4         12 {for my $x(keys %{$i->partitions->{$p}})
  4         55  
99 58         85 {for my $y(keys %{$i->partitions->{$p}{$x}})
  58         803  
100 221         578 {$partitions{$p}{$x}{$y}++# = $i->partitions->{$p}{$x}{$y};
101             }
102             }
103             }
104              
105 1         17 bless {%$i, partitions=>\%partitions}; # Cloned image
106             } # clone
107              
108             sub clonePartition($$) #P Clone a partition of an image.
109 12     12 1 34 {my ($i, $partition) = @_; # Image, partition
110 12         20 my %partition; # Cloned partition
111              
112 12         21 for my $x(keys %{$i->partitions->{$partition}})
  12         259  
113 174         1238 {for my $y(keys %{$i->partitions->{$partition}{$x}})
  174         3209  
114 683         16269 {$partition{$x}{$y} = $i->partitions->{$partition}{$x}{$y};
115             }
116             }
117              
118 12         157 my $I = bless {%$i}; # Clone image quickly
119 12         34 $I->partitions = {%{$i->partitions}}; # Clone partitions quickly
  12         241  
120 12         371 $I->partitions->{$partition} = \%partition; # Replace cloned partition
121 12         85 $I # Return new image
122             } # clonePartition
123              
124             sub numberOfLoops($) # Number of loops in the image. The partitions and loops are numbered from 1.
125 19     19 1 85 {my ($i) = @_; # Image
126 19         30 scalar(keys %{$i->partitions})
  19         405  
127             } # numberOfLoops
128              
129             sub partitionImage($$) #P Partition the images into disjoint sets of connected points.
130 2     2 1 17 {my ($i, $small) = @_; # Image, minimum size of a partition - smaller partitions will be ignored
131              
132 2         7 for my $x(sort{$a<=>$b} keys %{$i->image}) # Stabilize partition numbers to make testing possible
  251         454  
  2         68  
133 61         163 {for my $y(sort{$a<=>$b} keys %{$i->image->{$x}})
  436         1212  
  61         1279  
134 257         5360 {my $p = $i->image->{$x}{$y};
135 257 100 66     2269 $i->mapPartition($x, $y, $small) if defined($p) and $p == 0; # Bucket fill anything that touches this pixels
136             }
137             }
138             } # partitionImage
139              
140             sub mapPartition($$$$) #P Locate the pixels in the image that are connected to a pixel with a specified value.
141 13     13 1 44 {my ($i, $x, $y, $small) = @_; # Image, x coordinate of first point in partition, y coordinate of first point in partition, delete partitions of fewer pixels
142 13         51 my $p = $i->image->{$x}{$y} = $i->numberOfLoops+1; # Next partition
143 13         645 $i->partitions->{$p}{$x}{$y}++; # Add first pixel to this partition
144 13         120 my $pixelsInPartition = 0;
145              
146 13         32 my ($x1, $x2, $y1, $y2); # Rectangle bounding the partition
147 13         301 for(1..$i->count) # Worst case - each pixel is a separate line
148 66         235 {my $changed = 0; # Number of pixels added to this partition on this pass
149 66         127 for my $x(keys %{$i->image}) # Each pixel
  66         1431  
150 3036         9045 {for my $y(keys %{$i->image->{$x}})
  3036         65005  
151 13116 100       308092 {next if $i->image->{$x}{$y} == $p; # Already partitioned
152 10990         279252 my $I = $i->image;
153 10990         79262 my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1);
154 10990 100 100     187492 if (exists($I->{$𝘅}) && exists($I->{$𝘅}{$y}) && $I->{$𝘅}{$y} == $p or # Add this pixel to the partition if a neigboring pixel exists and is already a part of the paritition
      100        
      66        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
      100        
155             exists($I->{$x}) && exists($I->{$x}{$𝘆}) && $I->{$x}{$𝘆} == $p or
156             exists($I->{$𝕩}) && exists($I->{$𝕩}{$y}) && $I->{$𝕩}{$y} == $p or
157             exists($I->{$x}) && exists($I->{$x}{$𝕪}) && $I->{$x}{$𝕪} == $p)
158 244         5327 {$i->image->{$x}{$y} = $p;
159 244         1684 ++$changed;
160 244         5127 ++$i->partitions->{$p}{$x}{$y}; # Pixels in this partition
161 244         1922 ++$pixelsInPartition;
162 244 100 100     1046 $x1 = $x if !defined($x1) or $x1 > $x; # Rectangular bounds for partition
163 244 100 100     939 $x2 = $x if !defined($x2) or $x2 < $x;
164 244 100 100     891 $y1 = $y if !defined($y1) or $y1 > $y;
165 244 100 100     1085 $y2 = $y if !defined($y2) or $y2 < $y;
166             }
167             }
168             }
169 66 100       437 last unless $changed; # No more pixels in parition to consider
170             }
171              
172 13 100       46 if ($pixelsInPartition <= $small) # Remove small partitions
173 4         10 {for my $x(keys %{$i->image})
  4         91  
174 214         1801 {for my $y(keys %{$i->image->{$x}})
  214         4405  
175 950 100       24582 {delete $i->image->{$x}{$y} if $i->image->{$x}{$y} == $p;
176 950 100       6327 delete $i->image->{$x} unless keys %{$i->image->{$x}}; # Remove containing hash if now empty
  950         19193  
177             }
178             }
179 4         134 delete $i->partitions->{$p}
180             }
181             else
182 9         227 {$i->bounds->{$p} = [$x1, $y1, $x2, $y2]; # Record bounds
183             }
184             } # mapPartition
185              
186             sub removeInteriorOfPartition($$) #P Remove the interior of a partition to leave the exterior loop.
187 4     4 1 36 {my ($I, $partition) = @_; # Image, partition
188 4         15 my $i = $I->clonePartition($partition);
189 4         83 my $p = $i->partitions->{$partition}; # Each point in image
190              
191 4         40 for my $x(keys % $p) # Zero out the interior
192 58         98 {for my $y(keys %{$p->{$x}})
  58         199  
193 241         601 {my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1);
194              
195             $p->{$x}{$y} = 0 if # Zero an element theat does not touch the exterior of the partition
196             exists $p->{$x}{$𝕪} and exists $p->{$x}{$𝘆} and
197             exists $p->{$𝘅}{$y} and exists $p->{$𝘅}{$𝘆} and exists $p->{$𝘅}{$𝕪} and
198 241 100 100     1392 exists $p->{$𝕩}{$y} and exists $p->{$𝕩}{$𝘆} and exists $p->{$𝕩}{$𝕪};
      100        
      100        
      100        
      100        
      100        
      100        
199             }
200             }
201              
202 4         43 for my $x(keys % $p) # The remaining pixels are the exterior edge of the partition
203 62         130 {for my $y(keys %{$p->{$x}})
  62         168  
204 241 100       1100 {delete $I->partitions->{$partition}{$x}{$y} unless $p->{$x}{$y};
205             }
206             }
207             } # removeInteriorOfPartition
208              
209             sub findLongestLoop($$) #P Find the longest loop in a partition.
210 4     4 1 118 {my ($I, $partition) = @_; # Image, partition
211 4         22 my $i = $I->clonePartition($partition);
212 4         92 my $p = $i->partitions->{$partition}; # Pixels in the partition
213              
214             my $break = sub
215 4     4   45 {for my $x(sort keys % $p) # Break the loop into a path
216 4         11 {for my $y(sort keys %{$p->{$x}})
  4         26  
217 4         20 {return [$x, $y];
218             }
219             }
220 0         0 confess "No pixels in partition $partition?"; # This should not happen!
221 4         57 }->();
222              
223 4         46 my ($x, $y) = @$break; # The start/end point for this partition
224 4         13 delete $p->{$x}{$y}; # Break the loop to get to end points that we can find the shortest path between
225 4         18 my ($start, $end) = $i->searchArea($partition, $x, $y); # Start and end points
226 4 50       14 $start or confess "No start point";
227 4 50       12 $end or confess "No end point";
228 4         13 my ($X, $Y) = @$end; # Coordinates of end point
229 4         12 my @loop = $start; # Start the path
230 4         8 my @longestLoop; # Shortest path so far
231 4         15 my @search = [$i->searchArea($partition, @$start)]; # Initial search area is the pixels around the start pixel
232 4         10 my %visited; # Pixels we have already visited along the possible path
233              
234 4         13 while(@search) # Find the shortest path amongst all the possible paths
235 271378 50       593987 {@loop == @search or confess "Search and path depth mismatch"; # These two arrays must stay in sync because their dimensions reflects the progress along the possible path
236 271378         433288 my $search = $search[-1]; # Pixels to search for latest path element
237 271378 100       487849 if (!@$search) # Nothing left to search at this level
238 133751         185021 {pop @search; # Remove search level
239 133751         187929 my ($x, $y) = @{pop @loop}; # Pixel to remove from possible path
  133751         259122  
240 133751         377744 delete $visited{$x}{$y}; # Pixel no longer visited on this possible path
241             }
242             else
243 137627         189084 {my ($x, $y) = @{pop @$search}; # Next pixel to add to path
  137627         279046  
244 137627 50       346450 next if $visited{$x}{$y}; # Pixel has already been vsisited on this path so skip it
245 137627 100 100     308692 if ($x == $X and $y == $Y)
246 1940 100 100     7746 {@longestLoop = @loop if !@longestLoop or @loop > @longestLoop;
247 1940         3862 my ($x, $y) = @{pop @loop}; # Pixel to remove from possible path
  1940         4229  
248 1940         3670 pop @search; # any other adjacent pixels will not produce a shorter path
249 1940         5867 delete $visited{$x}{$y}; # Pixel no longer visited on this possible path
250             }
251             else # Extend the search
252 135687         318401 {push @loop, [$x, $y]; # Extend the path
253 135687         286253 $visited{$x}{$y}++;
254             push @search, # Extend the search area to pixels not already visited on this path
255 135687         309387 [grep {my ($x, $y) = @$_; !$visited{$x}{$y}}
  324442         628595  
  324442         995064  
256             $i->searchArea($partition, $x, $y)]
257             }
258             }
259             }
260              
261 4         157 $I->partitionLoop->{$partition} = [$break, @longestLoop, $end] # Return the shortest path from start to end and the break point to make a loop
262             } # findLongestLoop
263              
264             sub searchArea($$$$) #P Return the pixels to search from around a given pixel.
265 135695     135695 1 270755 {my ($i, $partition, $x, $y) = @_; # Image, partition, x coordinate of center of search, y coordinate of center of search.
266 135695         2781812 my $p = $i->partitions->{$partition};
267 135695         941244 my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1);
268 135695         219595 my @s; # Pixels to search from
269 135695 100       401599 push @s, [$𝘅, $y] if exists $p->{$𝘅}{$y};
270 135695 100       385278 push @s, [$x, $𝘆] if exists $p->{$x}{$𝘆};
271 135695 100       367512 push @s, [$x, $𝕪] if exists $p->{$x}{$𝕪};
272 135695 100       363533 push @s, [$𝕩, $y] if exists $p->{$𝕩}{$y};
273             @s # Return all possible pixels
274 135695         325566 } # searchArea
275              
276             sub widthOfLoop($$) #P Find the (estimated) width of the loop at each point.
277 4     4 1 113 {my ($I, $partition) = @_; # Image, partition
278 4         11 my $i = $I->clonePartition($partition); # Clone the specified partition so that we can remove pixels once they have been processed to spped up the remaining search
279 4         57 my $loop = $i->partitionLoop->{$partition}; # Loop in image
280 4         21 my $maxSteps = @$loop;
281              
282 4         19 for my $step(keys @$loop) # Each pixel in the path
283 214         4860 {my ($x, $y) = @{$$loop[$step]};
  214         516  
284              
285             my $explore = sub #P Explore away from a point checking that we are still in the partition associated with the Loop
286 1712     1712   11435 {my ($dx, $dy) = @_; # x direction, y direction
287 1712         2727 for my $step(1..$maxSteps) # Maximum possible width
288             {return $step-1 unless $i->partitions->{$partition} # Keep stepping whilst still in partition
289             {$x+$step*$dx}
290 4081 100       71084 {$y+$step*$dy};
291             }
292             $maxSteps # We never left the partition
293 214         778 };
  0         0  
294              
295 214         302 push @{$I->partitionLoop->{$partition}[$step]}, 1 + min # Explore in opposite directions along 4 lines and take the minimum as the width
  214         3657  
296             ($explore->(1, 0) + $explore->(-1, 0),
297             $explore->(1, 1) + $explore->(-1, -1),
298             $explore->(0, 1) + $explore->( 0, -1),
299             $explore->(1, -1) + $explore->(-1, +1));
300             }
301             } # widthOfLoop
302              
303             sub loop($$) # Return an array of arrays [x, y] of sequentially touching pixels describing the largest loop in the specified partition where the loops in an image are numbered from 1.
304 1     1 1 2 {my ($i, $partition) = @_; # Image, partition
305 1         16 $i->partitionLoop->{$partition} # Return the loop
306             } # loop
307              
308             sub printLoop($$) # Print a loop in the image numbering pixels with the estimated thickness of the loop.
309 3     3 1 7 {my ($i, $partition) = @_; # Image, partition
310 3         46 my $X = $i->x; my $Y = $i->y; # Image dimensions
  3         52  
311 3         15 my $s = ' ' x $X; # Image line
312 3         9 my @s = ($s) x $Y; # Image lines
313 3         43 my $p = $i->partitionLoop->{$partition}; # Each point in image
314 3         14 my $c = 0; # Cycle though 0..9 to show loop
315              
316             my $plot = sub # Plot a pixel
317 192     192   325 {my ($x, $y, $symbol) = @_;
318 192 50 33     688 substr($s[$y], $x, 1) = $symbol % 10 if $y< $Y and $x < $X;
319 3         13 };
320              
321 3         10 $plot->(@$_) for @$p; # Plot each pixel in the loop
322              
323 3         6 my ($x1, $y1, $x2, $y2) = my @bounds = @{$i->bounds->{$partition}}; # Bounds
  3         55  
324 3   33     47 $x1--, $x2++ while $x1 > 0 and $x2 < $X-1 and $x2 - $x1 <= 10;
      66        
325 3   100     13 $y1--, $y2++ while $y1 > 0 and $y2 < $Y-1;
326 3         6 my ($xl, $yl) = ($x2-$x1+1, $y2-$y1+1); # Lengths
327              
328             my $h = sub # Header layout
329 3     3   5 {my ($space) = @_;
330 3         9 my $N = 1 + int($X/10);
331             my $s = join '',
332 3 100       26 map{substr($_, -1) ? q( ) : $_ > 9 ? substr($_, -2, 1) : 0} 0..$X;
  240 100       390  
333 3         32 my $t = substr(("0123456789"x(1 + int($X/10))), 0, $X);
334 3         9 $s = substr($s, $x1, $xl);
335 3         5 $t = substr($t, $x1, $xl);
336 3         13 "$space $s\n$space $t\n"
337 3         7 }->(" " x indent);
338              
339             my $m = # Loop layout
340             join "\n",
341 41         68 map{sprintf("%".indent."d ", $_).substr($s[$_].(q( )x$X), $x1, $xl)}
342 3 100       22 grep{$_ >= $y1 and $_ <= $y2}
  45         104  
343             keys @s;
344              
345 3         9 my $f = "Image: X = $X, Y = $Y, Loop = $partition"; # Footer layout
346              
347 3         36 join "\n", $h, $m, $f;
348             } # printLoop
349              
350             sub print($) # Print the loops in an image sequentially numbering adjacent pixels in each loop from 0..9.
351 1     1 1 4 {my ($i) = @_; # Image
352 1         18 my $X = $i->x; my $Y = $i->y; # Image dimensions
  1         20  
353 1         6 my $s = ' ' x $X; # Image line
354 1         4 my @s = ($s) x $Y; # Image lines
355              
356 1         5 for my $partition(1..$i->numberOfLoops) # Each partition
357 4         74 {my $p = $i->partitionLoop->{$partition}; # Each point in image
358 4         21 my $c = 0; # Cycle though 0..9 to show loop
359              
360             my $plot = sub # Plot a pixel
361 214     214   340 {my ($x, $y) = @_;
362 214 50 33     771 substr($s[$y], $x, 1) = (++$c % 10) if $y < $Y and $x < $X;
363 4         14 };
364              
365 4         11 $plot->(@$_) for @$p; # Plot each pixel in the loop
366             }
367              
368             my $h = sub # Header layout
369 1     1   3 {my ($space) = @_;
370 1         4 my $N = 1 + int($X/10);
371             my $s = join '',
372 1 100       4 map{substr($_, -1) ? q( ) : $_ > 9 ? substr($_, -2, 1) : 0} 0..$X;
  80 100       129  
373 1         10 my $t = substr(("0123456789"x(1 + int($X/10))), 0, $X);
374 1         5 "$space $s\n$space $t\n"
375 1         4 }->(" " x indent);
376              
377 1         7 my $m = join "\n", map{sprintf("%".indent."d ", $_).$s[$_]} keys @s; # Loop layout
  15         23  
378              
379 1         8 my $f = "Image: X = $X, Y = $Y, Loops = ".$i->numberOfLoops; # Footer layout
380              
381 1         32 join "\n", $h, $m, $f;
382             } # print
383              
384             #1 Attributes # Attributes of an image
385              
386             BEGIN{
387 1     1   4328 genLValueScalarMethods(q(bounds)); # The bounds of each partition: [$x1, $y1, $x2, $y2].
388 1         193 genLValueScalarMethods(q(count)); # Number of points in the image.
389 1         141 genLValueScalarMethods(q(image)); # Image data points.
390 1         141 genLValueScalarMethods(q(partitions)); # Number of partitions in the image.
391 1         131 genLValueScalarMethods(q(partitionLoop)); # Loop for each partition.
392 1         121 genLValueScalarMethods(q(x)); # Image dimension in x.
393 1         129 genLValueScalarMethods(q(y)); # Image dimension in y.
394             }
395              
396             #-------------------------------------------------------------------------------
397             # Export
398             #-------------------------------------------------------------------------------
399              
400 1     1   184 use Exporter qw(import);
  1         10  
  1         55  
401              
402 1     1   8 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         388  
403              
404             @ISA = qw(Exporter);
405             @EXPORT_OK = qw(
406             );
407             %EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]);
408              
409             # podDocumentation
410              
411             =pod
412              
413             =encoding utf-8
414              
415             =head1 Name
416              
417             Image::Find::Loops - Find loops in an image.
418              
419             =head1 Synopsis
420              
421             Use L to create and analyze a new image, then L to
422             visualize the loops detected, or L to get the coordinates of
423             points in each loop in sequential order.
424              
425             =head1 Description
426              
427             Find loops in an image.
428              
429             The following sections describe the methods in each functional area of this
430             module. For an alphabetic listing of all methods by name see L.
431              
432              
433              
434             =head1 Methods
435              
436             Find loops in an image.
437              
438             =head2 new($)
439              
440             Find loops in an image represented as a string.
441              
442             Parameter Description
443             1 $string String of blanks; non blanks; new lines defining the image
444              
445             Example:
446              
447              
448             my $d = new (<
449              
450             11 11111111111 11111111111
451             1111 1 1 1 1
452             11 11 1111 1 111 1 1 1 1
453             11 11 11 11 1 1 1 1 1 1
454             11 11 11 11 1 1 1 1 1 1
455             1111 11111 1 1 1 1 1 1
456             11 1111 1 1 1 1111111111 1 1
457             1111 1 1 1 1111111111 1 1
458             11111 1 1 1 1 1 1
459             111111111111111111 1 1 1 1 1 1
460             11 1111111111 1 111 1 1 1 1
461             1 11111111 1 1 1 1
462             11111111111 1 1 1 1
463             111 11111111111 11111111111
464              
465             END
466              
467             ok nws($d->print) eq nws(<
468             0 1 2 3 4 5 6 7
469             0123456789012345678901234567890123456789012345678901234567890123456789012345678
470             0
471             1 56 12345678901 23456789012
472             2 3478 8 2 1 3
473             3 12 90 1234 7 432 3 0 4
474             4 2 1 09 5 6 5 1 4 9 5
475             5 10 32 78 67 5 6 0 5 8 6
476             6 9854 63298 4 7 9 6 7 7
477             7 76 5410 3 8 8 7890123456 8
478             8 0123 2 9 7 0987654321 9
479             9 89 4 1 0 6 1 0 0
480             10 123412367 5678 0 1 5 2 9 1
481             11 0 45 09 9 234 3 8 2
482             12 9 654321 8 4 7 3
483             13 8765432107 7 5 6 4
484             14 98 65432109876 54321098765
485              
486             Image: X = 79, Y = 15, Loops = 4
487             END
488              
489             ok nws($d->printLoop(2)) eq nws(<
490             1 2
491             678901234567890123
492              
493             3 1111
494             4 22 1
495             5 22 22
496             6 22412
497             7 1334
498             8 3411
499             9 11 1
500             10 111111111 1111
501             11 1 22 22
502             12 1 111111
503             13 1111111122
504             14 21
505              
506             Image: X = 79, Y = 15, Loop = 2
507             END
508              
509             ok nws($d->printLoop(3)) eq nws(<
510             3 4 5
511             012345678901234567890123456789
512              
513             1 11111111111 11111111111
514             2 1 1 1 1
515             3 1 1 1 1
516             4 1 1 1 1
517             5 1 1 1 1
518             6 1 1 1 1
519             7 1 1222222221 1
520             8 1 1222222221 1
521             9 1 1 1 1
522             10 1 1 1 1
523             11 1 1 1 1
524             12 1 1 1 1
525             13 1 1 1 1
526             14 11111111111 11111111111
527              
528             Image: X = 79, Y = 15, Loop = 3
529             END
530              
531             ok nws($d->printLoop(4)) eq nws(<
532             3 4
533             9012345678901
534              
535             0
536             1
537             2
538             3 111
539             4 1 1
540             5 1 1
541             6 1 1
542             7 1 1
543             8 1 1
544             9 1 1
545             10 1 1
546             11 111
547             12
548             13
549             14
550              
551             Image: X = 79, Y = 15, Loop = 4
552             END
553              
554              
555             This is a static method and so should be invoked as:
556              
557             Image::Find::Loops::new
558              
559              
560             =head2 numberOfLoops($)
561              
562             Number of loops in the image. The partitions and loops are numbered from 1.
563              
564             Parameter Description
565             1 $i Image
566              
567             Example:
568              
569              
570             is_deeply [$d->count, $d->x, $d->y, $d->numberOfLoops],
571              
572             [239, 79, 15, 4];
573              
574              
575             =head2 loop($$)
576              
577             Return an array of arrays [x, y] of sequentially touching pixels describing the largest loop in the specified partition where the loops in an image are numbered from 1.
578              
579             Parameter Description
580             1 $i Image
581             2 $partition Partition
582              
583             Example:
584              
585              
586             is_deeply [grep{$_->[2] > 2} @{$d->loop(2)}],
587              
588             [[15, 8, 3],
589              
590             [15, 7, 3],
591              
592             [15, 6, 4],
593              
594             [14, 7, 3],
595              
596             [16, 7, 4],
597              
598              
599             =head2 printLoop($$)
600              
601             Print a loop in the image numbering pixels with the estimated thickness of the loop.
602              
603             Parameter Description
604             1 $i Image
605             2 $partition Partition
606              
607             Example:
608              
609              
610             ok nws($d->printLoop(2)) eq nws(<
611             1 2
612             678901234567890123
613              
614             3 1111
615             4 22 1
616             5 22 22
617             6 22412
618             7 1334
619             8 3411
620             9 11 1
621             10 111111111 1111
622             11 1 22 22
623             12 1 111111
624             13 1111111122
625             14 21
626              
627             Image: X = 79, Y = 15, Loop = 2
628             END
629              
630             ok nws($d->printLoop(3)) eq nws(<
631             3 4 5
632             012345678901234567890123456789
633              
634             1 11111111111 11111111111
635             2 1 1 1 1
636             3 1 1 1 1
637             4 1 1 1 1
638             5 1 1 1 1
639             6 1 1 1 1
640             7 1 1222222221 1
641             8 1 1222222221 1
642             9 1 1 1 1
643             10 1 1 1 1
644             11 1 1 1 1
645             12 1 1 1 1
646             13 1 1 1 1
647             14 11111111111 11111111111
648              
649             Image: X = 79, Y = 15, Loop = 3
650             END
651              
652             ok nws($d->printLoop(4)) eq nws(<
653             3 4
654             9012345678901
655              
656             0
657             1
658             2
659             3 111
660             4 1 1
661             5 1 1
662             6 1 1
663             7 1 1
664             8 1 1
665             9 1 1
666             10 1 1
667             11 111
668             12
669             13
670             14
671              
672             Image: X = 79, Y = 15, Loop = 4
673             END
674              
675              
676             =head2 print($)
677              
678             Print the loops in an image sequentially numbering adjacent pixels in each loop from 0..9.
679              
680             Parameter Description
681             1 $i Image
682              
683             Example:
684              
685              
686             ok nws($d->print) eq nws(<
687             0 1 2 3 4 5 6 7
688             0123456789012345678901234567890123456789012345678901234567890123456789012345678
689             0
690             1 56 12345678901 23456789012
691             2 3478 8 2 1 3
692             3 12 90 1234 7 432 3 0 4
693             4 2 1 09 5 6 5 1 4 9 5
694             5 10 32 78 67 5 6 0 5 8 6
695             6 9854 63298 4 7 9 6 7 7
696             7 76 5410 3 8 8 7890123456 8
697             8 0123 2 9 7 0987654321 9
698             9 89 4 1 0 6 1 0 0
699             10 123412367 5678 0 1 5 2 9 1
700             11 0 45 09 9 234 3 8 2
701             12 9 654321 8 4 7 3
702             13 8765432107 7 5 6 4
703             14 98 65432109876 54321098765
704              
705             Image: X = 79, Y = 15, Loops = 4
706             END
707              
708              
709             =head1 Attributes
710              
711             Attributes of an image
712              
713             =head2 bounds :lvalue
714              
715             The bounds of each partition: [$x1, $y1, $x2, $y2].
716              
717              
718             =head2 count :lvalue
719              
720             Number of points in the image.
721              
722              
723             =head2 image :lvalue
724              
725             Image data points.
726              
727              
728             =head2 partitions :lvalue
729              
730             Number of partitions in the image.
731              
732              
733             =head2 partitionLoop :lvalue
734              
735             Loop for each partition.
736              
737              
738             =head2 x :lvalue
739              
740             Image dimension in x.
741              
742              
743             =head2 y :lvalue
744              
745             Image dimension in y.
746              
747              
748              
749             =head1 Private Methods
750              
751             =head2 fillPartition($$)
752              
753             Remove any interior voids in a partition.
754              
755             Parameter Description
756             1 $i Image
757             2 $partition Partition
758              
759             =head2 clone($)
760              
761             Clone an image.
762              
763             Parameter Description
764             1 $i Image
765              
766             Example:
767              
768              
769             is_deeply $d, $d->clone;
770              
771              
772             =head2 clonePartition($$)
773              
774             Clone a partition of an image.
775              
776             Parameter Description
777             1 $i Image
778             2 $partition Partition
779              
780             =head2 partitionImage($$)
781              
782             Partition the images into disjoint sets of connected points.
783              
784             Parameter Description
785             1 $i Image
786             2 $small Minimum size of a partition - smaller partitions will be ignored
787              
788             =head2 mapPartition($$$$)
789              
790             Locate the pixels in the image that are connected to a pixel with a specified value.
791              
792             Parameter Description
793             1 $i Image
794             2 $x X coordinate of first point in partition
795             3 $y Y coordinate of first point in partition
796             4 $small Delete partitions of fewer pixels
797              
798             =head2 removeInteriorOfPartition($$)
799              
800             Remove the interior of a partition to leave the exterior loop.
801              
802             Parameter Description
803             1 $I Image
804             2 $partition Partition
805              
806             =head2 findLongestLoop($$)
807              
808             Find the longest loop in a partition.
809              
810             Parameter Description
811             1 $I Image
812             2 $partition Partition
813              
814             =head2 searchArea($$$$)
815              
816             Return the pixels to search from around a given pixel.
817              
818             Parameter Description
819             1 $i Image
820             2 $partition Partition
821             3 $x X coordinate of center of search
822             4 $y Y coordinate of center of search.
823              
824             =head2 widthOfLoop($$)
825              
826             Find the (estimated) width of the loop at each point.
827              
828             Parameter Description
829             1 $I Image
830             2 $partition Partition
831              
832              
833             =head1 Index
834              
835              
836             1 L
837              
838             2 L
839              
840             3 L
841              
842             4 L
843              
844             5 L
845              
846             6 L
847              
848             7 L
849              
850             8 L
851              
852             9 L
853              
854             10 L
855              
856             11 L
857              
858             12 L
859              
860             13 L
861              
862             14 L
863              
864             15 L
865              
866             16 L
867              
868             17 L
869              
870             18 L
871              
872             19 L
873              
874             20 L
875              
876             21 L
877              
878             =head1 Installation
879              
880             This module is written in 100% Pure Perl and, thus, it is easy to read, use,
881             modify and install.
882              
883             Standard L process for building and installing modules:
884              
885             perl Build.PL
886             ./Build
887             ./Build test
888             ./Build install
889              
890             =head1 Author
891              
892             L
893              
894             L
895              
896             =head1 Copyright
897              
898             Copyright (c) 2016-2018 Philip R Brenan.
899              
900             This module is free software. It may be used, redistributed and/or modified
901             under the same terms as Perl itself.
902              
903             =cut
904              
905              
906              
907             # Tests and documentation
908              
909             sub test
910 1     1 0 12 {my $p = __PACKAGE__;
911 1         10 binmode($_, ":utf8") for *STDOUT, *STDERR;
912 1 50       68 return if eval "eof(${p}::DATA)";
913 1         58 my $s = eval "join('', <${p}::DATA>)";
914 1 50       8 $@ and die $@;
915 1     1   7 eval $s;
  1     1   3  
  1     1   41  
  1         7  
  1         2  
  1         25  
  1         680  
  1         74068  
  1         10  
  1         78  
916 1 50       589 $@ and die $@;
917             }
918              
919             test unless caller;
920              
921             1;
922             # podDocumentation
923             __DATA__