File Coverage

lib/PDL/Image2D.pd
Criterion Covered Total %
statement 8 8 100.0
branch 92 1104 8.3
condition n/a
subroutine n/a
pod n/a
total 100 1112 8.9


line stmt bran cond sub pod time code
1             use strict;
2             use warnings;
3             use PDL::Types qw(ppdefs_all);
4             my $A = [ppdefs_all];
5              
6             { no warnings 'once'; # pass info back to Makefile.PL
7             $PDL::Core::Dev::EXTRAS{$::PDLMOD}{OBJECT} .= join '', map " $::PDLBASE/$_\$(OBJ_EXT)", qw(rotate resample select misc);
8             $PDL::Core::Dev::EXTRAS{$::PDLMOD}{INC} .= qq{ "-I$::PDLBASE"};
9             }
10              
11             pp_addpm({At=>'Top'},<<'EOD');
12             use strict;
13             use warnings;
14              
15             =head1 NAME
16              
17             PDL::Image2D - Miscellaneous 2D image processing functions
18              
19             =head1 DESCRIPTION
20              
21             Miscellaneous 2D image processing functions - for want
22             of anywhere else to put them.
23              
24             =head1 SYNOPSIS
25              
26             use PDL::Image2D;
27              
28             =cut
29              
30             use PDL; # ensure qsort routine available
31             use PDL::Math;
32             use Carp;
33              
34             my %boundary2value = (Reflect=>1, Truncate=>2, Replicate=>3);
35             EOD
36              
37             pp_addpm({At=>'Bot'},<<'EOD');
38              
39             =head1 AUTHORS
40              
41             Copyright (C) Karl Glazebrook 1997 with additions by Robin Williams
42             (rjrw@ast.leeds.ac.uk), Tim Jenness (timj@jach.hawaii.edu),
43             and Doug Burke (burke@ifa.hawaii.edu).
44              
45             All rights reserved. There is no warranty. You are allowed
46             to redistribute this software / documentation under certain
47             conditions. For details, see the file COPYING in the PDL
48             distribution. If this file is separated from the PDL distribution,
49             the copyright notice should be included in the file.
50              
51             =cut
52              
53             EOD
54              
55             #################################################
56             # BEGIN INTERNAL FUNCTION DECLARATIONS #
57             #################################################
58              
59             pp_def('polyfill_pp',
60             HandleBad => 0, # a marker
61             Pars => '[io] im(m,n); float ps(two=2,np); int col()',
62             GenericTypes => ['L'],
63             CHeader => qq{void polyfill(PDL_Long *image, int wx, int wy, float *ps, int n, PDL_Long col, int *ierr);\n},
64             Code => 'int ierr = 0, nerr;
65             broadcastloop %{
66             polyfill($P(im), $SIZE(m), $SIZE(n), $P(ps), $SIZE(np), $col(), &nerr);
67             ierr = PDLMAX(ierr, nerr);
68             %}
69             if (ierr) warn("errors during polygonfilling");
70             ',
71             Doc => undef,
72             PMFunc => ''
73             );
74              
75             my %pnpolyFields = (
76             'pnpoly_pp' => {'pars' => 'a(m,n); ps(k,l); int [o] msk(m,n)', 'special' => '$msk() = c;'},
77             'pnpolyfill_pp' => {'pars' => '[io] a(m,n); ps(k,l); int col()', 'special' => 'if(c) { $a() = $col(); }'}
78             );
79              
80             for my $name (sort keys %pnpolyFields) {
81             pp_def($name,
82             HandleBad => 0,
83             PMFunc => '',
84             Doc => undef,
85             Pars => $pnpolyFields{$name}->{'pars'},
86             Code => '
87             #define VERTX(q) $ps(k=>0,l=>q)
88             #define VERTY(q) $ps(k=>1,l=>q)
89              
90             broadcastloop %{
91             loop(n,m) %{
92             PDL_Indx j = $SIZE(l)-1, c = 0;
93             loop (l) %{
94             if ( ((VERTY(l)>n) != (VERTY(j)>n)) &&
95             (m < (VERTX(j)-VERTX(l)) * (n-VERTY(l)) / (VERTY(j)-VERTY(l)) + VERTX(l)) )
96             c = !c;
97             j = l;
98             %}
99             ' . $pnpolyFields{$name}{special} .'
100             %}
101             %}
102              
103             #undef VERTX
104             #undef VERTY
105             '
106             );
107             }
108              
109             pp_export_nothing(); # Clear the export list
110              
111             #################################################
112             # END INTERNAL FUNCTION DECLARATIONS #
113             #################################################
114              
115              
116             pp_addhdr('
117              
118             /* Fast Modulus with proper negative behaviour */
119              
120             #define REALMOD(a,b) {while ((a)>=(b)) (a) -= (b); while ((a)<0) (a) += (b);}
121              
122             #define X(symbol, ctype, ppsym, ...) \
123             ctype quick_select_ ## ppsym(ctype arr[], int n);
124             PDL_TYPELIST_REAL(X)
125             #undef X
126             ');
127              
128             my %init =
129             (
130             i => { size => 'm_size', off => 'poff', init => '1-p_size' },
131             j => { size => 'n_size', off => 'qoff', init => '1-q_size' },
132             );
133              
134             # requires 'int $var, ${var}2' to have been declared in the c code
135             # (along with [pq]off and [pq]_size)
136             #
137             sub init_map {
138             my $var = shift;
139              
140             my $loop = $var;
141             my $loop2 = "${var}2";
142              
143             my $href = $init{$var} ||
144             die "ERROR: unknown variable sent to init_map()\n";
145             my $size = $href->{size} ||
146             die "ERROR: unable to find size for $var\n";
147             my $off = $href->{off} ||
148             die "ERROR: unable to find off for $var\n";
149             my $init = $href->{init} ||
150             die "ERROR: unable to find init for $var\n";
151              
152             return
153             "for ( $loop = $init; $loop< $size; ${loop}++) {
154             $loop2 = $loop + $off;
155             switch (opt) {
156             case 1: /* REFLECT */
157             if (${loop2}<0)
158             $loop2 = -${loop2}-1;
159             else if ($loop2 >= $size)
160             $loop2 = 2*${size}-(${loop2}+1);
161             break;
162             case 2: /* TRUNCATE */
163             if (${loop2}<0 || ${loop2} >= $size)
164             $loop2 = -1;
165             break;
166             case 3: /* REPLICATE */
167             if (${loop2}<0)
168             $loop2 = 0;
169             if (${loop2} >= $size)
170             $loop2 = $size-1;
171             break;
172             default:
173             REALMOD($loop2,$size);
174             }
175             map${var}\[$loop] = $loop2;
176             }\n";
177              
178             } # sub: init_map()
179              
180             sub init_vars {
181             my $sizevars = shift || [qw(m n p q)];
182             my $compvars = shift || [];
183             my $str = "int i,j, i2,j2, poff, qoff;";
184             $str .=
185             'int opt = $COMP(opt);
186             ' . join("\n", map "int ${_}_size = \$SIZE(${_});", @$sizevars) . '
187             ' . join("\n", map "int ${_}_size = \$COMP(${_}_size);", @$compvars) . '
188             PDL_Indx *mapi = $P(mapi), *mapj = $P(mapj);
189             if ((mapi==NULL) || (mapj==NULL)) $CROAK("Out of Memory");
190              
191             poff = p_size/2; mapi += p_size-1;
192             qoff = q_size/2; mapj += q_size-1;
193             ';
194              
195             return $str;
196             } # sub: init_vars()
197              
198             pp_def('conv2d', Doc=><<'EOD',
199             =for ref
200              
201             2D convolution of an array with a kernel (smoothing)
202              
203             For large kernels, using a FFT routine,
204             such as L,
205             will be quicker.
206              
207             =for usage
208              
209             $new = conv2d $old, $kernel, {OPTIONS}
210              
211             =for example
212              
213             $smoothed = conv2d $image, ones(3,3), {Boundary => Reflect}
214              
215             =for options
216              
217             Boundary - controls what values are assumed for the image when kernel
218             crosses its edge:
219             => Default - periodic boundary conditions
220             (i.e. wrap around axis)
221             => Reflect - reflect at boundary
222             => Truncate - truncate at boundary
223             => Replicate - repeat boundary pixel values
224              
225             =cut
226             EOD
227             BadDoc =>
228             'Unlike the FFT routines, conv2d is able to process bad values.',
229             HandleBad => 1,
230             Pars => 'a(m,n); kern(p,q); [o]b(m,n); indx [t]mapi(isize=CALC($SIZE(p) + $SIZE(m))); indx [t]mapj(jsize=CALC($SIZE(q) + $SIZE(n)))',
231             OtherPars => 'int opt;',
232             PMCode => '
233             sub PDL::conv2d {
234             my $opt; $opt = pop @_ if ref($_[$#_]) eq \'HASH\';
235             die \'Usage: conv2d( a(m,n), kern(p,q), [o]b(m,n), {Options} )\'
236             if $#_<1 || $#_>2;
237             my($x,$kern) = @_;
238             my $c = $#_ == 2 ? $_[2] : $x->nullcreate;
239             PDL::_conv2d_int($x,$kern,$c,
240             (!($opt && exists $$opt{Boundary}))?0:$boundary2value{$$opt{Boundary}}
241             );
242             return $c;
243             }
244             ',
245             GenericTypes => $A,
246             Code =>
247             init_vars() .
248             init_map("i") .
249             init_map("j") .
250             pp_line_numbers(__LINE__, <<'EOF'),
251             broadcastloop %{
252             loop(n,m) %{
253             PDL_IF_BAD(int flag = 0;,)
254             PDL_CLDouble tmp = 0;
255             loop (q) %{
256             j2 = mapj[n-q];
257             if (j2 >= 0) {
258             loop (p) %{
259             i2 = mapi[m-p];
260             if (i2 >= 0) {
261             PDL_IF_BAD(if ( $ISGOOD(a(m=>i2,n=>j2)) && $ISGOOD(kern()) ),) {
262             tmp += $a(m=>i2,n=>j2) * $kern();
263             PDL_IF_BAD(flag = 1;,)
264             } /* if: good */
265             } /* if: i2 >= 0 */
266             %}
267             } /* if: j2 >= 0 */
268             %}
269             PDL_IF_BAD(if ( !flag ) { $SETBAD(b()); }
270             else,) { $b() = tmp; }
271             %}
272             %}
273             EOF
274             ); # pp_def: conv2d
275              
276             pp_def('med2d', Doc=> <<'EOD',
277             =for ref
278              
279             2D median-convolution of an array with a kernel (smoothing)
280              
281             Note: only points in the kernel E0 are included in the median, other
282             points are weighted by the kernel value (medianing lots of zeroes
283             is rather pointless)
284              
285             =for usage
286              
287             $new = med2d $old, $kernel, {OPTIONS}
288              
289             =for example
290              
291             $smoothed = med2d $image, ones(3,3), {Boundary => Reflect}
292              
293             =for options
294              
295             Boundary - controls what values are assumed for the image when kernel
296             crosses its edge:
297             => Default - periodic boundary conditions (i.e. wrap around axis)
298             => Reflect - reflect at boundary
299             => Truncate - truncate at boundary
300             => Replicate - repeat boundary pixel values
301              
302             =cut
303             EOD
304             BadDoc =>
305             'Bad values are ignored in the calculation. If all elements within the
306             kernel are bad, the output is set bad.',
307             HandleBad => 1,
308             Pars => 'a(m,n); kern(p,q); [o]b(m,n); double+ [t]tmp(pq=CALC($SIZE(p)*$SIZE(q))); indx [t]mapi(isize=CALC($SIZE(p) + $SIZE(m))); indx [t]mapj(jsize=CALC($SIZE(q) + $SIZE(n)))',
309             OtherPars => 'int opt;',
310             PMCode => pp_line_numbers(__LINE__, <<'EOF'),
311             sub PDL::med2d {
312             my $opt; $opt = pop @_ if ref($_[$#_]) eq 'HASH';
313             die 'Usage: med2d( a(m,n), kern(p,q), [o]b(m,n), {Options} )'
314             if $#_<1 || $#_>2;
315             my($x,$kern) = @_;
316             croak "med2d: kernel must contain some positive elements.\n"
317             if all( $kern <= 0 );
318             my $c = $#_ == 2 ? $_[2] : $x->nullcreate;
319             PDL::_med2d_int($x,$kern,$c,
320             (!($opt && exists $$opt{Boundary}))?0:$boundary2value{$$opt{Boundary}}
321             );
322             return $c;
323             }
324             EOF
325             CHeader => 'PDL_TYPELIST_FLOATREAL(PDL_QSORT)',
326             Code =>
327             init_vars() .
328             init_map("i") .
329             init_map("j") .
330             pp_line_numbers(__LINE__, <<'EOF'),
331             broadcastloop %{
332             loop (n,m) %{
333             PDL_Indx count = 0;
334             PDL_IF_BAD(int flag = 0;,)
335             loop (q) %{
336             j2 = mapj[n-q];
337             if (j2 >= 0)
338             loop (p) %{
339             i2 = mapi[m-p];
340             if (i2 >= 0) {
341             PDL_LDouble kk = $kern(), aa = $a(m=>i2,n=>j2);
342             PDL_IF_BAD(if ( $ISGOODVAR(kk,kern) && $ISGOODVAR(aa,a) ),) {
343             PDL_IF_BAD(flag = 1;,)
344             if ( kk > 0 )
345             $tmp(pq=>count++) = aa * kk;
346             }
347             } /* if: i2 >= 0 */
348             %}
349             %}
350             PDL_IF_BAD(if ( flag == 0 ) {
351             $SETBAD(b());
352             } else,) {
353             qsort_$PPSYM(tmp)( $P(tmp), 0, count-1 );
354             $b() = $tmp(pq=>(count-1)/2);
355             }
356             %}
357             %}
358             EOF
359             ); # pp_def: med2d
360              
361             pp_def('med2df', Doc=> <<'EOD',
362              
363             =for ref
364              
365             2D median-convolution of an array in a pxq window (smoothing)
366              
367             Note: this routine does the median over all points in a rectangular
368             window and is not quite as flexible as C in this regard
369             but slightly faster instead
370              
371             =for usage
372              
373             $new = med2df $old, $xwidth, $ywidth, {OPTIONS}
374              
375             =for example
376              
377             $smoothed = med2df $image, 3, 3, {Boundary => Reflect}
378              
379             =for options
380              
381             Boundary - controls what values are assumed for the image when kernel
382             crosses its edge:
383             => Default - periodic boundary conditions (i.e. wrap around axis)
384             => Reflect - reflect at boundary
385             => Truncate - truncate at boundary
386             => Replicate - repeat boundary pixel values
387              
388             =cut
389              
390             EOD
391             Pars => 'a(m,n); [o]b(m,n); indx [t]mapi(isize=CALC($SIZE(p) + $SIZE(m))); indx [t]mapj(jsize=CALC($SIZE(q) + $SIZE(n)))',
392             OtherPars => 'IV p_size=>p; IV q_size=>q; int opt;',
393             PMCode => <<'EOF',
394             sub PDL::med2df {
395             my $opt; $opt = pop @_ if ref($_[$#_]) eq 'HASH';
396             die 'Usage: med2df( a(m,n), [o]b(m,n), p, q, {Options} )'
397             if $#_<2 || $#_>3;
398             my($x,$p,$q) = @_;
399             croak "med2df: kernel must contain some positive elements.\n"
400             if $p == 0 && $q == 0;
401             my $c = $#_ == 3 ? $_[3] : $x->nullcreate;
402             &PDL::_med2df_int($x,$c,$p,$q,
403             (!($opt && exists $$opt{Boundary}))?0:$boundary2value{$$opt{Boundary}}
404             );
405             return $c;
406             }
407             EOF
408             Code =>
409             init_vars() .
410             init_map("i") .
411             init_map("j") .
412             '
413             $GENERIC() tmp[p_size*q_size];
414             broadcastloop %{
415             loop (n,m) %{
416             int count = 0;
417             loop (q) %{
418             if ((j2 = mapj[n-q]) < 0) continue;
419             loop (p) %{
420             if ((i2 = mapi[m-p]) < 0) continue;
421             tmp[count++] = $a(m=>i2,n=>j2);
422             %}
423             %}
424             $b() = quick_select_$PPSYM() (tmp, count );
425             %}
426             %}
427             ',
428              
429             ); # pp_def: med2df
430              
431             pp_addhdr(<<'EOH');
432             #define EZ(x) ez ? 0 : (x)
433             EOH
434             pp_def('box2d',
435             Pars => 'a(n,m); [o] b(n,m)',
436             OtherPars => 'int wx; int wy; int edgezero',
437             GenericTypes => $A,
438             Code => '
439             register int nx = 0.5*$COMP(wx);
440             register int ny = 0.5*$COMP(wy);
441             register int ez = $COMP(edgezero);
442             PDL_CLDouble div, sum, lsum;
443             int ind1,ind2,first;
444              
445             div = 1/((2.0*nx+1)*(2.0*ny+1));
446              
447             broadcastloop %{
448             first = 1;
449             loop (m,n=:nx) %{
450             ind1 = $SIZE(n)-1-n; /* rightmost strip */
451             $b() = EZ($a());
452             $b(n=>ind1) = EZ($a(n=>ind1));
453             %}
454             loop (n,m=:ny) %{
455             ind1 = $SIZE(m)-1-m; /* topmost strip */
456             $b() = EZ($a());
457             $b(m=>ind1) = EZ($a(m=>ind1));
458             %}
459             loop (m=ny:-ny) %{
460             if (first) {
461             first = 0;
462             lsum = 0;
463             PDL_Indx m_outer = m;
464             loop (m=m_outer-ny:m_outer+ny+1,n=:2*nx+1) %{
465             lsum += $a(); /* initialize sum */
466             %}
467             } else {
468             ind1 = m-ny-1;
469             ind2 = m+ny;
470             loop (n=:2*nx+1) %{
471             lsum -= $a(m=>ind1); /* remove top pixels */
472             lsum += $a(m=>ind2); /* add bottom pixels */
473             %}
474             }
475             sum = lsum;
476             $b(n=>nx) = div*sum; /* and assign */
477             loop (n=nx+1:-nx) %{ /* loop along line */
478             ind1 = n-nx-1;
479             ind2 = n+nx;
480             PDL_Indx m_outer = m;
481             loop (m=m_outer-ny:m_outer+ny+1) %{
482             sum -= $a(n=>ind1); /* remove leftmost data */
483             sum += $a(n=>ind2); /* and add rightmost */
484             %}
485             $b() = div*sum; /* and assign */
486             %}
487             %}
488             %}',
489             Doc => << 'EOD',
490             =for ref
491              
492             fast 2D boxcar average
493              
494             =for example
495              
496             $smoothim = $im->box2d($wx,$wy,$edgezero=1);
497              
498             The edgezero argument controls if edge is set to zero (edgezero=1)
499             or just keeps the original (unfiltered) values.
500              
501             C should be updated to support similar edge options
502             as C and C etc.
503              
504             Boxcar averaging is a pretty crude way of filtering. For serious stuff
505             better filters are around (e.g., use L with the appropriate
506             kernel). On the other hand it is fast and computational cost grows only
507             approximately linearly with window size.
508              
509             =cut
510              
511             EOD
512             ); # pp_def box2d
513              
514             =head2 patch2d
515              
516             =cut
517              
518             pp_def('patch2d',
519             Doc=><<'EOD',
520              
521             =for ref
522              
523             patch bad pixels out of 2D images using a mask
524              
525             C<$bad> is a 2D mask array where 1=bad pixel 0=good pixel.
526             Pixels are replaced by the average of their non-bad neighbours;
527             if all neighbours are bad, the original data value is
528             copied across.
529              
530             =cut
531              
532             EOD
533             BadDoc =>
534             'This routine does not handle bad values - use L instead',
535             HandleBad => 0,
536             Pars => 'a(m,n); int bad(m,n); [o]b(m,n);',
537             GenericTypes => $A,
538             Code =>
539             'PDL_Indx i1,j1, i2,j2, norm;
540             PDL_CLDouble tmp;
541             broadcastloop %{
542             loop (n,m) %{
543             $b() = $a();
544             if (!$bad()) continue;
545             tmp = 0; norm=0;
546             for(j1=-1; j1<=1; j1++) {
547             j2 = n+j1;
548             if ( j2<0 || j2>=$SIZE(n) ) continue;
549             for(i1=-1; i1<=1; i1++) {
550             /* ignore central pixel, which we know is bad */
551             if ( i1==0 && j1==0 ) continue;
552             i2 = m+i1;
553             if ( i2<0 || i2>=$SIZE(m) || $bad(m=>i2,n=>j2) ) continue;
554             tmp += $a(m=>i2,n=>j2);
555             norm++;
556             } /* for: i1 */
557             } /* for: j1 */
558             if (norm>0) { /* Patch */
559             $b() = tmp/norm;
560             }
561             %}
562             %} /* broadcastloop */
563             ', # Code
564             );
565              
566             pp_def('patchbad2d',
567             Doc=><<'EOD',
568             =for ref
569              
570             patch bad pixels out of 2D images containing bad values
571              
572             Pixels are replaced by the average of their non-bad neighbours;
573             if all neighbours are bad, the output is set bad.
574             If the input ndarray contains I bad values, then a straight copy
575             is performed (see L).
576             EOD
577             BadDoc =>
578             'patchbad2d handles bad values. The output ndarray I contain
579             bad values, depending on the pattern of bad values in the input ndarray.',
580             HandleBad => 1,
581             Pars => 'a(m,n); [o]b(m,n);',
582             GenericTypes => $A,
583             Code => pp_line_numbers(__LINE__, <<'EOF'),
584 2         8 PDL_IF_BAD(int flag = 0;,)
585 9 50       3 broadcastloop %{
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    50          
    50          
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
586 63 0       39 loop(n,m) %{
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
587 51         5 $GENERIC(a) a_val = $a();
588 134 0       3 PDL_IF_BAD(if ( !$ISGOODVAR(a_val,a) ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    100          
    50          
    50          
    100          
    100          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
589             PDL_CLDouble tmp = 0; PDL_Indx norm=0; int i1; int j1;
590             for(j1=-1; j1<=1; j1++) {
591             PDL_Indx j2 = n+j1;
592             if ( j2<0 || j2>=$SIZE(n) ) continue;
593             for(i1=-1; i1<=1; i1++) {
594             /* ignore central pixel, which we know is bad */
595             if ( i1==0 && j1==0 ) continue;
596             PDL_Indx i2 = m+i1;
597             if ( i2<0 || i2>=$SIZE(m) ) continue;
598             a_val = $a(m=>i2,n=>j2);
599             if ( $ISBADVAR(a_val,a) ) continue;
600             tmp += a_val;
601             norm++;
602             } /* for: i1 */
603             } /* for: j1 */
604             /* Patch */
605             if (norm>0) {
606             $b() = tmp/norm;
607             } else {
608             $SETBAD(b());
609             flag = 1;
610             }
611             } else,) {
612 42         57 $b() = a_val;
613             } /* if: ISGOODVAR() */
614             %}
615 3 0       6 %} /* broadcastloop */
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
616             /* handle bad flag */
617 2 0       330 PDL_IF_BAD(if ( flag ) $PDLSTATESETBAD(b);,)
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
618             EOF
619             );
620              
621             pp_def('max2d_ind',
622             Doc=><<'EOD',
623              
624             =for ref
625              
626             Return value/position of maximum value in 2D image
627              
628             Contributed by Tim Jenness
629              
630             =cut
631              
632             EOD
633              
634             BadDoc=><<'EOD',
635              
636             Bad values are excluded from the search. If all pixels
637             are bad then the output is set bad.
638              
639             EOD
640              
641             HandleBad => 1,
642             Pars => 'a(m,n); [o]val(); int [o]x(); int[o]y();',
643             Code => '
644             PDL_LDouble cur = 0;
645             PDL_Indx curind1 = PDL_IF_BAD(-1,0), curind2 = PDL_IF_BAD(-1,0);
646             loop(m,n) %{
647             if (!(PDL_IF_BAD($ISGOOD(a()) &&,) ( (!n && !m) || $a() > cur || isnan(cur)))) continue;
648             cur = $a(); curind1 = m; curind2 = n;
649             %}
650             PDL_IF_BAD(if ( curind1 < 0 ) {
651             $SETBAD(val());
652             $SETBAD(x());
653             $SETBAD(y());
654             } else,) {
655             $val() = cur;
656             $x() = curind1;
657             $y() = curind2;
658             }
659             ');
660              
661             pp_def('centroid2d',
662             Doc=><<'EOD',
663             =for ref
664              
665             Refine a list of object positions in 2D image by centroiding in a box
666              
667             C<$box> is the full-width of the box, i.e. the window
668             is C<+/- $box/2>.
669              
670             =cut
671             EOD
672             BadDoc=><<'EOD',
673             Bad pixels are excluded from the centroid calculation. If all elements are
674             bad (or the pixel sum is 0 - but why would you be centroiding
675             something with negatives in...) then the output values are set bad.
676             EOD
677             HandleBad => 1,
678             Pars => 'im(m,n); x(); y(); box(); [o]xcen(); [o]ycen();',
679             Code => '
680             PDL_LDouble sum = 0, sumx = 0, sumy = 0;
681             PDL_Indx i1 = $x() - $box()/2;
682             PDL_Indx i2 = $x() + $box()/2 + 1;
683             PDL_Indx j1 = $y() - $box()/2;
684             PDL_Indx j2 = $y() + $box()/2 + 1;
685             loop (n=j1:j2,m=i1:i2) %{
686             PDL_LDouble data = $im();
687             PDL_IF_BAD(if ( $ISBADVAR(data,im) ) continue;,)
688             sum += data;
689             sumx += data*m;
690             sumy += data*n;
691             %}
692             /*
693             * if sum == 0 then we will flag as bad -- although it could just mean that
694             * there is negative values in the dataset.
695             * - should use a better check than != 0.0 ...
696             */
697             PDL_IF_BAD(if ( sum == 0.0 ) {
698             $SETBAD(xcen());
699             $SETBAD(ycen());
700             } else,) {
701             $xcen() = sumx/sum;
702             $ycen() = sumy/sum;
703             }
704             '
705             );
706              
707             pp_add_exported('', 'crop');
708             pp_addpm(<<'EOPM');
709             =head2 crop
710              
711             =for ref
712              
713             Return bounding box of given mask in an C ndarray, so it can broadcast.
714             Use other operations (such as L, or
715             L with a colour vector) to create a mask suitable
716             for your application.
717              
718             =for example
719              
720             $x1x2y1y2 = crop($image);
721              
722             =cut
723              
724             *crop = \&PDL::crop;
725             sub PDL::crop {
726             my ($mask) = @_;
727             $mask->xchg(0,1)->orover->_which_int(my $out = null, null);
728             $out->badflag(1); $out->badvalue(-1);
729             my ($x1, $x2) = $out->minmaximum;
730             $mask->orover->_which_int($out = null, null);
731             $out->badflag(1); $out->badvalue(-1);
732             my ($y1, $y2) = $out->minmaximum;
733             $x1->cat($x2, $y1, $y2)->mv(-1,0);
734             }
735             EOPM
736              
737             pp_add_exported('', 'cc8compt','cc4compt');
738             pp_addpm(<<'EOPM');
739              
740             =head2 cc8compt
741              
742             =for ref
743              
744             Connected 8-component labeling of a binary image.
745              
746             Connected 8-component labeling of 0,1 image - i.e. find separate
747             segmented objects and fill object pixels with object number.
748             8-component labeling includes all neighboring pixels.
749             This is just a front-end to ccNcompt. See also L.
750              
751             =for example
752              
753             $segmented = cc8compt( $image > $threshold );
754              
755             =head2 cc4compt
756              
757             =for ref
758              
759             Connected 4-component labeling of a binary image.
760              
761             Connected 4-component labeling of 0,1 image - i.e. find separate
762             segmented objects and fill object pixels with object number.
763             4-component labling does not include the diagonal neighbors.
764             This is just a front-end to ccNcompt. See also L.
765              
766             =for example
767              
768             $segmented = cc4compt( $image > $threshold );
769              
770             =cut
771              
772             sub PDL::cc8compt{
773             return ccNcompt(shift,8);
774             }
775             *cc8compt = \&PDL::cc8compt;
776              
777             sub PDL::cc4compt{
778             return ccNcompt(shift,4);
779             }
780             *cc4compt = \&PDL::cc4compt;
781              
782             EOPM
783              
784             pp_def('ccNcompt',Doc=>'
785              
786             =for ref
787              
788             Connected component labeling of a binary image.
789              
790             Connected component labeling of 0,1 image - i.e. find separate
791             segmented objects and fill object pixels with object number.
792             See also L and L.
793              
794             The connectivity parameter must be 4 or 8.
795              
796             =for example
797              
798             $segmented = ccNcompt( $image > $threshold, 4);
799              
800             $segmented2 = ccNcompt( $image > $threshold, 8);
801              
802             where the second parameter specifies the connectivity (4 or 8) of the labeling.
803              
804             =cut
805              
806             ',
807             HandleBad => 0, # a marker
808             Pars => 'a(m,n); int+ [o]b(m,n);',
809             OtherPars => 'int con',
810             CHeader => qq{void AddEquiv(PDL_Long* equiv, PDL_Long i, PDL_Long j);\n},
811             Code => '
812              
813             PDL_Long i,k;
814             PDL_Long newlabel;
815             PDL_Long neighbour[4];
816             PDL_Long nfound;
817             PDL_Long pass,count,next,this;
818             PDL_Long *equiv = NULL;
819             PDL_Long i1,j1,i2;
820              
821             if ($COMP(con)!=4 && $COMP(con)!=8)
822             $CROAK("In ccNcompt, connectivity must be 4 or 8, you gave %d",$COMP(con));
823             loop(n,m) %{ /* Copy */
824             $b() = $a();
825             %}
826              
827             /* 1st pass counts max possible compts, 2nd records equivalences */
828              
829             for (pass = 0; pass<2; pass++) {
830              
831             if (pass==1) {
832             equiv = (PDL_Long*) malloc((newlabel+1)*sizeof(PDL_Long));
833             if (equiv==(PDL_Long*)0)
834             $CROAK("Out of memory");
835             for(i=0;i<=newlabel;i++)
836             equiv[i]=i;
837             }
838              
839             newlabel = 1; /* Running label */
840              
841             loop (n,m) %{ /* Loop over image pixels */
842             nfound = 0; /* Number of neighbour >0 */
843              
844             i1 = m-1; j1 = n-1; i2 = m+1; /*West x, North y, East x*/
845              
846             if ($b() > 0) { /* Check 4 neighbour already seen */
847              
848             if (m>0 && $b(m=>i1)>0) /*West*/
849             neighbour[nfound++] = $b(m=>i1); /* Store label of it */
850             if (n>0 && $b(n=>j1)>0) /*North*/
851             neighbour[nfound++] = $b(n=>j1);
852             if (n>0 && m>0 && $b(m=>i1, n=>j1)>0 && $COMP(con) == 8) /*North-West*/
853             neighbour[nfound++] = $b(m=>i1, n=>j1);
854             if (n>0 && m<($SIZE(m)-1) && $b(m=>i2, n=>j1)>0 && $COMP(con) == 8) /*North-East*/
855             neighbour[nfound++] = $b(m=>i2, n=>j1);
856              
857             if (nfound==0) { /* Assign new label */
858             $b() = newlabel++;
859             }
860             else {
861             $b() = neighbour[0];
862             if (nfound>1 && pass == 1) { /* Assign equivalents */
863             for(k=1; k
864             AddEquiv( equiv, (PDL_Long)$b(),
865             neighbour[k] );
866             }
867             }
868             }
869              
870             else { /* No label */
871              
872             $b() = 0;
873             }
874              
875             %} /* End of image loop */
876              
877             } /* Passes */
878              
879             /* Replace each cycle by single label */
880              
881             count = 0;
882             for (i = 1; i <= newlabel; i++)
883             if ( i <= equiv[i] ) {
884             count++;
885             this = i;
886             while ( equiv[this] != i ) {
887             next = equiv[this];
888             equiv[this] = count;
889             this = next;
890             }
891             equiv[this] = count;
892             }
893              
894              
895             /* Now remove equivalences */
896              
897             loop (n,m) %{ /* Loop over image pixels */
898             $b() = equiv[ (PDL_Long) $b() ] ;
899             %}
900              
901             free(equiv); /* Tidy */
902             ');
903              
904             pp_add_exported('polyfill');
905             pp_addpm(<<'EOPM');
906             =head2 polyfill
907              
908             =for ref
909              
910             fill the area of the given polygon with the given colour.
911              
912             This function works inplace, i.e. modifies C.
913              
914             =for usage
915              
916             polyfill($im,$ps,$colour,[\%options]);
917              
918             The default method of determining which points lie inside of the polygon used
919             is not as strict as the method used in L. Often, it includes vertices
920             and edge points. Set the C option to change this behaviour.
921              
922             =for option
923              
924             Method - Set the method used to determine which points lie in the polygon.
925             => Default - internal PDL algorithm
926             => pnpoly - use the L algorithm
927              
928             =for example
929              
930             # Make a convex 3x3 square of 1s in an image using the pnpoly algorithm
931             $ps = pdl([3,3],[3,6],[6,6],[6,3]);
932             polyfill($im,$ps,1,{'Method' =>'pnpoly'});
933              
934             =cut
935             sub PDL::polyfill {
936             my $opt;
937             $opt = pop @_ if ref($_[-1]) eq 'HASH';
938             barf('Usage: polyfill($im,$ps,$colour,[\%options])') unless @_==3;
939             my ($im,$ps,$colour) = @_;
940              
941             if($opt) {
942             use PDL::Options qw();
943             my $parsed = PDL::Options->new({'Method' => undef});
944             $parsed->options($opt);
945             if( $parsed->current->{'Method'} eq 'pnpoly' ) {
946             PDL::pnpolyfill_pp($im,$ps,$colour);
947             }
948             }
949             else
950             {
951             PDL::polyfill_pp($im,$ps,$colour);
952             }
953             return $im;
954              
955             }
956              
957             *polyfill = \&PDL::polyfill;
958              
959             EOPM
960              
961             pp_add_exported('', 'pnpoly');
962             pp_addpm(<<'EOPM');
963              
964             =head2 pnpoly
965              
966             =for ref
967              
968             'points in a polygon' selection from a 2-D ndarray
969              
970             =for usage
971              
972             $mask = $img->pnpoly($ps);
973              
974             # Old style, do not use
975             $mask = pnpoly($x, $y, $px, $py);
976              
977             For a closed polygon determined by the sequence of points in {$px,$py}
978             the output of pnpoly is a mask corresponding to whether or not each
979             coordinate (x,y) in the set of test points, {$x,$y}, is in the interior
980             of the polygon. This is the 'points in a polygon' algorithm from
981             L
982             and vectorized for PDL by Karl Glazebrook.
983              
984             =for example
985              
986             # define a 3-sided polygon (a triangle)
987             $ps = pdl([3, 3], [20, 20], [34, 3]);
988              
989             # $tri is 0 everywhere except for points in polygon interior
990             $tri = $img->pnpoly($ps);
991              
992             With the second form, the x and y coordinates must also be specified.
993             B< I >.
994              
995             $px = pdl( 3, 20, 34 );
996             $py = pdl( 3, 20, 3 );
997             $x = $img->xvals; # get x pixel coords
998             $y = $img->yvals; # get y pixel coords
999              
1000             # $tri is 0 everywhere except for points in polygon interior
1001             $tri = pnpoly($x,$y,$px,$py);
1002              
1003             =cut
1004              
1005             # From: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
1006             #
1007             # Fixes needed to pnpoly code:
1008             #
1009             # Use topdl() to ensure ndarray args
1010             #
1011             # Add POD docs for usage
1012             #
1013             # Calculate first term in & expression to use to fix divide-by-zero when
1014             # the test point is in line with a vertical edge of the polygon.
1015             # By adding the value of $mask we prevent a divide-by-zero since the &
1016             # operator does not "short circuit".
1017              
1018             sub PDL::pnpoly {
1019             barf('Usage: $mask = pnpoly($img, $ps);') unless(@_==2 || @_==4);
1020             my ($tx, $ty, $vertx, $verty) = @_;
1021              
1022             # if only two inputs, use the pure PP version
1023             unless( defined $vertx ) {
1024             barf("ps must contain pairwise points.\n") unless $ty->getdim(0) == 2;
1025              
1026             # Input mapping: $img => $tx, $ps => $ty
1027             return PDL::pnpoly_pp($tx,$ty);
1028             }
1029              
1030             my $testx = PDL::Core::topdl($tx)->dummy(0);
1031             my $testy = PDL::Core::topdl($ty)->dummy(0);
1032             my $vertxj = PDL::Core::topdl($vertx)->rotate(1);
1033             my $vertyj = PDL::Core::topdl($verty)->rotate(1);
1034             my $mask = ( ($verty>$testy) == ($vertyj>$testy) );
1035             my $c = sumover( ! $mask & ( $testx < ($vertxj-$vertx) * ($testy-$verty)
1036             / ($vertyj-$verty+$mask) + $vertx) ) % 2;
1037             return $c;
1038             }
1039              
1040             *pnpoly = \&PDL::pnpoly;
1041              
1042             EOPM
1043              
1044             pp_add_exported('', 'polyfillv');
1045             pp_addpm(<<'EOPM');
1046              
1047             =head2 polyfillv
1048              
1049             =for ref
1050              
1051             return the (dataflowed) area of an image described by a polygon
1052              
1053             =for usage
1054              
1055             polyfillv($im,$ps,[\%options]);
1056              
1057             The default method of determining which points lie inside of the polygon used
1058             is not as strict as the method used in L. Often, it includes vertices
1059             and edge points. Set the C option to change this behaviour.
1060              
1061             =for option
1062              
1063             Method - Set the method used to determine which points lie in the polygon.
1064             => Default - internal PDL algorithm
1065             => pnpoly - use the L algorithm
1066              
1067             =for example
1068              
1069             # increment intensity in area bounded by $poly using the pnpoly algorithm
1070             $im->polyfillv($poly,{'Method'=>'pnpoly'})++; # legal in perl >= 5.6
1071              
1072             # compute average intensity within area bounded by $poly using the default algorithm
1073             $av = $im->polyfillv($poly)->avg;
1074              
1075             =cut
1076              
1077             sub PDL::polyfillv :lvalue {
1078             my $opt;
1079             $opt = pop @_ if ref($_[-1]) eq 'HASH';
1080             barf('Usage: polyfillv($im,$ps,[\%options])') unless @_==2;
1081              
1082             my ($im,$ps) = @_;
1083             barf("ps must contain pairwise points.\n") unless $ps->getdim(0) == 2;
1084              
1085             if($opt) {
1086             use PDL::Options qw();
1087             my $parsed = PDL::Options->new({'Method' => undef});
1088             $parsed->options($opt);
1089             return $im->where(PDL::pnpoly_pp($im, $ps)) if $parsed->current->{'Method'} eq 'pnpoly';
1090             }
1091              
1092             PDL::polyfill_pp(my $msk = zeroes(long,$im->dims), $ps, 1);
1093             return $im->where($msk);
1094             }
1095             *polyfillv = \&PDL::polyfillv;
1096              
1097             EOPM
1098              
1099             pp_addhdr(<<'EOF');
1100             int getnewsize(PDL_Indx cols, PDL_Indx rows, float fangle, PDL_Indx *newcols, PDL_Indx *newrows);
1101             typedef unsigned char imT; /* image type */
1102             int rotate(imT *im, imT *out, int cols, int rows, int nc, int nr,
1103             float fangle, imT bgval, int antialias);
1104             EOF
1105             pp_add_exported('','rotnewsz');
1106             pp_addxs('
1107              
1108             void
1109             rotnewsz(m,n,angle)
1110             int m
1111             int n
1112             float angle
1113             PPCODE:
1114             PDL_Indx newcols, newrows;
1115              
1116             if (getnewsize(m,n,angle,&newcols,&newrows) != 0)
1117             croak("wrong angle (should be between -90 and +90)");
1118             EXTEND(sp,2);
1119             PUSHs(sv_2mortal(newSVnv(newcols)));
1120             PUSHs(sv_2mortal(newSVnv(newrows)));
1121             ');
1122              
1123             pp_def('rot2d',
1124             HandleBad => 0,
1125             Pars => 'im(m,n); float angle(); bg(); int aa(); [o] om(p,q)',
1126             GenericTypes => $A,
1127             Code => 'int ierr;
1128             if ((ierr = rotate($P(im),$P(om),$SIZE(m),$SIZE(n),$SIZE(p),
1129             $SIZE(q),$angle(),$bg(),$aa())) != 0) {
1130             if (ierr == -1)
1131             $CROAK("error during rotate, wrong angle");
1132             else
1133             $CROAK("wrong output dims, did you set them?");
1134             }',
1135             RedoDimsCode => '
1136             if (getnewsize($SIZE(m),$SIZE(n),
1137             $angle(), &$SIZE(p),
1138             &$SIZE(q)) != 0)
1139             $CROAK("error during rotate, wrong angle");
1140             /* printf("o: %d, p: %d\n",ncols,nrows); */
1141             ',
1142             GenericTypes => ['B'],
1143             Doc => << 'EOD',
1144              
1145             =for ref
1146              
1147             rotate an image by given C
1148              
1149             =for example
1150              
1151             # rotate by 10.5 degrees with antialiasing, set missing values to 7
1152             $rot = $im->rot2d(10.5,7,1);
1153              
1154             This function rotates an image through an C between -90 and + 90
1155             degrees. Uses/doesn't use antialiasing depending on the C flag.
1156             Pixels outside the rotated image are set to C.
1157              
1158             Code modified from pnmrotate (Copyright Jef Poskanzer) with an algorithm based
1159             on "A Fast Algorithm for General Raster Rotation" by Alan Paeth,
1160             Graphics Interface '86, pp. 77-81.
1161              
1162             Use the C function to find out about the dimension of the
1163             newly created image
1164              
1165             ($newcols,$newrows) = rotnewsz $oldn, $oldm, $angle;
1166              
1167             L offers a more general interface to
1168             distortions, including rotation, with various types of sampling; but
1169             rot2d is faster.
1170              
1171             =cut
1172              
1173             EOD
1174             );
1175              
1176             pp_def('bilin2d',
1177             HandleBad => 0,
1178             Pars => 'Int(n,m); [io] O(q,p)',
1179             Doc=><<'EOD',
1180             =for ref
1181              
1182             Bilinearly maps the first ndarray in the second. The
1183             interpolated values are actually added to the second
1184             ndarray which is supposed to be larger than the first one.
1185             EOD
1186             GenericTypes => $A,
1187             RedoDimsCode =>'
1188             if ($SIZE(q)<$SIZE(n) || $SIZE(p)<$SIZE(m))
1189             $CROAK("the second matrix must be greater than first!");
1190             ',
1191             Code =>'
1192             PDL_CLDouble dx = ((PDL_CLDouble) ($SIZE(n)-1)) / ($SIZE(q)-1);
1193             PDL_CLDouble dy = ((PDL_CLDouble) ($SIZE(m)-1)) / ($SIZE(p)-1);
1194             broadcastloop %{
1195             PDL_CLDouble x=0;
1196             loop (q) %{
1197             PDL_CLDouble y = 0;
1198             loop (p) %{
1199             PDL_Indx ii = floor(x);
1200             if (ii>=($SIZE(n)-1)) ii = $SIZE(n)-2;
1201             PDL_Indx jj = floor(y);
1202             if (jj>=($SIZE(m)-1)) jj = $SIZE(m)-2;
1203             PDL_Indx ii1 = ii+1;
1204             PDL_Indx jj1 = jj+1;
1205             PDL_CLDouble y1 = $Int(n=>ii,m=>jj);
1206             PDL_CLDouble y2 = $Int(n=>ii1,m=>jj);
1207             PDL_CLDouble y3 = $Int(n=>ii1,m=>jj1);
1208             PDL_CLDouble y4 = $Int(n=>ii,m=>jj1);
1209             PDL_CLDouble t = x-ii;
1210             PDL_CLDouble u = y-jj;
1211             $O() += (1-t)*(1-u)*y1 + t*(1-u)*y2 + t*u*y3 + (1-t)*u*y4;
1212             y+=dy;
1213             %}
1214             x+=dx;
1215             %}
1216             %}
1217             ');
1218              
1219             pp_def('rescale2d',
1220             HandleBad => 0,
1221             Pars => 'Int(m,n); [io] O(p,q)',
1222             Doc=><<'EOD',
1223             =for ref
1224              
1225             The first ndarray is rescaled to the dimensions of the second
1226             (expanding or meaning values as needed) and then added to it in place.
1227             Nothing useful is returned.
1228              
1229             If you want photometric accuracy or automatic FITS header metadata
1230             tracking, consider using L
1231             instead: it does these things, at some speed penalty compared to
1232             rescale2d.
1233             EOD
1234             GenericTypes => $A,
1235             Code =>'
1236             if($SIZE(p) >= $SIZE(m) && $SIZE(q) >= $SIZE(n)) {
1237             PDL_LDouble kx = ((PDL_LDouble) $SIZE(p)) / $SIZE(m);
1238             PDL_LDouble ky = ((PDL_LDouble) $SIZE(q)) / $SIZE(n);
1239             broadcastloop %{
1240             PDL_Indx lx = 0;
1241             loop (m) %{
1242             PDL_Indx ly = 0, cx = rint((m+1)*kx);
1243             loop (n) %{
1244             PDL_Indx cy = rint((n+1)*ky);
1245             loop (p=lx:cx,q=ly:cy) %{
1246             $O() += $Int();
1247             %}
1248             ly = cy + 1;
1249             %}
1250             lx = cx + 1;
1251             %}
1252             %}
1253             }
1254             else if($SIZE(p) < $SIZE(m) && $SIZE(q) < $SIZE(n)) {
1255             PDL_LDouble kx = ((PDL_LDouble) $SIZE(m)) / $SIZE(p);
1256             PDL_LDouble ky = ((PDL_LDouble) $SIZE(n)) / $SIZE(q);
1257             broadcastloop %{
1258             PDL_Indx lx = 0;
1259             loop (p) %{
1260             PDL_Indx ly = 0, cx = rint((p+1)*kx);
1261             loop (q) %{
1262             PDL_Indx cy = rint((q+1)*ky);
1263             PDL_LDouble temp = 0.0;
1264             PDL_Indx num = 0;
1265             loop (m=lx:cx,n=ly:cy) %{
1266             temp += $Int();
1267             num++;
1268             %}
1269             $O() += temp/num;
1270             ly = cy + 1;
1271             %}
1272             lx = cx + 1;
1273             %}
1274             %}
1275             }
1276             else if($SIZE(p) >= $SIZE(m) && $SIZE(q) < $SIZE(n)) {
1277             PDL_LDouble kx = ((PDL_LDouble) $SIZE(p)) / $SIZE(m);
1278             PDL_LDouble ky = ((PDL_LDouble) $SIZE(n)) / $SIZE(q);
1279             broadcastloop %{
1280             PDL_Indx lx = 0;
1281             loop (m) %{
1282             PDL_Indx ly = 0, cx = rint((m+1)*kx);
1283             loop (q) %{
1284             PDL_Indx cy = rint((q+1)*ky);
1285             PDL_LDouble temp = 0.0;
1286             PDL_Indx num = 0;
1287             loop (n=ly:cy) %{
1288             temp += $Int();
1289             num++;
1290             %}
1291             loop (p=lx:cx) %{
1292             $O() += temp/num;
1293             %}
1294             ly = cy + 1;
1295             %}
1296             lx = cx + 1;
1297             %}
1298             %}
1299             }
1300             else if($SIZE(p) < $SIZE(m) && $SIZE(q) >= $SIZE(n)) {
1301             PDL_LDouble kx = ((PDL_LDouble) $SIZE(m)) / $SIZE(p);
1302             PDL_LDouble ky = ((PDL_LDouble) $SIZE(q)) / $SIZE(n);
1303             broadcastloop %{
1304             PDL_Indx lx = 0;
1305             loop (p) %{
1306             PDL_Indx ly = 0, cx = rint((p+1)*kx);
1307             loop (n) %{
1308             PDL_Indx cy = rint((n+1)*ky);
1309             PDL_CLDouble temp = 0.0;
1310             PDL_Indx num = 0;
1311             loop (m=lx:cx) %{
1312             temp += $Int();
1313             num++;
1314             %}
1315             loop (q=ly:cy) %{
1316             $O() += temp/num;
1317             %}
1318             ly = cy + 1;
1319             %}
1320             lx = cx + 1;
1321             %}
1322             %}
1323             }
1324             else $CROAK("I am not supposed to be here, please report the bug to ");
1325             ');
1326              
1327             # functions to make handling 2D polynomial mappings a bit easier
1328             #
1329             pp_add_exported('', 'fitwarp2d applywarp2d');
1330              
1331             pp_addpm( '
1332              
1333             =head2 fitwarp2d
1334              
1335             =for ref
1336              
1337             Find the best-fit 2D polynomial to describe
1338             a coordinate transformation.
1339              
1340             =for usage
1341              
1342             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, $nf, { options } )
1343              
1344             Given a set of points in the output plane (C<$u,$v>), find
1345             the best-fit (using singular-value decomposition) 2D polynomial
1346             to describe the mapping back to the image plane (C<$x,$y>).
1347             The order of the fit is controlled by the C<$nf> parameter
1348             (the maximum power of the polynomial is C<$nf - 1>), and you
1349             can restrict the terms to fit using the C option.
1350              
1351             C<$px> and C<$py> are C by C element ndarrays which describe
1352             a polynomial mapping (of order C)
1353             from the I C<(u,v)> image to the I C<(x,y)> image:
1354              
1355             x = sum(j=0,np-1) sum(i=0,np-1) px(i,j) * u^i * v^j
1356             y = sum(j=0,np-1) sum(i=0,np-1) py(i,j) * u^i * v^j
1357              
1358             The transformation is returned for the reverse direction (ie
1359             output to input image) since that is what is required by the
1360             L routine. The L
1361             routine can be used to convert a set of C<$u,$v> points given
1362             C<$px> and C<$py>.
1363              
1364             Options:
1365              
1366             =for options
1367              
1368             FIT - which terms to fit? default ones(byte,$nf,$nf)
1369              
1370             =begin comment
1371              
1372             old option, caused trouble
1373             THRESH - in svd, remove terms smaller than THRESH * max value
1374             default is 1.0e-5
1375              
1376             =end comment
1377              
1378             =over 4
1379              
1380             =item FIT
1381              
1382             C allows you to restrict which terms of the polynomial to fit:
1383             only those terms for which the FIT ndarray evaluates to true will be
1384             evaluated. If a 2D ndarray is sent in, then it is
1385             used for the x and y polynomials; otherwise
1386             C<< $fit->slice(":,:,(0)") >> will be used for C<$px> and
1387             C<< $fit->slice(":,:,(1)") >> will be used for C<$py>.
1388              
1389             =begin comment
1390              
1391             =item THRESH
1392              
1393             Remove all singular values whose value is less than C
1394             times the largest singular value.
1395              
1396             =end comment
1397              
1398             =back
1399              
1400             The number of points must be at least equal to the number of
1401             terms to fit (C<$nf*$nf> points for the default value of C).
1402              
1403             =for example
1404              
1405             # points in original image
1406             $x = pdl( 0, 0, 100, 100 );
1407             $y = pdl( 0, 100, 100, 0 );
1408             # get warped to these positions
1409             $u = pdl( 10, 10, 90, 90 );
1410             $v = pdl( 10, 90, 90, 10 );
1411             #
1412             # shift of origin + scale x/y axis only
1413             $fit = byte( [ [1,1], [0,0] ], [ [1,0], [1,0] ] );
1414             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, 2, { FIT => $fit } );
1415             print "px = ${px}py = $py";
1416             px =
1417             [
1418             [-12.5 1.25]
1419             [ 0 0]
1420             ]
1421             py =
1422             [
1423             [-12.5 0]
1424             [ 1.25 0]
1425             ]
1426             #
1427             # Compared to allowing all 4 terms
1428             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, 2 );
1429             print "px = ${px}py = $py";
1430             px =
1431             [
1432             [ -12.5 1.25]
1433             [ 1.110223e-16 -1.1275703e-17]
1434             ]
1435             py =
1436             [
1437             [ -12.5 1.6653345e-16]
1438             [ 1.25 -5.8546917e-18]
1439             ]
1440              
1441             # A higher-degree polynomial should not affect the answer much, but
1442             # will require more control points
1443              
1444             $x = $x->glue(0,pdl(50,12.5, 37.5, 12.5, 37.5));
1445             $y = $y->glue(0,pdl(50,12.5, 37.5, 37.5, 12.5));
1446             $u = $u->glue(0,pdl(73,20,40,20,40));
1447             $v = $v->glue(0,pdl(29,20,40,40,20));
1448             ( $px3, $py3 ) = fitwarp2d( $x, $y, $u, $v, 3 );
1449             print "px3 =${px3}py3 =$py3";
1450             px3 =
1451             [
1452             [-6.4981162e+08 71034917 -726498.95]
1453             [ 49902244 -5415096.7 55945.388]
1454             [ -807778.46 88457.191 -902.51612]
1455             ]
1456             py3 =
1457             [
1458             [-6.2732159e+08 68576392 -701354.77]
1459             [ 48175125 -5227679.8 54009.114]
1460             [ -779821.18 85395.681 -871.27997]
1461             ]
1462              
1463             #This illustrates an important point about singular value
1464             #decompositions that are used in fitwarp2d: like all SVDs, the
1465             #rotation matrices are not unique, and so the $px and $py returned
1466             #by fitwarp2d are not guaranteed to be the "simplest" solution.
1467             #They do still work, though:
1468              
1469             ($x3,$y3) = applywarp2d($px3,$py3,$u,$v);
1470             print approx $x3,$x,1e-4;
1471             [1 1 1 1 1 1 1 1 1]
1472             print approx $y3,$y;
1473             [1 1 1 1 1 1 1 1 1]
1474              
1475             =head2 applywarp2d
1476              
1477             =for ref
1478              
1479             Transform a set of points using a 2-D polynomial mapping
1480              
1481             =for usage
1482              
1483             ( $x, $y ) = applywarp2d( $px, $py, $u, $v )
1484              
1485             Convert a set of points (stored in 1D ndarrays C<$u,$v>)
1486             to C<$x,$y> using the 2-D polynomial with coefficients stored in C<$px>
1487             and C<$py>. See L
1488             for more information on the format of C<$px> and C<$py>.
1489              
1490             =cut
1491              
1492             # use SVD to fit data. Assuming no errors.
1493              
1494             =pod
1495              
1496             =begin comment
1497              
1498             Some explanation of the following three subroutines (_svd, _mkbasis,
1499             and fitwarp2d): See Wolberg 1990 (full ref elsewhere in this
1500             documentation), Chapter 3.6 "Polynomial Transformations". The idea is
1501             that, given a set of control points in the input and output images
1502             denoted by coordinates (x,y) and (u,v), we want to create a polynomial
1503             transformation that relates u to linear combinations of powers of x
1504             and y, and another that relates v to powers of x and y.
1505              
1506             The transformations used here and by Wolberg differ slightly, but the
1507             basic idea is something like this. For each u and each v, define a
1508             transform:
1509              
1510             u = (sum over j) (sum over i) a_{ij} x**i * y**j , (eqn 1)
1511             v = (sum over j) (sum over i) b_{ij} x**i * y**j . (eqn 2)
1512              
1513             We can write this in matrix notation. Given that there are M control
1514             points, U is a column vector with M rows. A and B are vectors containing
1515             the N coefficients (related to the degree of the polynomial fit). W
1516             is an MxN matrix of the basis row-vectors (the x**i y**j).
1517              
1518             The matrix equations we are trying to solve are
1519             U = W A , (eqn 3)
1520             V = W B . (eqn 4)
1521              
1522             We need to find the A and B column vectors, those are the coefficients
1523             of the polynomial terms in W. W is not square, so it has no inverse.
1524             But is has a pseudo-inverse W^+ that is NxM. We are going to use that
1525             pseudo-inverse to isolate A and B, like so:
1526              
1527             W^+ U = W^+ W A = A (eqn 5)
1528             W^+ V = W^+ W B = B (eqn 6)
1529              
1530             We are going to get W^+ by performing a singular value decomposition
1531             of W (to use some of the variable names below):
1532              
1533             W = $svd_u x SIGMA x $svd_v->transpose (eqn 7)
1534             W^+ = $svd_v x SIGMA^+ x $svd_u->transpose . (eqn 8)
1535              
1536             Here SIGMA is a square diagonal matrix that contains the singular
1537             values of W that are in the variable $svd_w. SIGMA^+ is the
1538             pseudo-inverse of SIGMA, which is calculated by replacing the non-zero
1539             singular values with their reciprocals, and then taking the transpose
1540             of the matrix (which is a no-op since the matrix is square and
1541             diagonal).
1542              
1543             So the code below does this:
1544              
1545             _mkbasis computes the matrix W, given control coordinates u and v and
1546             the maximum degree of the polynomial (and the terms to use).
1547              
1548             _svd takes the svd of W, computes the pseudo-inverse of W, and then
1549             multiplies that with the U vector (there called $y). The output of
1550             _svd is the A or B vector in eqns 5 & 6 above. Rarely is the matrix
1551             multiplication explicit, unfortunately, so I have added EXPLANATIONs
1552             below.
1553              
1554             =end comment
1555              
1556             =cut
1557              
1558             sub _svd ($$) {
1559             my $basis = shift;
1560             my $y = shift;
1561             # my $thresh = shift;
1562              
1563             # if we had errors for these points, would normalise the
1564             # basis functions, and the output array, by these errors here
1565              
1566             # perform the SVD
1567             my ( $svd_u, $svd_w, $svd_v ) = svd( $basis );
1568              
1569             # DAL, 09/2017: the reason for removing ANY singular values, much less
1570             #the smallest ones, is not clear. I commented the line below since this
1571             #actually removes the LARGEST values in SIGMA^+.
1572             # $svd_w *= ( $svd_w >= ($svd_w->max * $thresh ) );
1573             # The line below would instead remove the SMALLEST values in SIGMA^+, but I can see no reason to include it either.
1574             # $svd_w *= ( $svd_w <= ($svd_w->min / $thresh ) );
1575              
1576             # perform the back substitution
1577             # EXPLANATION: same thing as $svd_u->transpose x $y->transpose.
1578             my $tmp = $y x $svd_u;
1579              
1580             #EXPLANATION: the division by (the non-zero elements of) $svd_w (the singular values) is
1581             #equivalent to $sigma_plus x $tmp, so $tmp is now SIGMA^+ x $svd_u x $y
1582             $tmp /= $svd_w->setvaltobad(0.0);
1583             $tmp->inplace->setbadtoval(0.0);
1584              
1585             #EXPLANATION: $svd_v x SIGMA^+ x $svd_u x $y
1586             return sumover( $svd_v * $tmp );
1587              
1588             } # sub: _svd()
1589              
1590             #_mkbasis returns an ndarray in which the k(=j*n+i)_th column is v**j * u**i
1591             #k=0 j=0 i=0
1592             #k=1 j=0 i=1
1593             #k=2 j=0 i=2
1594             #k=3 j=1 i=0
1595             # ...
1596              
1597             #each row corresponds to a control point
1598             #and the rows for each of the control points look like this, e.g.:
1599             # (1) (u) (u**2) (v) (vu) (v(u**2)) (v**2) ((v**2)u) ((v**2)(u**2))
1600             # and so on for the next control point.
1601              
1602             sub _mkbasis ($$$$) {
1603             my $fit = shift; # dims n n
1604             my $npts = shift; # scalar
1605             my $u = shift; # dims npts
1606             my $v = shift; # dims npts
1607             my $ncoeff = sum( $fit );
1608             my $fit_coords = $fit->whichND; # dims uv ncoeff
1609             cat($u,$v) # npts uv
1610             ->transpose # uv npts
1611             ->dummy(1,$ncoeff) # uv ncoeff npts
1612             ->ipow($fit_coords) # same
1613             ->prodover # ncoeff npts
1614             ;
1615             } # sub: _mkbasis()
1616              
1617             sub PDL::fitwarp2d {
1618             croak "Usage: (\$px,\$py) = fitwarp2d(x(m);y(m);u(m);v(m);\$nf; { options })"
1619             if $#_ < 4 or ( $#_ >= 5 and ref($_[5]) ne "HASH" );
1620              
1621             my $x = shift;
1622             my $y = shift;
1623             my $u = shift;
1624             my $v = shift;
1625             my $nf = shift;
1626              
1627             my $opts = PDL::Options->new( { FIT => ones(byte,$nf,$nf) } ); #, THRESH => 1.0e-5 } );
1628             $opts->options( $_[0] ) if $#_ > -1;
1629             my $oref = $opts->current();
1630              
1631             # safety checks
1632             my $npts = $x->nelem;
1633             croak "fitwarp2d: x, y, u, and v must be the same size (and 1D)"
1634             unless $npts == $y->nelem and $npts == $u->nelem and $npts == $v->nelem
1635             and $x->getndims == 1 and $y->getndims == 1 and $u->getndims == 1 and $v->getndims == 1;
1636              
1637             # my $svd_thresh = $$oref{THRESH};
1638             # croak "fitwarp2d: THRESH option must be >= 0."
1639             # if $svd_thresh < 0;
1640              
1641             my $fit = $$oref{FIT};
1642             my $fit_ndim = $fit->getndims();
1643             croak "fitwarp2d: FIT option must be sent a (\$nf,\$nf[,2]) element ndarray"
1644             unless UNIVERSAL::isa($fit,"PDL") and
1645             ($fit_ndim == 2 or ($fit_ndim == 3 and $fit->getdim(2) == 2)) and
1646             $fit->getdim(0) == $nf and $fit->getdim(1) == $nf;
1647              
1648             # how many coeffs to fit (first we ensure $fit is either 0 or 1)
1649             $fit = convert( $fit != 0, byte );
1650              
1651             my ( $fitx, $fity, $ncoeffx, $ncoeffy, $ncoeff );
1652             if ( $fit_ndim == 2 ) {
1653             $fitx = $fit;
1654             $fity = $fit;
1655             $ncoeff = $ncoeffx = $ncoeffy = sum( $fit );
1656             } else {
1657             $fitx = $fit->slice(",,(0)");
1658             $fity = $fit->slice(",,(1)");
1659             $ncoeffx = sum($fitx);
1660             $ncoeffy = sum($fity);
1661             $ncoeff = $ncoeffx > $ncoeffy ? $ncoeffx : $ncoeffy;
1662             }
1663              
1664             croak "fitwarp2d: number of points ($npts) must be >= \$ncoeff ($ncoeff)"
1665             unless $npts >= $ncoeff;
1666              
1667             # create the basis functions for the SVD fitting
1668             my ( $basisx, $basisy );
1669              
1670             $basisx = _mkbasis( $fitx, $npts, $u, $v );
1671              
1672             if ( $fit_ndim == 2 ) {
1673             $basisy = $basisx;
1674             } else {
1675             $basisy = _mkbasis( $fity, $npts, $u, $v );
1676             }
1677              
1678             my $px = _svd( $basisx, $x ); # $svd_thresh);
1679             my $py = _svd( $basisy, $y ); # $svd_thresh);
1680              
1681             # convert into $nf x $nf element ndarrays, if necessary
1682             my $nf2 = $nf * $nf;
1683              
1684             return ( $px->reshape($nf,$nf), $py->reshape($nf,$nf) )
1685             if $ncoeff == $nf2 and $ncoeffx == $ncoeffy;
1686              
1687             # re-create the matrix
1688             my $xtmp = zeroes( $nf, $nf );
1689             my $ytmp = zeroes( $nf, $nf );
1690              
1691             my $kx = 0;
1692             my $ky = 0;
1693             foreach my $i ( 0 .. ($nf - 1) ) {
1694             foreach my $j ( 0 .. ($nf - 1) ) {
1695             if ( $fitx->at($i,$j) ) {
1696             $xtmp->set($i,$j, $px->at($kx) );
1697             $kx++;
1698             }
1699             if ( $fity->at($i,$j) ) {
1700             $ytmp->set($i,$j, $py->at($ky) );
1701             $ky++;
1702             }
1703             }
1704             }
1705              
1706             return ( $xtmp, $ytmp )
1707              
1708             } # sub: fitwarp2d
1709              
1710             *fitwarp2d = \&PDL::fitwarp2d;
1711              
1712             sub PDL::applywarp2d {
1713             # checks
1714             croak "Usage: (\$x,\$y) = applywarp2d(px(nf,nf);py(nf,nf);u(m);v(m);)"
1715             if $#_ != 3;
1716              
1717             my $px = shift;
1718             my $py = shift;
1719             my $u = shift;
1720             my $v = shift;
1721             my $npts = $u->nelem;
1722              
1723             # safety check
1724             croak "applywarp2d: u and v must be the same size (and 1D)"
1725             unless $npts == $u->nelem and $npts == $v->nelem
1726             and $u->getndims == 1 and $v->getndims == 1;
1727              
1728             my $nf = $px->getdim(0);
1729             my $nf2 = $nf * $nf;
1730              
1731             # could remove terms with 0 coeff here
1732             # (would also have to remove them from px/py for
1733             # the matrix multiplication below)
1734             #
1735             my $mat = _mkbasis( ones(byte,$nf,$nf), $npts, $u, $v );
1736              
1737             my $x = reshape( $mat x $px->flat->transpose(), $npts );
1738             my $y = reshape( $mat x $py->flat->transpose(), $npts );
1739             return ( $x, $y );
1740              
1741             } # sub: applywarp2d
1742              
1743             *applywarp2d = \&PDL::applywarp2d;
1744              
1745             ' );
1746              
1747             ## resampling routines taken from v3.6-0 of the Eclipse package
1748             ## http://www.eso.org/eclipse by Nicolas Devillard
1749             ##
1750              
1751             pp_addhdr( '#include "resample.h"' . "\n" );
1752              
1753             pp_def(
1754             'warp2d',
1755             Doc=> <<'EOD',
1756             =for ref
1757              
1758             Warp a 2D image given a polynomial describing the I mapping.
1759              
1760             =for usage
1761              
1762             $out = warp2d( $img, $px, $py, { options } );
1763              
1764             Apply the polynomial transformation encoded in the C<$px> and
1765             C<$py> ndarrays to warp the input image C<$img> into the output
1766             image C<$out>.
1767              
1768             The format for the polynomial transformation is described in
1769             the documentation for the L routine.
1770              
1771             At each point C, the closest 16 pixel values are combined
1772             with an interpolation kernel to calculate the value at C.
1773             The interpolation is therefore done in the image, rather than
1774             Fourier, domain.
1775             By default, a C kernel is used, but this can be changed
1776             using the C option discussed below
1777             (the choice of kernel depends on the frequency content of the input image).
1778              
1779             The routine is based on the C command from
1780             the Eclipse data-reduction package - see http://www.eso.org/eclipse/ - and
1781             for further details on image resampling see
1782             Wolberg, G., "Digital Image Warping", 1990, IEEE Computer
1783             Society Press ISBN 0-8186-8944-7).
1784              
1785             Currently the output image is the same size as the input one,
1786             which means data will be lost if the transformation reduces
1787             the pixel scale. This will (hopefully) be changed soon.
1788              
1789             =for example
1790              
1791             $img = rvals(byte,501,501);
1792             imag $img, { JUSTIFY => 1 };
1793             #
1794             # use a not-particularly-obvious transformation:
1795             # x = -10 + 0.5 * $u - 0.1 * $v
1796             # y = -20 + $v - 0.002 * $u * $v
1797             #
1798             $px = pdl( [ -10, 0.5 ], [ -0.1, 0 ] );
1799             $py = pdl( [ -20, 0 ], [ 1, 0.002 ] );
1800             $wrp = warp2d( $img, $px, $py );
1801             #
1802             # see the warped image
1803             imag $warp, { JUSTIFY => 1 };
1804              
1805             The options are:
1806              
1807             =for options
1808              
1809             KERNEL - default value is tanh
1810             NOVAL - default value is 0
1811              
1812             C is used to specify which interpolation kernel to use
1813             (to see what these kernels look like, use the
1814             L routine).
1815             The options are:
1816              
1817             =over 4
1818              
1819             =item tanh
1820              
1821             Hyperbolic tangent: the approximation of an ideal box filter by the
1822             product of symmetric tanh functions.
1823              
1824             =item sinc
1825              
1826             For a correctly sampled signal, the ideal filter in the fourier domain is a rectangle,
1827             which produces a C interpolation kernel in the spatial domain:
1828              
1829             sinc(x) = sin(pi * x) / (pi * x)
1830              
1831             However, it is not ideal for the C<4x4> pixel region used here.
1832              
1833             =item sinc2
1834              
1835             This is the square of the sinc function.
1836              
1837             =item lanczos
1838              
1839             Although defined differently to the C kernel, the result is very
1840             similar in the spatial domain. The Lanczos function is defined as
1841              
1842             L(x) = sinc(x) * sinc(x/2) if abs(x) < 2
1843             = 0 otherwise
1844              
1845             =item hann
1846              
1847             This kernel is derived from the following function:
1848              
1849             H(x) = a + (1-a) * cos(2*pi*x/(N-1)) if abs(x) < 0.5*(N-1)
1850             = 0 otherwise
1851              
1852             with C and N currently equal to 2001.
1853              
1854             =item hamming
1855              
1856             This kernel uses the same C as the Hann filter, but with
1857             C.
1858              
1859             =back
1860              
1861             C gives the value used to indicate that a pixel in the
1862             output image does not map onto one in the input image.
1863             EOD
1864             HandleBad => 0,
1865             Pars => 'img(m,n); ldouble px(np,np); ldouble py(np,np); [o] warp(m,n); ldouble [t] poly(np); ldouble [t] kernel(ns)',
1866             OtherPars => 'char *kernel_type; double noval; IV nsamples => ns',
1867             GenericTypes => [ qw(F D E) ],
1868             PMCode => <<'EOF',
1869             # support routine
1870             {
1871             my %warp2d = map { ($_,1) } qw( tanh sinc sinc2 lanczos hamming hann );
1872             # note: convert to lower case
1873             sub _check_kernel ($$) {
1874             my $kernel = lc shift;
1875             my $code = shift;
1876             barf "Unknown kernel $kernel sent to $code\n" .
1877             "\tmust be one of [" . join(',',keys %warp2d) . "]\n"
1878             unless exists $warp2d{$kernel};
1879             return $kernel;
1880             }
1881             }
1882              
1883             sub PDL::warp2d {
1884             my $opts = PDL::Options->new( { KERNEL => "tanh", NOVAL => 0 } );
1885             $opts->options( pop(@_) ) if ref($_[$#_]) eq "HASH";
1886             die "Usage: warp2d( in(m,n), px(np,np); py(np,np); [o] out(m,n), {Options} )"
1887             if $#_<2 || $#_>3;
1888             my $img = shift;
1889             my $px = shift;
1890             my $py = shift;
1891             my $out = $#_ == -1 ? PDL->null() : shift;
1892             # safety checks
1893             my $copt = $opts->current();
1894             my $kernel = _check_kernel( $$copt{KERNEL}, "warp2d" );
1895             &PDL::_warp2d_int( $img, $px, $py, $out, $kernel, $$copt{NOVAL}, _get_kernel_size() );
1896             return $out;
1897             }
1898             EOF
1899             Code => <<'EOF',
1900             PDL_Indx k, lx_3, ly_3, px, py, tabx, taby;
1901             PDL_Indx da[16], db[16] ;
1902             PDL_LDouble cur, neighbors[16], rsc[8], sumrs, x, y, *kernel = $P(kernel), *poly = $P(poly);
1903              
1904             /* Generate interpolation kernel */
1905             if (!generate_interpolation_kernel($COMP(kernel_type), $SIZE(ns), kernel))
1906             $CROAK("Invalid kernel type '%s'", $COMP(kernel_type));
1907              
1908             /* Compute sizes */
1909             lx_3 = $SIZE(m) - 3;
1910             ly_3 = $SIZE(n) - 3;
1911              
1912             /* Pre compute leaps for 16 closest neighbors positions */
1913             da[0] = -1; db[0] = -1;
1914             da[1] = 0; db[1] = -1;
1915             da[2] = 1; db[2] = -1;
1916             da[3] = 2; db[3] = -1;
1917              
1918             da[4] = -1; db[4] = 0;
1919             da[5] = 0; db[5] = 0;
1920             da[6] = 1; db[6] = 0;
1921             da[7] = 2; db[7] = 0;
1922              
1923             da[8] = -1; db[8] = 1;
1924             da[9] = 0; db[9] = 1;
1925             da[10] = 1; db[10] = 1;
1926             da[11] = 2; db[11] = 1;
1927              
1928             da[12] = -1; db[12] = 2;
1929             da[13] = 0; db[13] = 2;
1930             da[14] = 1; db[14] = 2;
1931             da[15] = 2; db[15] = 2;
1932              
1933             poly[0] = 1.0;
1934              
1935             /* Loop over the output image */
1936             broadcastloop %{
1937             loop(n) %{
1938              
1939             /* fill in poly array */
1940             loop (np=1) %{
1941             poly[np] = (PDL_LDouble) n * poly[np-1];
1942             %}
1943              
1944             loop(m) %{
1945              
1946             /* Compute the original source for this pixel */
1947             x = poly2d_compute( $SIZE(np), $P(px), m, poly );
1948             y = poly2d_compute( $SIZE(np), $P(py), m, poly );
1949              
1950             /* Which is the closest integer positioned neighbor? */
1951             px = (int)x ;
1952             py = (int)y ;
1953              
1954             if ((px < 1) || (px > lx_3) || (py < 1) || (py > ly_3))
1955             $warp() = ($GENERIC()) $COMP(noval);
1956             else {
1957              
1958             /* Now feed the positions for the closest 16 neighbors */
1959             for (k=0 ; k<16 ; k++) {
1960             neighbors[k] = (PDL_LDouble) $img( m => px+da[k], n => py+db[k] );
1961             }
1962              
1963             /* Which tabulated value index shall we use? */
1964             tabx = (x - (PDL_LDouble)px) * (PDL_LDouble)(TABSPERPIX) ;
1965             taby = (y - (PDL_LDouble)py) * (PDL_LDouble)(TABSPERPIX) ;
1966              
1967             /* Compute resampling coefficients */
1968             /* rsc[0..3] in x, rsc[4..7] in y */
1969              
1970             rsc[0] = kernel[TABSPERPIX + tabx] ;
1971             rsc[1] = kernel[tabx] ;
1972             rsc[2] = kernel[TABSPERPIX - tabx] ;
1973             rsc[3] = kernel[2 * TABSPERPIX - tabx] ;
1974             rsc[4] = kernel[TABSPERPIX + taby] ;
1975             rsc[5] = kernel[taby] ;
1976             rsc[6] = kernel[TABSPERPIX - taby] ;
1977             rsc[7] = kernel[2 * TABSPERPIX - taby] ;
1978              
1979             sumrs = (rsc[0]+rsc[1]+rsc[2]+rsc[3]) *
1980             (rsc[4]+rsc[5]+rsc[6]+rsc[7]) ;
1981              
1982             /* Compute interpolated pixel now */
1983             cur = rsc[4] * ( rsc[0]*neighbors[0] +
1984             rsc[1]*neighbors[1] +
1985             rsc[2]*neighbors[2] +
1986             rsc[3]*neighbors[3] ) +
1987             rsc[5] * ( rsc[0]*neighbors[4] +
1988             rsc[1]*neighbors[5] +
1989             rsc[2]*neighbors[6] +
1990             rsc[3]*neighbors[7] ) +
1991             rsc[6] * ( rsc[0]*neighbors[8] +
1992             rsc[1]*neighbors[9] +
1993             rsc[2]*neighbors[10] +
1994             rsc[3]*neighbors[11] ) +
1995             rsc[7] * ( rsc[0]*neighbors[12] +
1996             rsc[1]*neighbors[13] +
1997             rsc[2]*neighbors[14] +
1998             rsc[3]*neighbors[15] ) ;
1999              
2000             /* Copy the value to the output image */
2001             $warp() = ($GENERIC()) (cur/sumrs);
2002              
2003             } /* if: edge or interior */
2004              
2005             %} /* loop(m) */
2006             %} /* loop(n) */
2007             %} /* broadcastloop */
2008             EOF
2009             ); # pp_def: warp2d
2010              
2011             pp_addxs( '
2012              
2013             int
2014             _get_kernel_size()
2015             PROTOTYPE:
2016             CODE:
2017             RETVAL = KERNEL_SAMPLES;
2018             OUTPUT:
2019             RETVAL
2020              
2021             ');
2022              
2023             pp_def( 'warp2d_kernel',
2024             Doc => <<'EOF',
2025             =for ref
2026              
2027             Return the specified kernel, as used by L
2028              
2029             =for usage
2030              
2031             ( $x, $k ) = warp2d_kernel( $name )
2032              
2033             The valid values for C<$name> are the same as the C option
2034             of L.
2035              
2036             =for example
2037              
2038             line warp2d_kernel( "hamming" );
2039             EOF
2040             HandleBad => 0,
2041             PMCode => '
2042             sub PDL::warp2d_kernel ($) {
2043             my $kernel = _check_kernel( shift, "warp2d_kernel" );
2044             &PDL::_warp2d_kernel_int( my $x=PDL->null, my $k=PDL->null, $kernel, _get_kernel_size() );
2045             return ( $x, $k );
2046             }
2047             ',
2048             Pars => '[o] x(n); [o] k(n); ldouble [t] kernel(n)',
2049             OtherPars => 'char *name; PDL_Indx nsize => n',
2050             GenericTypes => [ qw(F D E) ],
2051             Code => <<'EOF',
2052             PDL_LDouble *kernel = $P(kernel);
2053             if (!generate_interpolation_kernel($COMP(name), $SIZE(n), kernel))
2054             $CROAK("Invalid kernel type '%s'", $COMP(name));
2055             /* fill in ndarrays */
2056             PDL_LDouble xx = 0.0;
2057             broadcastloop %{
2058             loop (n) %{
2059             $x() = xx;
2060             $k() = kernel[n];
2061             xx += 1.0 / (double) TABSPERPIX;
2062             %}
2063             %}
2064             EOF
2065             );
2066              
2067             pp_done();