line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
3
|
|
|
|
|
|
|
# Image::Find::Paths - Find paths in an image. |
4
|
|
|
|
|
|
|
# Philip R Brenan at gmail dot com, Appa Apps Ltd Inc., 2018 |
5
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
6
|
|
|
|
|
|
|
package Image::Find::Paths; |
7
|
|
|
|
|
|
|
our $VERSION = "20180505"; |
8
|
|
|
|
|
|
|
require v5.16; |
9
|
1
|
|
|
1
|
|
719
|
use warnings FATAL => qw(all); |
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
41
|
|
10
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
37
|
|
11
|
1
|
|
|
1
|
|
6
|
use Carp qw(confess); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
81
|
|
12
|
1
|
|
|
1
|
|
569
|
use Data::Dump qw(dump); |
|
1
|
|
|
|
|
8480
|
|
|
1
|
|
|
|
|
75
|
|
13
|
1
|
|
|
1
|
|
943
|
use Data::Table::Text qw(:all); |
|
1
|
|
|
|
|
70833
|
|
|
1
|
|
|
|
|
621
|
|
14
|
|
|
|
|
|
|
#use Time::HiRes qw(time); |
15
|
1
|
|
|
1
|
|
11
|
use utf8; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
7
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
#my %exec; sub e($) {$exec{$_[0]}++} |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
#1 Methods # Find paths in an image |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub new($) #S Find paths in an image represented as a string. |
22
|
4
|
|
|
4
|
1
|
10
|
{my ($string) = @_; # String of blanks; non blanks; new lines defining the image |
23
|
4
|
|
|
|
|
34
|
my @lines = split /\n/, $string; |
24
|
4
|
|
|
|
|
13
|
my $count; # Number of active pixels |
25
|
|
|
|
|
|
|
my %image; # {x}{y} of active pixels |
26
|
4
|
|
|
|
|
0
|
my $x; # Image dimension in x |
27
|
4
|
|
|
|
|
14
|
for my $j(0..$#lines) # Load active pixels |
28
|
49
|
|
|
|
|
75
|
{my $line = $lines[$j]; |
29
|
49
|
100
|
100
|
|
|
148
|
$x = length($line) if !defined($x) or length($line) > $x; # Longest line |
30
|
49
|
|
|
|
|
79
|
for my $i(0..length($line)-1) # Parse each line |
31
|
5461
|
100
|
|
|
|
10286
|
{$image{$i}{$j} = 0, $count++ if substr($line, $i, 1) ne q( ); |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
|
35
|
4
|
|
|
|
|
38
|
my $d = bless{image=>\%image, x=>$x, y=>scalar(@lines), count=>$count, # Create image of paths |
36
|
|
|
|
|
|
|
partitions=>{}, partitionStart=>{}, partitionEnd=>{}, |
37
|
|
|
|
|
|
|
partitionPath=>{}}; |
38
|
|
|
|
|
|
|
|
39
|
4
|
|
|
|
|
19
|
$d->partition; # Partition the image |
40
|
4
|
|
|
|
|
75
|
$d->start($_), $d->end($_) for 1..$d->numberOfPaths; # Find a start point for each partition |
41
|
4
|
|
|
|
|
43
|
my $h = $d->height; # Clone and add height |
42
|
4
|
|
|
|
|
19
|
$d->shortestPathBetweenEndPoints($h, $_) for 1..$d->numberOfPaths; # Find the longest path in each partition |
43
|
4
|
|
|
|
|
789
|
$d->widthOfPaths; |
44
|
4
|
|
|
|
|
1221
|
$d # Return new image with path details |
45
|
|
|
|
|
|
|
} # new |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub clone($) #P Clone an image. |
48
|
1
|
|
|
1
|
1
|
3
|
{my ($i) = @_; # Image |
49
|
1
|
|
|
|
|
1
|
my %partitions; # Clone partitions |
50
|
1
|
|
|
|
|
12
|
for my $p(keys %{$i->partitions}) |
|
1
|
|
|
|
|
18
|
|
51
|
6
|
|
|
|
|
35
|
{for my $x(keys %{$i->partitions->{$p}}) |
|
6
|
|
|
|
|
76
|
|
52
|
32
|
|
|
|
|
174
|
{for my $y(keys %{$i->partitions->{$p}{$x}}) |
|
32
|
|
|
|
|
420
|
|
53
|
94
|
|
|
|
|
1602
|
{$partitions{$p}{$x}{$y} = $i->partitions->{$p}{$x}{$y}; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
1
|
|
|
|
|
23
|
bless {%$i, partitions=>\%partitions}; # Cloned image |
59
|
|
|
|
|
|
|
} # clone |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub clonePartition($$) #P Clone a partition of an image. |
62
|
27
|
|
|
27
|
1
|
52
|
{my ($i, $partition) = @_; # Image, partition |
63
|
27
|
|
|
|
|
42
|
my %partition; # Cloned partition |
64
|
|
|
|
|
|
|
|
65
|
27
|
|
|
|
|
49
|
for my $x(keys %{$i->partitions->{$partition}}) |
|
27
|
|
|
|
|
420
|
|
66
|
972
|
|
|
|
|
8756
|
{for my $y(keys %{$i->partitions->{$partition}{$x}}) |
|
972
|
|
|
|
|
17297
|
|
67
|
14124
|
|
|
|
|
327529
|
{$partition{$x}{$y} = $i->partitions->{$partition}{$x}{$y}; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
27
|
|
|
|
|
402
|
my $I = bless {%$i}; # Clone image quickly |
72
|
27
|
|
|
|
|
59
|
$I->partitions = {%{$i->partitions}}; # Clone partitions quickly |
|
27
|
|
|
|
|
434
|
|
73
|
27
|
|
|
|
|
674
|
$I->partitions->{$partition} = \%partition; # Replace cloned partition |
74
|
27
|
|
|
|
|
160
|
$I # Return new image |
75
|
|
|
|
|
|
|
} # clonePartition |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub countPixels($) #P Count the pixels in an image. |
78
|
0
|
|
|
0
|
1
|
0
|
{my ($i) = @_; # Image |
79
|
|
|
|
|
|
|
|
80
|
0
|
|
|
|
|
0
|
my $count; |
81
|
0
|
|
|
|
|
0
|
for my $p(keys %{$i->partitions}) |
|
0
|
|
|
|
|
0
|
|
82
|
0
|
|
|
|
|
0
|
{for my $x(keys %{$i->partitions->{$p}}) |
|
0
|
|
|
|
|
0
|
|
83
|
0
|
|
|
|
|
0
|
{for my $y(keys %{$i->partitions->{$p}{$x}}) |
|
0
|
|
|
|
|
0
|
|
84
|
0
|
|
|
|
|
0
|
{++$count |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
$count |
90
|
0
|
|
|
|
|
0
|
} # countPixels |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
sub height($) #P Clone an image adding height to each pixel. |
93
|
4
|
|
|
4
|
1
|
13
|
{my ($i) = @_; # Image |
94
|
|
|
|
|
|
|
|
95
|
4
|
|
|
|
|
7
|
my %contours; # Clone partitions |
96
|
4
|
|
|
|
|
7
|
my $pixels = 0; |
97
|
4
|
|
|
|
|
7
|
for my $p(keys %{$i->partitions}) # Base |
|
4
|
|
|
|
|
64
|
|
98
|
9
|
|
|
|
|
36
|
{for my $x(keys %{$i->partitions->{$p}}) |
|
9
|
|
|
|
|
123
|
|
99
|
324
|
|
|
|
|
453
|
{for my $y(keys %{$i->partitions->{$p}{$x}}) |
|
324
|
|
|
|
|
4314
|
|
100
|
4708
|
|
|
|
|
7994
|
{$contours{$p}{1}{$x}{$y} = 1; |
101
|
4708
|
|
|
|
|
5404
|
$pixels++; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
4
|
|
|
|
|
25
|
for my $p(keys %contours) # Contours |
107
|
9
|
|
|
|
|
21
|
{for my $h(1..$pixels) |
108
|
26
|
|
|
|
|
47
|
{my $count; |
109
|
26
|
|
|
|
|
38
|
for my $x(keys %{$contours{$p}{$h}}) |
|
26
|
|
|
|
|
829
|
|
110
|
2231
|
|
|
|
|
3942
|
{for my $y(keys %{$contours{$p}{$h}{$x}}) |
|
2231
|
|
|
|
|
11009
|
|
111
|
20169
|
|
|
|
|
49898
|
{my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1); |
112
|
20169
|
50
|
100
|
|
|
196666
|
if (exists $contours{$p}{$h }{$x}{$𝕪} and |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
113
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$x}{$𝘆} and |
114
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝘅}{$y} and |
115
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝘅}{$𝘆} and |
116
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝘅}{$𝕪} and |
117
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝕩}{$y} and |
118
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝕩}{$𝘆} and |
119
|
|
|
|
|
|
|
exists $contours{$p}{$h }{$𝕩}{$𝕪}) |
120
|
15461
|
|
|
|
|
40069
|
{ $contours{$p}{$h+1}{$x}{$y}++; |
121
|
15461
|
|
|
|
|
31031
|
++$count; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
} |
125
|
26
|
100
|
|
|
|
442
|
last unless defined $count; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
4
|
|
|
|
|
8
|
my %partitions; # Project contours to obtain height partition |
130
|
4
|
|
|
|
|
12
|
for my $p(keys % contours) |
131
|
9
|
|
|
|
|
14
|
{for my $h(sort{$a<=>$b}keys %{$contours{$p}}) |
|
37
|
|
|
|
|
64
|
|
|
9
|
|
|
|
|
38
|
|
132
|
26
|
|
|
|
|
39
|
{for my $x(keys %{$contours{$p}{$h}}) |
|
26
|
|
|
|
|
445
|
|
133
|
2263
|
|
|
|
|
3403
|
{for my $y(keys %{$contours{$p}{$h}{$x}}) |
|
2263
|
|
|
|
|
8132
|
|
134
|
20169
|
|
|
|
|
44576
|
{$partitions{$p}{$x}{$y} = $h; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
4
|
|
|
|
|
2204
|
bless {%$i, partitions=>\%partitions}; # Cloned image |
141
|
|
|
|
|
|
|
} # height |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub numberOfPaths($) # Number of paths in the image. |
144
|
30
|
|
|
30
|
1
|
248
|
{my ($i) = @_; # Image |
145
|
30
|
|
|
|
|
45
|
scalar(keys %{$i->partitions}) |
|
30
|
|
|
|
|
571
|
|
146
|
|
|
|
|
|
|
} # numberOfPaths |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub partition($) #P Partition the images into disjoint sets of connected points. |
149
|
4
|
|
|
4
|
1
|
8
|
{my ($i) = @_; # Image |
150
|
4
|
|
|
|
|
6
|
for my $x(sort{$a<=>$b} keys %{$i->image}) # Stabilize partition numbers to make testing possible |
|
1975
|
|
|
|
|
2144
|
|
|
4
|
|
|
|
|
96
|
|
151
|
322
|
|
|
|
|
3168
|
{for my $y(sort{$a<=>$b} keys %{$i->image->{$x}}) |
|
13162
|
|
|
|
|
26781
|
|
|
322
|
|
|
|
|
7311
|
|
152
|
4710
|
100
|
|
|
|
136318
|
{$i->mapPartition($x, $y) if $i->image->{$x}{$y} == 0; # Bucket fill anything that touches this pixels |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
} # partition |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub mapPartition($$$) #P Locate the pixels in the image that are connected to a pixel with a specified value. |
158
|
11
|
|
|
11
|
1
|
103
|
{my ($i, $x, $y) = @_; # Image, x coordinate of first point in partition, y coordinate of first point in partition |
159
|
11
|
|
|
|
|
37
|
my $p = $i->image->{$x}{$y} = $i->numberOfPaths+1; # Next partition |
160
|
11
|
|
|
|
|
409
|
$i->partitions->{$p}{$x}{$y}++; # Add first pixel to this partition |
161
|
11
|
|
|
|
|
104
|
my $pixelsInPartition = 0; |
162
|
|
|
|
|
|
|
|
163
|
11
|
|
|
|
|
200
|
for(1..$i->count) # Worst case - each pixel is a separate line |
164
|
183
|
|
|
|
|
487
|
{my $changed = 0; # Number of pixels added to this partition on this pass |
165
|
183
|
|
|
|
|
323
|
for my $x(keys %{$i->image}) # Each pixel |
|
183
|
|
|
|
|
3295
|
|
166
|
33681
|
|
|
|
|
146477
|
{for my $y(keys %{$i->image->{$x}}) |
|
33681
|
|
|
|
|
558505
|
|
167
|
524355
|
100
|
|
|
|
10012343
|
{next if $i->image->{$x}{$y} == $p; # Already partitioned |
168
|
264866
|
|
|
|
|
5450723
|
my $I = $i->image; |
169
|
264866
|
|
|
|
|
1645802
|
my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1); |
170
|
264866
|
100
|
100
|
|
|
3830304
|
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
|
|
|
|
|
171
|
|
|
|
|
|
|
exists($I->{$x}) && exists($I->{$x}{$𝘆}) && $I->{$x}{$𝘆} == $p or |
172
|
|
|
|
|
|
|
exists($I->{$𝕩}) && exists($I->{$𝕩}{$y}) && $I->{$𝕩}{$y} == $p or |
173
|
|
|
|
|
|
|
exists($I->{$x}) && exists($I->{$x}{$𝕪}) && $I->{$x}{$𝕪} == $p) |
174
|
4699
|
|
|
|
|
80048
|
{$i->image->{$x}{$y} = $p; |
175
|
4699
|
|
|
|
|
25945
|
++$changed; |
176
|
4699
|
|
|
|
|
72910
|
++$i->partitions->{$p}{$x}{$y}; # Pixels in this partition |
177
|
4699
|
|
|
|
|
30870
|
++$pixelsInPartition; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
183
|
100
|
|
|
|
4012
|
last unless $changed; # No more pixels in parition to consider |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
11
|
100
|
|
|
|
47
|
if ($pixelsInPartition <= 1) # Remove partitions of just one pixel |
185
|
2
|
|
|
|
|
3
|
{for my $x(keys %{$i->image}) |
|
2
|
|
|
|
|
29
|
|
186
|
59
|
|
|
|
|
339
|
{for my $y(keys %{$i->image->{$x}}) |
|
59
|
|
|
|
|
733
|
|
187
|
191
|
100
|
|
|
|
3032
|
{delete $i->image->{$x}{$y} if $i->image->{$x}{$y} == $p; |
188
|
191
|
100
|
|
|
|
831
|
delete $i->image->{$x} unless keys %{$i->image->{$x}}; # Remove containing hash if now empty |
|
191
|
|
|
|
|
2405
|
|
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
} |
191
|
2
|
|
|
|
|
38
|
delete $i->partitions->{$p} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} # mapPartition |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub start($$) #P Find the starting point for a path in a partition. |
196
|
9
|
|
|
9
|
1
|
76
|
{my ($i, $partition) = @_; # Image, partition |
197
|
9
|
|
|
|
|
14
|
my $remove; # Removal sequence |
198
|
|
|
|
|
|
|
|
199
|
9
|
|
|
|
|
24
|
for my $x((sort{$a<=>$b} keys %{$i->partitions->{$partition} })[0]) # Find the first point in a partition |
|
1924
|
|
|
|
|
3529
|
|
|
9
|
|
|
|
|
136
|
|
200
|
9
|
|
|
|
|
29
|
{for my $y((sort{$a<=>$b} keys %{$i->partitions->{$partition}{$x}})[0]) |
|
104
|
|
|
|
|
200
|
|
|
9
|
|
|
|
|
159
|
|
201
|
9
|
|
|
|
|
355
|
{$remove = [$x, $y]; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
9
|
|
|
|
|
53
|
$i->partitionStart->{$partition} = # Record start point |
206
|
|
|
|
|
|
|
$i->traverseToOtherEnd($partition, @$remove); |
207
|
|
|
|
|
|
|
} # start |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub end($$) #P Find the other end of a path in a partition. |
210
|
9
|
|
|
9
|
1
|
76
|
{my ($i, $partition) = @_; # Image, partition |
211
|
|
|
|
|
|
|
$i->partitionEnd->{$partition} = # Record start point |
212
|
9
|
|
|
|
|
15
|
$i->traverseToOtherEnd($partition, @{$i->partitionStart->{$partition}}); |
|
9
|
|
|
|
|
134
|
|
213
|
|
|
|
|
|
|
} # end |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub traverseToOtherEnd($$$$) #P Traverse to the other end of a partition. |
216
|
18
|
|
|
18
|
1
|
78
|
{my ($I, $partition, $X, $Y) = @_; # Image, partition, start x coordinate, start y coordinate |
217
|
18
|
|
|
|
|
50
|
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 |
218
|
18
|
|
|
|
|
50
|
my @remove = ([$X, $Y]); # Removal sequence |
219
|
18
|
|
|
|
|
52
|
my %remove = ($X=>{$Y=>1}); # Removal sequence deduplication |
220
|
18
|
|
|
|
|
26
|
my $last; # We know that there are two or more pixels in the paritition |
221
|
18
|
|
|
|
|
48
|
while(@remove) |
222
|
9416
|
|
|
|
|
18792
|
{$last = shift @remove; |
223
|
9416
|
|
|
|
|
20673
|
my ($x, $y) = @$last; |
224
|
9416
|
|
|
|
|
181897
|
delete $i->partitions->{$partition}{$x}{$y}; |
225
|
9416
|
|
|
|
|
62864
|
$remove{$x}{$y}++; |
226
|
9416
|
|
|
|
|
19934
|
my @r = $i->searchArea($partition, $x, $y); |
227
|
9416
|
|
|
|
|
17954
|
my @s = grep {my ($x, $y) = @$_; !$remove{$x}{$y}} @r; |
|
18020
|
|
|
|
|
34121
|
|
|
18020
|
|
|
|
|
50178
|
|
228
|
9416
|
|
|
|
|
18588
|
for(@r) |
229
|
18020
|
|
|
|
|
31590
|
{my ($x, $y) = @$_; $remove{$x}{$y}++; |
|
18020
|
|
|
|
|
39620
|
|
230
|
|
|
|
|
|
|
} |
231
|
9416
|
|
|
|
|
15417
|
push @remove, @s; |
232
|
9416
|
|
|
|
|
20026
|
$i->searchArea($partition, $x, $y); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
$last # Last point is the other end |
235
|
18
|
|
|
|
|
1825
|
} # traverseToOtherEnd |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub searchArea($$$$) #P Return the pixels to search from around a given pixel. |
238
|
18841
|
|
|
18841
|
1
|
37695
|
{my ($i, $partition, $x, $y) = @_; # Image, partition, x coordinate of center of search, y coordinate of center of search. |
239
|
18841
|
|
|
|
|
26258
|
my @s; # Pixels to search from |
240
|
18841
|
|
|
|
|
346808
|
my $P = $i->partitions->{$partition}; |
241
|
18841
|
|
|
|
|
123300
|
my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1); |
242
|
18841
|
100
|
100
|
|
|
84156
|
push @s, [$𝘅, $y] if exists $P->{$𝘅} and exists $P->{$𝘅}{$y}; |
243
|
18841
|
100
|
66
|
|
|
73457
|
push @s, [$x, $𝘆] if exists $P->{$x} and exists $P->{$x}{$𝘆}; |
244
|
18841
|
100
|
66
|
|
|
72891
|
push @s, [$x, $𝕪] if exists $P->{$x} and exists $P->{$x}{$𝕪}; |
245
|
18841
|
100
|
100
|
|
|
75805
|
push @s, [$𝕩, $y] if exists $P->{$𝕩} and exists $P->{$𝕩}{$y}; |
246
|
|
|
|
|
|
|
@s # Return all possible pixels |
247
|
18841
|
|
|
|
|
57919
|
} # searchArea |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub checkAtLevelOne($$$) #P Confirm that the specified pixel is at level one. |
250
|
18
|
|
|
18
|
1
|
102
|
{my ($i, $partition, $pixel) = @_; # Image, partition, pixel |
251
|
18
|
|
|
|
|
35
|
my ($x, $y) = @$pixel; |
252
|
18
|
|
|
|
|
241
|
my $h = $i->partitions->{$partition}{$x}{$y}; |
253
|
18
|
50
|
|
|
|
98
|
defined($h) or confess "No pixel in partition=$partition at x=$x, y=$y"; |
254
|
18
|
50
|
|
|
|
40
|
$h == 1 or confess "Pixel in partition=$partition at x=$x, y=$y is $h not one"; |
255
|
|
|
|
|
|
|
} # checkAtLevelOne |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
sub searchAreaHighest($$$$$$) #P Return the highest pixels to search from around a given pixel. |
258
|
37917
|
|
|
37917
|
1
|
68118
|
{my ($i, $partition, $seen, $depth, $x, $y) = @_; # Image, partition, pixels already visited, depth of search, x coordinate of center of search, y coordinate of center of search. |
259
|
37917
|
|
|
|
|
46109
|
my @s; # Pixels to search from |
260
|
37917
|
|
|
|
|
711989
|
my $P = $i->partitions->{$partition}; |
261
|
37917
|
|
|
|
|
234012
|
my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1); |
262
|
37917
|
100
|
100
|
|
|
310534
|
push @s, [$𝘅, $y, $P->{$𝘅}{$y}] if exists $P->{$𝘅} and exists $P->{$𝘅}{$y} and !$seen->{$𝘅}{$y} || $seen->{$𝘅}{$y} > $depth; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
263
|
37917
|
100
|
66
|
|
|
217543
|
push @s, [$x, $𝘆, $P->{$x}{$𝘆}] if exists $P->{$x} and exists $P->{$x}{$𝘆} and !$seen->{$x}{$𝘆} || $seen->{$x}{$𝘆} > $depth; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
264
|
37917
|
100
|
66
|
|
|
247632
|
push @s, [$x, $𝕪, $P->{$x}{$𝕪}] if exists $P->{$x} and exists $P->{$x}{$𝕪} and !$seen->{$x}{$𝕪} || $seen->{$x}{$𝕪} > $depth; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
265
|
37917
|
100
|
100
|
|
|
208047
|
push @s, [$𝕩, $y, $P->{$𝕩}{$y}] if exists $P->{$𝕩} and exists $P->{$𝕩}{$y} and !$seen->{$𝕩}{$y} || $seen->{$𝕩}{$y} > $depth; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
266
|
37917
|
100
|
|
|
|
78760
|
return @s unless @s > 1; # Nothing further to search or just one pixel - which is then the higest pixel returned |
267
|
34763
|
|
|
|
|
88291
|
my @S = sort {$$b[2] <=> $$a[2]} @s; # Highest pixels first |
|
35896
|
|
|
|
|
98143
|
|
268
|
34763
|
|
|
|
|
53179
|
my $h = $S[0][2]; # Highest height |
269
|
34763
|
|
|
|
|
54407
|
grep {$$_[2] == $h} @S # Remove lower pixels |
|
70097
|
|
|
|
|
186076
|
|
270
|
|
|
|
|
|
|
} # searchAreaHighest |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub shortestPathBetweenEndPoints($$$) #P Find the shortest path between the start and the end points of a partition. |
273
|
9
|
|
|
9
|
1
|
95
|
{my ($I, $i, $partition) = @_; # Image, image height clone, partition |
274
|
|
|
|
|
|
|
|
275
|
9
|
|
|
|
|
144
|
$i->checkAtLevelOne($partition, $i->partitionStart->{$partition}); # The end points should be at level one because that is the boundary |
276
|
9
|
|
|
|
|
132
|
$i->checkAtLevelOne($partition, $i->partitionEnd ->{$partition}); |
277
|
|
|
|
|
|
|
|
278
|
9
|
|
|
|
|
12
|
my ($X, $Y) = @{$i->partitionEnd->{$partition}}; # The end point for this partition |
|
9
|
|
|
|
|
127
|
|
279
|
9
|
|
|
|
|
148
|
my @path = ($i->partitionStart->{$partition}); # A possible path |
280
|
9
|
|
|
|
|
42
|
my @shortestPath; # Shortest path so far |
281
|
9
|
|
|
|
|
16
|
my @search = [$i->searchArea($partition, @{$path[0]})]; # Initial search area is the pixels around the start pixel |
|
9
|
|
|
|
|
29
|
|
282
|
9
|
|
|
|
|
14
|
my %seen; # Pixels we have already visited along the possible path |
283
|
|
|
|
|
|
|
|
284
|
9
|
|
|
|
|
31
|
while(@search) # Find the shortest path amongst all the possible paths |
285
|
75843
|
50
|
|
|
|
136237
|
{@path == @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 |
286
|
75843
|
|
|
|
|
106204
|
my $search = $search[-1]; # Pixels to search for latest path element |
287
|
75843
|
100
|
|
|
|
121595
|
if (!@$search) # Nothing left to search at this level |
288
|
37917
|
|
|
|
|
45548
|
{pop @search; # Remove search level |
289
|
37917
|
|
|
|
|
85665
|
pop @path; # Pixel to remove from possible path |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
else |
292
|
37926
|
|
|
|
|
46484
|
{my ($x, $y) = @{pop @$search}; # Next pixel to add to path |
|
37926
|
|
|
|
|
70809
|
|
293
|
37926
|
100
|
100
|
|
|
90933
|
if ($x == $X and $y == $Y) |
294
|
9
|
50
|
33
|
|
|
61
|
{@shortestPath = @path if !@shortestPath or @path < @shortestPath; |
295
|
9
|
|
|
|
|
13
|
pop @search; # Remove search level |
296
|
9
|
|
|
|
|
23
|
pop @path; # Pixel to remove from possible path |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
else # Extend the search |
299
|
37917
|
|
|
|
|
79275
|
{push @path, [$x, $y]; # Extend the path |
300
|
37917
|
|
|
|
|
52742
|
my $P = scalar(@path); # Current path length |
301
|
|
|
|
|
|
|
# e(q(shortestPath)); |
302
|
37917
|
|
|
|
|
91953
|
my @r = $i->searchAreaHighest($partition, \%seen, $P, $x, $y); |
303
|
37917
|
|
|
|
|
74395
|
for(@r) # Update visitation status |
304
|
37918
|
|
|
|
|
69032
|
{my ($x, $y) = @$_; |
305
|
37918
|
50
|
66
|
|
|
161450
|
$seen{$x}{$y} = $P if !exists $seen{$x}{$y} or $seen{$x}{$y} > $P; |
306
|
|
|
|
|
|
|
# e(q(shortestPath - loop)); |
307
|
|
|
|
|
|
|
} |
308
|
37917
|
|
|
|
|
84949
|
push @search, [@r]; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
37926
|
|
|
|
|
50266
|
if (1) # Set minimum path for surrounding pixels |
312
|
37926
|
|
|
|
|
59701
|
{my $P = scalar(@path) + 1; my $Q = $P + 1; |
|
37926
|
|
|
|
|
53013
|
|
313
|
37926
|
|
|
|
|
71021
|
my ($𝘅, $𝕩, $𝘆, $𝕪) = ($x+1, $x-1, $y+1, $y-1); |
314
|
|
|
|
|
|
|
|
315
|
37926
|
100
|
100
|
|
|
129612
|
$seen{$x}{$𝘆} = $P if !exists $seen{$x}{$𝘆} or $seen{$x}{$𝘆} > $P; |
316
|
37926
|
100
|
100
|
|
|
120816
|
$seen{$x}{$𝕪} = $P if !exists $seen{$x}{$𝕪} or $seen{$x}{$𝕪} > $P; |
317
|
37926
|
100
|
100
|
|
|
112049
|
$seen{$𝘅}{$y} = $P if !exists $seen{$𝘅}{$y} or $seen{$𝘅}{$y} > $P; |
318
|
37926
|
100
|
66
|
|
|
148895
|
$seen{$𝕩}{$y} = $P if !exists $seen{$𝕩}{$y} or $seen{$𝕩}{$y} > $P; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
9
|
|
|
|
|
161
|
push @shortestPath, $i->partitionEnd->{$partition}; # Add end point. |
324
|
9
|
|
|
|
|
170
|
$I->partitions = $i->partitions; # Save the partition with height information added |
325
|
9
|
|
|
|
|
1144
|
$I->partitionPath->{$partition} = [@shortestPath] # Return the shortest path |
326
|
|
|
|
|
|
|
} # shortestPathBetweenEndPoints |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub widthOfPath($$) #P Find the (estimated) width of the path at each point. |
329
|
9
|
|
|
9
|
1
|
183
|
{my ($I, $partition) = @_; # Image, partition |
330
|
9
|
|
|
|
|
28
|
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 |
331
|
9
|
|
|
|
|
156
|
my $path = $i->partitionPath->{$partition}; # Path in image |
332
|
9
|
|
|
|
|
55
|
my $maxSteps = @$path; |
333
|
9
|
|
|
|
|
44
|
for my $step(keys @$path) |
334
|
385
|
|
|
|
|
11186
|
{my ($x, $y) = @{$$path[$step]}; |
|
385
|
|
|
|
|
1243
|
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
my $explore = sub #P Explore away from a point checking that we are still in the partition associated with the path |
337
|
3080
|
|
|
3080
|
|
24060
|
{my ($dx, $dy) = @_; # x direction, y direction |
338
|
3080
|
|
|
|
|
6105
|
for my $step(1..$maxSteps) # Maximum possible width |
339
|
|
|
|
|
|
|
{return $step-1 unless $i->partitions->{$partition} # Keep stepping whilst still in partition |
340
|
|
|
|
|
|
|
{$x+$step*$dx} |
341
|
87943
|
100
|
|
|
|
1895839
|
{$y+$step*$dy}; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
$maxSteps # We never left the partition |
344
|
385
|
|
|
|
|
1762
|
}; |
|
0
|
|
|
|
|
0
|
|
345
|
|
|
|
|
|
|
|
346
|
385
|
|
|
|
|
702
|
push @{$I->partitionPath->{$partition}[$step]}, 1 + min # Explore in opposite directions along 4 lines and take the minimum as the width |
|
385
|
|
|
|
|
7093
|
|
347
|
|
|
|
|
|
|
($explore->(1, 0) + $explore->(-1, 0), |
348
|
|
|
|
|
|
|
$explore->(1, 1) + $explore->(-1, -1), |
349
|
|
|
|
|
|
|
$explore->(0, 1) + $explore->( 0, -1), |
350
|
|
|
|
|
|
|
$explore->(1, -1) + $explore->(-1, +1)); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
} # widthOfPath |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub widthOfPaths($) #P Find the (estimated) width of each path at each point. |
355
|
4
|
|
|
4
|
1
|
10
|
{my ($i) = @_; # Image |
356
|
4
|
|
|
|
|
16
|
$i->widthOfPath($_) for 1..$i->numberOfPaths; # Add path width estimate at each point |
357
|
|
|
|
|
|
|
} # widthOfPaths |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub path($$) # Returns an array of arrays [x, y, t] where x, y are the coordinates of each point sequentially along the specified path and t is the estimated thickness of the path at that point. Paths are numbered from 1 to L. |
360
|
1
|
|
|
1
|
1
|
3
|
{my ($i, $partition) = @_; # Image, partition |
361
|
1
|
|
|
|
|
15
|
$i->partitionPath->{$partition} # Return the shortest path |
362
|
|
|
|
|
|
|
} # path |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
sub printHeader($) #P Print a header for the image so we can locate pixels by their coordinates. |
365
|
3
|
|
|
3
|
1
|
6
|
{my ($i) = @_; # Image |
366
|
3
|
|
|
|
|
41
|
my $X = $i->x; my $Y = $i->y; |
|
3
|
|
|
|
|
49
|
|
367
|
3
|
|
|
|
|
15
|
my $indent = length($Y); |
368
|
3
|
|
|
|
|
7
|
my $space = q( ) x $indent; |
369
|
3
|
|
|
|
|
17
|
my $N = 1 + int($X/10); |
370
|
|
|
|
|
|
|
my $s = join '', |
371
|
3
|
100
|
|
|
|
11
|
map{substr($_, -1) ? q( ) : $_ > 9 ? substr($_, -2, 1) : 0} 0..$X; |
|
121
|
100
|
|
|
|
207
|
|
372
|
3
|
|
|
|
|
24
|
my $t = substr(("0123456789"x(1 + int($X/10))), 0, $X); |
373
|
|
|
|
|
|
|
|
374
|
3
|
|
|
|
|
12
|
my $f = "Image: X = $X, Y = $Y, Paths = ".$i->numberOfPaths; # Footer layout |
375
|
|
|
|
|
|
|
|
376
|
3
|
|
|
|
|
34
|
("$space $s\n$space $t\n", "%".$indent."d %s", $f) # Header, line format, footer |
377
|
|
|
|
|
|
|
} # printHeader |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub print($) # Print the image: use B, B to show the start and end of each path, otherwise use the estimated thickness of the path at each point to mark the track of each path within each connected partition of the image. |
380
|
3
|
|
|
3
|
1
|
7
|
{my ($i) = @_; # Image |
381
|
3
|
|
|
|
|
47
|
my $X = $i->x; my $Y = $i->y; |
|
3
|
|
|
|
|
54
|
|
382
|
3
|
|
|
|
|
18
|
my $s = ' ' x $X; |
383
|
3
|
|
|
|
|
11
|
my @s = ($s) x $Y; |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
my $plot = sub # Plot a pixel |
386
|
114
|
|
|
114
|
|
155
|
{my ($x, $y, $symbol) = @_; |
387
|
114
|
|
|
|
|
182
|
substr($s[$y], $x, 1) = $symbol; |
388
|
3
|
|
|
|
|
14
|
}; |
389
|
|
|
|
|
|
|
|
390
|
3
|
|
|
|
|
10
|
my ($header, $line, $footer) = $i->printHeader; |
391
|
|
|
|
|
|
|
|
392
|
3
|
|
|
|
|
5
|
for my $partition(keys %{$i->partitionPath}) # Each path |
|
3
|
|
|
|
|
41
|
|
393
|
8
|
|
|
|
|
27
|
{my ($start, @p) = @{$i->partitionPath->{$partition}}; # Draw path |
|
8
|
|
|
|
|
106
|
|
394
|
8
|
|
|
|
|
65
|
my @start = @$start; pop @start; |
|
8
|
|
|
|
|
19
|
|
395
|
8
|
|
|
|
|
10
|
my @end = @{pop @p}; pop @end; |
|
8
|
|
|
|
|
16
|
|
|
8
|
|
|
|
|
13
|
|
396
|
|
|
|
|
|
|
|
397
|
8
|
|
|
|
|
12
|
$plot->(@start, q(S)); |
398
|
8
|
|
|
|
|
16
|
for(@p) |
399
|
98
|
|
|
|
|
163
|
{my ($x, $y, $h) = @$_; |
400
|
98
|
|
|
|
|
138
|
$plot->($x, $y, $h % 10); |
401
|
|
|
|
|
|
|
} |
402
|
8
|
|
|
|
|
12
|
$plot->(@end, q(E)); |
403
|
|
|
|
|
|
|
} |
404
|
3
|
|
|
|
|
12
|
join "\n", $header, (map{sprintf($line, $_, $s[$_])} keys @s), $footer |
|
31
|
|
|
|
|
104
|
|
405
|
|
|
|
|
|
|
} # print |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
#1 Attributes # Attributes of an image |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
genLValueScalarMethods(q(count)); # Number of points in the image. |
410
|
|
|
|
|
|
|
genLValueScalarMethods(q(image)); # Image data points. |
411
|
|
|
|
|
|
|
genLValueScalarMethods(q(partitions)); # Number of partitions in the image. |
412
|
|
|
|
|
|
|
genLValueScalarMethods(q(partitionEnd)); # End points for each path. |
413
|
|
|
|
|
|
|
genLValueScalarMethods(q(partitionStart)); # Start points for each path. |
414
|
|
|
|
|
|
|
genLValueScalarMethods(q(partitionPath)); # Path for each partition. |
415
|
|
|
|
|
|
|
genLValueScalarMethods(q(x)); # Image dimension in x. |
416
|
|
|
|
|
|
|
genLValueScalarMethods(q(y)); # Image dimension in y. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
419
|
|
|
|
|
|
|
# Export |
420
|
|
|
|
|
|
|
#------------------------------------------------------------------------------- |
421
|
|
|
|
|
|
|
|
422
|
1
|
|
|
1
|
|
4691
|
use Exporter qw(import); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
36
|
|
423
|
|
|
|
|
|
|
|
424
|
1
|
|
|
1
|
|
7
|
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
376
|
|
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
427
|
|
|
|
|
|
|
@EXPORT_OK = qw( |
428
|
|
|
|
|
|
|
); |
429
|
|
|
|
|
|
|
%EXPORT_TAGS = (all=>[@EXPORT, @EXPORT_OK]); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# podDocumentation |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=pod |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
=encoding utf-8 |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=head1 Name |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
Image::Find::Paths - Find paths in an image. |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=head1 Synopsis |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
Use L to create and analyze a new image, then L to |
444
|
|
|
|
|
|
|
visualize the paths detected, or L to get the coordinates of points |
445
|
|
|
|
|
|
|
along each path in sequential order with an estimate of the thickness of the |
446
|
|
|
|
|
|
|
path at each point. |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=head1 Description |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Find paths in an image. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
The following sections describe the methods in each functional area of this |
453
|
|
|
|
|
|
|
module. For an alphabetic listing of all methods by name see L. |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=head1 Methods |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Find paths in an image |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head2 new($) |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Find paths in an image represented as a string. |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Parameter Description |
466
|
|
|
|
|
|
|
1 $string String of blanks; non blanks; new lines defining the image |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
Example: |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
my $d = new(<
|
472
|
|
|
|
|
|
|
11 111 |
473
|
|
|
|
|
|
|
11 1 111 |
474
|
|
|
|
|
|
|
1111 111 111 |
475
|
|
|
|
|
|
|
1 111111 1 111 |
476
|
|
|
|
|
|
|
111 1111 111 111 |
477
|
|
|
|
|
|
|
11 1111111 1 1 |
478
|
|
|
|
|
|
|
11 11111 1 1 |
479
|
|
|
|
|
|
|
1 111 1 1 1 |
480
|
|
|
|
|
|
|
1111111111 1 111111 1 |
481
|
|
|
|
|
|
|
111 1 1 1 |
482
|
|
|
|
|
|
|
END |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
is_deeply [$d->count, $d->x, $d->y, $d->numberOfPaths], [96, 80, 10, 6]; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
ok nws($d->print) eq nws(<
|
487
|
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 |
488
|
|
|
|
|
|
|
01234567890123456789012345678901234567890123456789012345678901234567890123456789 |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
0 E1 E |
491
|
|
|
|
|
|
|
1 11 23 |
492
|
|
|
|
|
|
|
2 1111 3 |
493
|
|
|
|
|
|
|
3 1 322E S 3 |
494
|
|
|
|
|
|
|
4 111 2 E1 2S |
495
|
|
|
|
|
|
|
5 11 221S 1 |
496
|
|
|
|
|
|
|
6 11 23 1 E |
497
|
|
|
|
|
|
|
7 1 3 1 S 1 |
498
|
|
|
|
|
|
|
8 11111112 1 E1111 1 |
499
|
|
|
|
|
|
|
9 S S |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Image: X = 80, Y = 10, Paths = 6 |
502
|
|
|
|
|
|
|
END |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
is_deeply $d->path(5), |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
[[79,4, 1], [78,4, 2], [78,3, 3], [78,2, 3], [78,1, 3], [77,1, 2], [77,0, 1]]; |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
This is a static method and so should be invoked as: |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
Image::Find::Paths::new |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=head2 numberOfPaths($) |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
Number of paths in the image. |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
Parameter Description |
519
|
|
|
|
|
|
|
1 $i Image |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
Example: |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
is_deeply [$d->count, $d->x, $d->y, $d->numberOfPaths], [96, 80, 10, 6]; |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
=head2 path($$) |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
Returns an array of arrays [x, y, t] where x, y are the coordinates of each point sequentially along the specified path and t is the estimated thickness of the path at that point. Paths are numbered from 1 to L. |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Parameter Description |
532
|
|
|
|
|
|
|
1 $i Image |
533
|
|
|
|
|
|
|
2 $partition Partition |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
Example: |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
is_deeply $d->path(5), |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
[[79,4, 1], [78,4, 2], [78,3, 3], [78,2, 3], [78,1, 3], [77,1, 2], [77,0, 1]]; |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
=head2 print($) |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
Print the image: use B, B to show the start and end of each path, otherwise use the estimated thickness of the path at each point to mark the track of each path within each connected partition of the image. |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Parameter Description |
548
|
|
|
|
|
|
|
1 $i Image |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
Example: |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
ok nws($d->print) eq nws(<
|
554
|
|
|
|
|
|
|
0 1 2 3 4 5 6 7 8 |
555
|
|
|
|
|
|
|
01234567890123456789012345678901234567890123456789012345678901234567890123456789 |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
0 E1 E |
558
|
|
|
|
|
|
|
1 11 23 |
559
|
|
|
|
|
|
|
2 1111 3 |
560
|
|
|
|
|
|
|
3 1 322E S 3 |
561
|
|
|
|
|
|
|
4 111 2 E1 2S |
562
|
|
|
|
|
|
|
5 11 221S 1 |
563
|
|
|
|
|
|
|
6 11 23 1 E |
564
|
|
|
|
|
|
|
7 1 3 1 S 1 |
565
|
|
|
|
|
|
|
8 11111112 1 E1111 1 |
566
|
|
|
|
|
|
|
9 S S |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
Image: X = 80, Y = 10, Paths = 6 |
569
|
|
|
|
|
|
|
END |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=head1 Attributes |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Attributes of an image |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
=head2 count :lvalue |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
Number of points in the image. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=head2 image :lvalue |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
Image data points. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=head2 partitions :lvalue |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
Number of partitions in the image. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=head2 partitionEnd :lvalue |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
End points for each path. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=head2 partitionStart :lvalue |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
Start points for each path. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=head2 partitionPath :lvalue |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
Path for each partition. |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=head2 x :lvalue |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
Image dimension in x. |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=head2 y :lvalue |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
Image dimension in y. |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=head1 Private Methods |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
=head2 clone($) |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
Clone an image. |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
Parameter Description |
624
|
|
|
|
|
|
|
1 $i Image |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
Example: |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
is_deeply $d, $d->clone; |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=head2 clonePartition($$) |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
Clone a partition of an image. |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
Parameter Description |
637
|
|
|
|
|
|
|
1 $i Image |
638
|
|
|
|
|
|
|
2 $partition Partition |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=head2 countPixels($) |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
Count the pixels in an image. |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
Parameter Description |
645
|
|
|
|
|
|
|
1 $i Image |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=head2 height($) |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
Clone an image adding height to each pixel. |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
Parameter Description |
652
|
|
|
|
|
|
|
1 $i Image |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=head2 partition($) |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
Partition the images into disjoint sets of connected points. |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
Parameter Description |
659
|
|
|
|
|
|
|
1 $i Image |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
=head2 mapPartition($$$) |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
Locate the pixels in the image that are connected to a pixel with a specified value. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
Parameter Description |
666
|
|
|
|
|
|
|
1 $i Image |
667
|
|
|
|
|
|
|
2 $x X coordinate of first point in partition |
668
|
|
|
|
|
|
|
3 $y Y coordinate of first point in partition |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
=head2 start($$) |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
Find the starting point for a path in a partition. |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
Parameter Description |
675
|
|
|
|
|
|
|
1 $i Image |
676
|
|
|
|
|
|
|
2 $partition Partition |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=head2 end($$) |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
Find the other end of a path in a partition. |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
Parameter Description |
683
|
|
|
|
|
|
|
1 $i Image |
684
|
|
|
|
|
|
|
2 $partition Partition |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=head2 traverseToOtherEnd($$$$) |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
Traverse to the other end of a partition. |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
Parameter Description |
691
|
|
|
|
|
|
|
1 $I Image |
692
|
|
|
|
|
|
|
2 $partition Partition |
693
|
|
|
|
|
|
|
3 $X Start x coordinate |
694
|
|
|
|
|
|
|
4 $Y Start y coordinate |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=head2 searchArea($$$$) |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
Return the pixels to search from around a given pixel. |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
Parameter Description |
701
|
|
|
|
|
|
|
1 $i Image |
702
|
|
|
|
|
|
|
2 $partition Partition |
703
|
|
|
|
|
|
|
3 $x X coordinate of center of search |
704
|
|
|
|
|
|
|
4 $y Y coordinate of center of search. |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
=head2 checkAtLevelOne($$$) |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
Confirm that the specified pixel is at level one. |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
Parameter Description |
711
|
|
|
|
|
|
|
1 $i Image |
712
|
|
|
|
|
|
|
2 $partition Partition |
713
|
|
|
|
|
|
|
3 $pixel Pixel |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=head2 searchAreaHighest($$$$$$) |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
Return the highest pixels to search from around a given pixel. |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
Parameter Description |
720
|
|
|
|
|
|
|
1 $i Image |
721
|
|
|
|
|
|
|
2 $partition Partition |
722
|
|
|
|
|
|
|
3 $seen Pixels already visited |
723
|
|
|
|
|
|
|
4 $depth Depth of search |
724
|
|
|
|
|
|
|
5 $x X coordinate of center of search |
725
|
|
|
|
|
|
|
6 $y Y coordinate of center of search. |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
=head2 shortestPathBetweenEndPoints($$$) |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
Find the shortest path between the start and the end points of a partition. |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
Parameter Description |
732
|
|
|
|
|
|
|
1 $I Image |
733
|
|
|
|
|
|
|
2 $i Image height clone |
734
|
|
|
|
|
|
|
3 $partition Partition |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
=head2 widthOfPath($$) |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
Find the (estimated) width of the path at each point. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
Parameter Description |
741
|
|
|
|
|
|
|
1 $I Image |
742
|
|
|
|
|
|
|
2 $partition Partition |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
=head2 widthOfPaths($) |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
Find the (estimated) width of each path at each point. |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
Parameter Description |
749
|
|
|
|
|
|
|
1 $i Image |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
=head2 printHeader($) |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
Print a header for the image so we can locate pixels by their coordinates. |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
Parameter Description |
756
|
|
|
|
|
|
|
1 $i Image |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
=head1 Index |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
1 L |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
2 L |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
3 L |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
4 L |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
5 L |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
6 L |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
7 L |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
8 L |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
9 L |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
10 L |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
11 L |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
12 L |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
13 L |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
14 L |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
15 L |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
16 L |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
17 L |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
18 L |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
19 L |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
20 L |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
21 L |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
22 L |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
23 L |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
24 L |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
25 L |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
26 L |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
27 L |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
28 L |
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
=head1 Installation |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
This module is written in 100% Pure Perl and, thus, it is easy to read, use, |
821
|
|
|
|
|
|
|
modify and install. |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
Standard L process for building and installing modules: |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
perl Build.PL |
826
|
|
|
|
|
|
|
./Build |
827
|
|
|
|
|
|
|
./Build test |
828
|
|
|
|
|
|
|
./Build install |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
=head1 Author |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
L |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
L |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=head1 Copyright |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
Copyright (c) 2016-2018 Philip R Brenan. |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
This module is free software. It may be used, redistributed and/or modified |
841
|
|
|
|
|
|
|
under the same terms as Perl itself. |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
=cut |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
# Tests and documentation |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
sub test |
850
|
1
|
|
|
1
|
0
|
9
|
{my $p = __PACKAGE__; |
851
|
1
|
|
|
|
|
7
|
binmode($_, ":utf8") for *STDOUT, *STDERR; |
852
|
1
|
50
|
|
|
|
47
|
return if eval "eof(${p}::DATA)"; |
853
|
1
|
|
|
|
|
43
|
my $s = eval "join('', <${p}::DATA>)"; |
854
|
1
|
50
|
|
|
|
5
|
$@ and die $@; |
855
|
1
|
100
|
|
1
|
0
|
6
|
eval $s; |
|
1
|
|
|
1
|
|
2
|
|
|
1
|
|
|
1
|
|
30
|
|
|
1
|
|
|
2
|
|
4
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
19
|
|
|
1
|
|
|
|
|
500
|
|
|
1
|
|
|
|
|
52124
|
|
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
63
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
52
|
|
|
2
|
|
|
|
|
48
|
|
|
2
|
|
|
|
|
3721
|
|
856
|
1
|
50
|
|
|
|
178
|
$@ and die $@; |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
test unless caller; |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
1; |
862
|
|
|
|
|
|
|
# podDocumentation |
863
|
|
|
|
|
|
|
__DATA__ |