File Coverage

blib/lib/PDL/Image2D.pm
Criterion Covered Total %
statement 187 196 95.4
branch 59 90 65.5
condition 30 75 40.0
subroutine 23 24 95.8
pod 0 12 0.0
total 299 397 75.3


line stmt bran cond sub pod time code
1              
2             #
3             # GENERATED WITH PDL::PP! Don't modify!
4             #
5             package PDL::Image2D;
6              
7             @EXPORT_OK = qw( PDL::PP conv2d PDL::PP med2d PDL::PP med2df PDL::PP box2d PDL::PP patch2d PDL::PP patchbad2d PDL::PP max2d_ind PDL::PP centroid2d cc8compt cc4compt PDL::PP ccNcompt polyfill pnpoly polyfillv rotnewsz PDL::PP rot2d PDL::PP bilin2d PDL::PP rescale2d fitwarp2d applywarp2d PDL::PP warp2d warp2d_kernel PDL::PP warp2d_kernel );
8             %EXPORT_TAGS = (Func=>[@EXPORT_OK]);
9              
10 4     4   2298 use PDL::Core;
  4         10  
  4         21  
11 4     4   31 use PDL::Exporter;
  4         9  
  4         23  
12 4     4   19 use DynaLoader;
  4         8  
  4         220  
13              
14              
15              
16            
17             @ISA = ( 'PDL::Exporter','DynaLoader' );
18             push @PDL::Core::PP, __PACKAGE__;
19             bootstrap PDL::Image2D ;
20              
21              
22              
23              
24              
25             =head1 NAME
26              
27             PDL::Image2D - Miscellaneous 2D image processing functions
28              
29             =head1 DESCRIPTION
30              
31             Miscellaneous 2D image processing functions - for want
32             of anywhere else to put them.
33              
34             =head1 SYNOPSIS
35              
36             use PDL::Image2D;
37              
38             =cut
39              
40 4     4   26 use PDL; # ensure qsort routine available
  4         9  
  4         24  
41 4     4   25 use PDL::Math;
  4         8  
  4         22  
42 4     4   28 use Carp;
  4         8  
  4         203  
43              
44 4     4   26 use strict;
  4         7  
  4         3154  
45              
46              
47              
48              
49              
50              
51              
52             =head1 FUNCTIONS
53              
54              
55              
56             =cut
57              
58              
59              
60              
61              
62              
63              
64              
65              
66              
67              
68              
69              
70              
71              
72              
73              
74              
75              
76              
77              
78             =head2 conv2d
79              
80             =for sig
81              
82             Signature: (a(m,n); kern(p,q); [o]b(m,n); int opt)
83              
84              
85             =for ref
86              
87             2D convolution of an array with a kernel (smoothing)
88              
89             For large kernels, using a FFT routine,
90             such as L in C,
91             will be quicker.
92              
93             =for usage
94              
95             $new = conv2d $old, $kernel, {OPTIONS}
96              
97             =for example
98              
99             $smoothed = conv2d $image, ones(3,3), {Boundary => Reflect}
100              
101             =for options
102              
103             Boundary - controls what values are assumed for the image when kernel
104             crosses its edge:
105             => Default - periodic boundary conditions
106             (i.e. wrap around axis)
107             => Reflect - reflect at boundary
108             => Truncate - truncate at boundary
109             => Replicate - repeat boundary pixel values
110              
111              
112              
113             =for bad
114              
115             Unlike the FFT routines, conv2d is able to process bad values.
116              
117             =cut
118              
119              
120              
121              
122              
123              
124             sub PDL::conv2d {
125 8 100   8 0 55 my $opt; $opt = pop @_ if ref($_[$#_]) eq 'HASH';
  8         36  
126 8 50 33     52 die 'Usage: conv2d( a(m,n), kern(p,q), [o]b(m,n), {Options} )'
127             if $#_<1 || $#_>2;
128 8         34 my($x,$kern) = @_;
129 8 50       39 my $c = $#_ == 2 ? $_[2] : $x->nullcreate;
130             &PDL::_conv2d_int($x,$kern,$c,
131             (!(defined $opt && exists $$opt{Boundary}))?0:
132             (($$opt{Boundary} eq "Reflect") +
133             2*($$opt{Boundary} eq "Truncate") +
134 8 100 66     101221 3*($$opt{Boundary} eq "Replicate")));
135 8         104 return $c;
136             }
137              
138              
139              
140             *conv2d = \&PDL::conv2d;
141              
142              
143              
144              
145              
146             =head2 med2d
147              
148             =for sig
149              
150             Signature: (a(m,n); kern(p,q); [o]b(m,n); int opt)
151              
152              
153             =for ref
154              
155             2D median-convolution of an array with a kernel (smoothing)
156              
157             Note: only points in the kernel E0 are included in the median, other
158             points are weighted by the kernel value (medianing lots of zeroes
159             is rather pointless)
160              
161             =for usage
162              
163             $new = med2d $old, $kernel, {OPTIONS}
164              
165             =for example
166              
167             $smoothed = med2d $image, ones(3,3), {Boundary => Reflect}
168              
169             =for options
170              
171             Boundary - controls what values are assumed for the image when kernel
172             crosses its edge:
173             => Default - periodic boundary conditions (i.e. wrap around axis)
174             => Reflect - reflect at boundary
175             => Truncate - truncate at boundary
176             => Replicate - repeat boundary pixel values
177              
178              
179              
180             =for bad
181              
182             Bad values are ignored in the calculation. If all elements within the
183             kernel are bad, the output is set bad.
184              
185             =cut
186              
187              
188              
189              
190              
191              
192             sub PDL::med2d {
193 2 50   2 0 12 my $opt; $opt = pop @_ if ref($_[$#_]) eq 'HASH';
  2         12  
194 2 50 33     15 die 'Usage: med2d( a(m,n), kern(p,q), [o]b(m,n), {Options} )'
195             if $#_<1 || $#_>2;
196 2         6 my($x,$kern) = @_;
197 2 50       70 croak "med2d: kernel must contain some positive elements.\n"
198             if all( $kern <= 0 );
199 2 50       19 my $c = $#_ == 2 ? $_[2] : $x->nullcreate;
200             &PDL::_med2d_int($x,$kern,$c,
201             (!(defined $opt && exists $$opt{Boundary}))?0:
202             (($$opt{Boundary} eq "Reflect") +
203             2*($$opt{Boundary} eq "Truncate") +
204 2 50 33     182 3*($$opt{Boundary} eq "Replicate")));
205 2         26 return $c;
206             }
207              
208              
209              
210             *med2d = \&PDL::med2d;
211              
212              
213              
214              
215              
216             =head2 med2df
217              
218             =for sig
219              
220             Signature: (a(m,n); [o]b(m,n); int __p_size; int __q_size; int opt)
221              
222              
223             =for ref
224              
225             2D median-convolution of an array in a pxq window (smoothing)
226              
227             Note: this routine does the median over all points in a rectangular
228             window and is not quite as flexible as C in this regard
229             but slightly faster instead
230              
231             =for usage
232              
233             $new = med2df $old, $xwidth, $ywidth, {OPTIONS}
234              
235             =for example
236              
237             $smoothed = med2df $image, 3, 3, {Boundary => Reflect}
238              
239             =for options
240              
241             Boundary - controls what values are assumed for the image when kernel
242             crosses its edge:
243             => Default - periodic boundary conditions (i.e. wrap around axis)
244             => Reflect - reflect at boundary
245             => Truncate - truncate at boundary
246             => Replicate - repeat boundary pixel values
247              
248              
249              
250             =for bad
251              
252             med2df does not process bad values.
253             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
254              
255              
256             =cut
257              
258              
259              
260              
261              
262              
263             sub PDL::med2df {
264 1 50   1 0 8 my $opt; $opt = pop @_ if ref($_[$#_]) eq 'HASH';
  1         6  
265 1 50 33     8 die 'Usage: med2df( a(m,n), [o]b(m,n), p, q, {Options} )'
266             if $#_<2 || $#_>3;
267 1         3 my($x,$p,$q) = @_;
268 1 50 33     5 croak "med2df: kernel must contain some positive elements.\n"
269             if $p == 0 && $q == 0;
270 1 50       6 my $c = $#_ == 3 ? $_[3] : $x->nullcreate;
271             &PDL::_med2df_int($x,$c,$p,$q,
272             (!(defined $opt && exists $$opt{Boundary}))?0:
273             (($$opt{Boundary} eq "Reflect") +
274             2*($$opt{Boundary} eq "Truncate") +
275 1 50 33     72 3*($$opt{Boundary} eq "Replicate")));
276 1         7 return $c;
277             }
278              
279              
280              
281             *med2df = \&PDL::med2df;
282              
283              
284              
285              
286              
287             =head2 box2d
288              
289             =for sig
290              
291             Signature: (a(n,m); [o] b(n,m); int wx; int wy; int edgezero)
292              
293              
294             =for ref
295              
296             fast 2D boxcar average
297              
298             =for example
299              
300             $smoothim = $im->box2d($wx,$wy,$edgezero=1);
301              
302             The edgezero argument controls if edge is set to zero (edgezero=1)
303             or just keeps the original (unfiltered) values.
304              
305             C should be updated to support similar edge options
306             as C and C etc.
307              
308             Boxcar averaging is a pretty crude way of filtering. For serious stuff
309             better filters are around (e.g., use L with the appropriate
310             kernel). On the other hand it is fast and computational cost grows only
311             approximately linearly with window size.
312              
313              
314              
315             =for bad
316              
317             box2d does not process bad values.
318             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
319              
320              
321             =cut
322              
323              
324              
325              
326              
327              
328             *box2d = \&PDL::box2d;
329              
330              
331              
332              
333              
334             =head2 patch2d
335              
336             =for sig
337              
338             Signature: (a(m,n); int bad(m,n); [o]b(m,n))
339              
340              
341             =for ref
342              
343             patch bad pixels out of 2D images using a mask
344              
345             =for usage
346              
347             $patched = patch2d $data, $bad;
348              
349             C<$bad> is a 2D mask array where 1=bad pixel 0=good pixel.
350             Pixels are replaced by the average of their non-bad neighbours;
351             if all neighbours are bad, the original data value is
352             copied across.
353              
354              
355              
356             =for bad
357              
358             This routine does not handle bad values - use L instead
359              
360             =cut
361              
362              
363              
364              
365              
366              
367             *patch2d = \&PDL::patch2d;
368              
369              
370              
371              
372              
373             =head2 patchbad2d
374              
375             =for sig
376              
377             Signature: (a(m,n); [o]b(m,n))
378              
379              
380             =for ref
381              
382             patch bad pixels out of 2D images containing bad values
383              
384             =for usage
385              
386             $patched = patchbad2d $data;
387              
388             Pixels are replaced by the average of their non-bad neighbours;
389             if all neighbours are bad, the output is set bad.
390             If the input piddle contains I bad values, then a straight copy
391             is performed (see L).
392              
393              
394              
395             =for bad
396              
397             patchbad2d handles bad values. The output piddle I contain
398             bad values, depending on the pattern of bad values in the input piddle.
399              
400             =cut
401              
402              
403              
404              
405              
406              
407             *patchbad2d = \&PDL::patchbad2d;
408              
409              
410              
411              
412              
413             =head2 max2d_ind
414              
415             =for sig
416              
417             Signature: (a(m,n); [o]val(); int [o]x(); int[o]y())
418              
419              
420             =for ref
421              
422             Return value/position of maximum value in 2D image
423              
424             Contributed by Tim Jeness
425              
426              
427              
428             =for bad
429              
430             Bad values are excluded from the search. If all pixels
431             are bad then the output is set bad.
432              
433              
434              
435             =cut
436              
437              
438              
439              
440              
441              
442             *max2d_ind = \&PDL::max2d_ind;
443              
444              
445              
446              
447              
448             =head2 centroid2d
449              
450             =for sig
451              
452             Signature: (im(m,n); x(); y(); box(); [o]xcen(); [o]ycen())
453              
454              
455             =for ref
456              
457             Refine a list of object positions in 2D image by centroiding in a box
458              
459             C<$box> is the full-width of the box, i.e. the window
460             is C<+/- $box/2>.
461              
462              
463              
464             =for bad
465              
466             Bad pixels are excluded from the centroid calculation. If all elements are
467             bad (or the pixel sum is 0 - but why would you be centroiding
468             something with negatives in...) then the output values are set bad.
469              
470              
471              
472             =cut
473              
474              
475              
476              
477              
478              
479             *centroid2d = \&PDL::centroid2d;
480              
481              
482              
483              
484             =head2 cc8compt
485              
486             =for ref
487              
488             Connected 8-component labeling of a binary image.
489              
490             Connected 8-component labeling of 0,1 image - i.e. find separate
491             segmented objects and fill object pixels with object number.
492             8-component labeling includes all neighboring pixels.
493             This is just a front-end to ccNcompt. See also L.
494              
495             =for example
496              
497             $segmented = cc8compt( $image > $threshold );
498              
499             =head2 cc4compt
500              
501             =for ref
502              
503             Connected 4-component labeling of a binary image.
504              
505             Connected 4-component labeling of 0,1 image - i.e. find separate
506             segmented objects and fill object pixels with object number.
507             4-component labling does not include the diagonal neighbors.
508             This is just a front-end to ccNcompt. See also L.
509              
510             =for example
511              
512             $segmented = cc4compt( $image > $threshold );
513              
514             =cut
515              
516             sub PDL::cc8compt{
517 1     1 0 69 return ccNcompt(shift,8);
518             }
519             *cc8compt = \&PDL::cc8compt;
520              
521             sub PDL::cc4compt{
522 3     3 0 160 return ccNcompt(shift,4);
523             }
524             *cc4compt = \&PDL::cc4compt;
525              
526              
527              
528              
529              
530             =head2 ccNcompt
531              
532             =for sig
533              
534             Signature: (a(m,n); int+ [o]b(m,n); int con)
535              
536              
537              
538             =for ref
539              
540             Connected component labeling of a binary image.
541              
542             Connected component labeling of 0,1 image - i.e. find separate
543             segmented objects and fill object pixels with object number.
544             See also L and L.
545              
546             The connectivity parameter must be 4 or 8.
547              
548             =for example
549              
550             $segmented = ccNcompt( $image > $threshold, 4);
551              
552             $segmented2 = ccNcompt( $image > $threshold, 8);
553              
554             where the second parameter specifies the connectivity (4 or 8) of the labeling.
555              
556              
557              
558             =for bad
559              
560             ccNcompt ignores the bad-value flag of the input piddles.
561             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
562              
563              
564             =cut
565              
566              
567              
568              
569              
570              
571             *ccNcompt = \&PDL::ccNcompt;
572              
573              
574              
575             =head2 polyfill
576              
577             =for ref
578              
579             fill the area of the given polygon with the given colour.
580              
581             This function works inplace, i.e. modifies C.
582              
583             =for usage
584              
585             polyfill($im,$ps,$colour,[\%options]);
586              
587             The default method of determining which points lie inside of the polygon used
588             is not as strict as the method used in L. Often, it includes vertices
589             and edge points. Set the C option to change this behaviour.
590              
591             =for option
592              
593             Method - Set the method used to determine which points lie in the polygon.
594             => Default - internal PDL algorithm
595             => pnpoly - use the L algorithm
596              
597             =for example
598              
599             # Make a convex 3x3 square of 1s in an image using the pnpoly algorithm
600             $ps = pdl([3,3],[3,6],[6,6],[6,3]);
601             polyfill($im,$ps,1,{'Method' =>'pnpoly'});
602              
603             =cut
604             sub PDL::polyfill {
605 3     3 0 67 my $opt;
606 3 100       13 $opt = pop @_ if ref($_[-1]) eq 'HASH';
607 3 50       11 barf('Usage: polyfill($im,$ps,$colour,[\%options])') unless @_==3;
608 3         9 my ($im,$ps,$colour) = @_;
609              
610 3 100       10 if($opt) {
611 4     4   41 use PDL::Options qw();
  4         13  
  4         1698  
612 1         7 my $parsed = PDL::Options->new({'Method' => undef});
613 1         4 $parsed->options($opt);
614 1 50       3 if( $parsed->current->{'Method'} eq 'pnpoly' ) {
615 1         46 PDL::pnpolyfill_pp($im,$ps,$colour);
616             }
617             }
618             else
619             {
620 2         310 PDL::polyfill_pp($im,$ps,$colour);
621             }
622 2         36 return $im;
623              
624             }
625              
626             *polyfill = \&PDL::polyfill;
627              
628              
629              
630              
631             =head2 pnpoly
632              
633             =for ref
634              
635             'points in a polygon' selection from a 2-D piddle
636              
637             =for usage
638              
639             $mask = $img->pnpoly($ps);
640              
641             # Old style, do not use
642             $mask = pnpoly($x, $y, $px, $py);
643              
644             For a closed polygon determined by the sequence of points in {$px,$py}
645             the output of pnpoly is a mask corresponding to whether or not each
646             coordinate (x,y) in the set of test points, {$x,$y}, is in the interior
647             of the polygon. This is the 'points in a polygon' algorithm from
648             L
649             and vectorized for PDL by Karl Glazebrook.
650              
651             =for example
652              
653             # define a 3-sided polygon (a triangle)
654             $ps = pdl([3, 3], [20, 20], [34, 3]);
655              
656             # $tri is 0 everywhere except for points in polygon interior
657             $tri = $img->pnpoly($ps);
658              
659             With the second form, the x and y coordinates must also be specified.
660             B< I >.
661              
662             $px = pdl( 3, 20, 34 );
663             $py = pdl( 3, 20, 3 );
664             $x = $img->xvals; # get x pixel coords
665             $y = $img->yvals; # get y pixel coords
666              
667             # $tri is 0 everywhere except for points in polygon interior
668             $tri = pnpoly($x,$y,$px,$py);
669              
670             =cut
671              
672             # From: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
673             #
674             # Fixes needed to pnpoly code:
675             #
676             # Use topdl() to ensure piddle args
677             #
678             # Add POD docs for usage
679             #
680             # Calculate first term in & expression to use to fix divide-by-zero when
681             # the test point is in line with a vertical edge of the polygon.
682             # By adding the value of $mask we prevent a divide-by-zero since the &
683             # operator does not "short circuit".
684              
685             sub PDL::pnpoly {
686 2 50 66 2 0 19 barf('Usage: $mask = pnpoly($img, $ps);') unless(@_==2 || @_==4);
687 2         6 my ($tx, $ty, $vertx, $verty) = @_;
688              
689             # if only two inputs, use the pure PP version
690 2 100       7 unless( defined $vertx ) {
691 1 50       8 barf("ps must contain pairwise points.\n") unless $ty->getdim(0) == 2;
692              
693             # Input mapping: $img => $tx, $ps => $ty
694 1         98 return PDL::pnpoly_pp($tx,$ty);
695             }
696              
697 1         4 my $testx = PDL::Core::topdl($tx)->dummy(0);
698 1         4 my $testy = PDL::Core::topdl($ty)->dummy(0);
699 1         5 my $vertxj = PDL::Core::topdl($vertx)->rotate(1);
700 1         12 my $vertyj = PDL::Core::topdl($verty)->rotate(1);
701 1         70 my $mask = ( ($verty>$testy) == ($vertyj>$testy) );
702 1         10 my $c = sumover( ! $mask & ( $testx < ($vertxj-$vertx) * ($testy-$verty)
703             / ($vertyj-$verty+$mask) + $vertx) ) % 2;
704 1         29 return $c;
705             }
706              
707             *pnpoly = \&PDL::pnpoly;
708              
709              
710              
711              
712             =head2 polyfillv
713              
714             =for ref
715              
716             return the (dataflown) area of an image described by a polygon
717              
718             =for usage
719              
720             polyfillv($im,$ps,[\%options]);
721              
722             The default method of determining which points lie inside of the polygon used
723             is not as strict as the method used in L. Often, it includes vertices
724             and edge points. Set the C option to change this behaviour.
725              
726             =for option
727              
728             Method - Set the method used to determine which points lie in the polygon.
729             => Default - internal PDL algorithm
730             => pnpoly - use the L algorithm
731              
732             =for example
733              
734             # increment intensity in area bounded by $poly using the pnpoly algorithm
735             $im->polyfillv($poly,{'Method'=>'pnpoly'})++; # legal in perl >= 5.6
736              
737             # compute average intensity within area bounded by $poly using the default algorithm
738             $av = $im->polyfillv($poly)->avg;
739              
740             =cut
741              
742             sub PDL::polyfillv :lvalue {
743 2     2 0 13 my $opt;
744 2 100       10 $opt = pop @_ if ref($_[-1]) eq 'HASH';
745 2 50       8 barf('Usage: polyfillv($im,$ps,[\%options])') unless @_==2;
746              
747 2         5 my ($im,$ps) = @_;
748 2 50       10 barf("ps must contain pairwise points.\n") unless $ps->getdim(0) == 2;
749              
750 2 100       6 if($opt) {
751 4     4   30 use PDL::Options qw();
  4         8  
  4         7038  
752 1         6 my $parsed = PDL::Options->new({'Method' => undef});
753 1         5 $parsed->options($opt);
754 1 50       5 return $im->where(PDL::pnpoly_pp($im, $ps)) if $parsed->current->{'Method'} eq 'pnpoly';
755             }
756              
757 1         5 my $msk = zeroes(long,$im->dims);
758 1         44 PDL::polyfill_pp($msk, $ps, 1);
759 1         12 return $im->where($msk);
760             }
761             *polyfillv = \&PDL::polyfillv;
762              
763              
764              
765              
766              
767             =head2 rot2d
768              
769             =for sig
770              
771             Signature: (im(m,n); float angle(); bg(); int aa(); [o] om(p,q))
772              
773              
774             =for ref
775              
776             rotate an image by given C
777              
778             =for example
779              
780             # rotate by 10.5 degrees with antialiasing, set missing values to 7
781             $rot = $im->rot2d(10.5,7,1);
782              
783             This function rotates an image through an C between -90 and + 90
784             degrees. Uses/doesn't use antialiasing depending on the C flag.
785             Pixels outside the rotated image are set to C.
786              
787             Code modified from pnmrotate (Copyright Jef Poskanzer) with an algorithm based
788             on "A Fast Algorithm for General Raster Rotation" by Alan Paeth,
789             Graphics Interface '86, pp. 77-81.
790              
791             Use the C function to find out about the dimension of the
792             newly created image
793              
794             ($newcols,$newrows) = rotnewsz $oldn, $oldm, $angle;
795              
796             L offers a more general interface to
797             distortions, including rotation, with various types of sampling; but
798             rot2d is faster.
799              
800              
801              
802             =for bad
803              
804             rot2d ignores the bad-value flag of the input piddles.
805             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
806              
807              
808             =cut
809              
810              
811              
812              
813              
814              
815             *rot2d = \&PDL::rot2d;
816              
817              
818              
819              
820              
821             =head2 bilin2d
822              
823             =for sig
824              
825             Signature: (Int(n,m); O(q,p))
826              
827              
828             =for ref
829              
830             Bilinearly maps the first piddle in the second. The
831             interpolated values are actually added to the second
832             piddle which is supposed to be larger than the first one.
833              
834              
835              
836             =for bad
837              
838             bilin2d ignores the bad-value flag of the input piddles.
839             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
840              
841              
842             =cut
843              
844              
845              
846              
847              
848              
849             *bilin2d = \&PDL::bilin2d;
850              
851              
852              
853              
854              
855             =head2 rescale2d
856              
857             =for sig
858              
859             Signature: (Int(m,n); O(p,q))
860              
861              
862             =for ref
863              
864             The first piddle is rescaled to the dimensions of the second
865             (expanding or meaning values as needed) and then added to it in place.
866             Nothing useful is returned.
867              
868             If you want photometric accuracy or automatic FITS header metadata
869             tracking, consider using L
870             instead: it does these things, at some speed penalty compared to
871             rescale2d.
872              
873              
874              
875             =for bad
876              
877             rescale2d ignores the bad-value flag of the input piddles.
878             It will set the bad-value flag of all output piddles if the flag is set for any of the input piddles.
879              
880              
881             =cut
882              
883              
884              
885              
886              
887              
888             *rescale2d = \&PDL::rescale2d;
889              
890              
891              
892              
893              
894             =head2 fitwarp2d
895              
896             =for ref
897              
898             Find the best-fit 2D polynomial to describe
899             a coordinate transformation.
900              
901             =for usage
902              
903             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, $nf, { options } )
904              
905             Given a set of points in the output plane (C<$u,$v>), find
906             the best-fit (using singular-value decomposition) 2D polynomial
907             to describe the mapping back to the image plane (C<$x,$y>).
908             The order of the fit is controlled by the C<$nf> parameter
909             (the maximum power of the polynomial is C<$nf - 1>), and you
910             can restrict the terms to fit using the C option.
911              
912             C<$px> and C<$py> are C by C element piddles which describe
913             a polynomial mapping (of order C)
914             from the I C<(u,v)> image to the I C<(x,y)> image:
915              
916             x = sum(j=0,np-1) sum(i=0,np-1) px(i,j) * u^i * v^j
917             y = sum(j=0,np-1) sum(i=0,np-1) py(i,j) * u^i * v^j
918              
919             The transformation is returned for the reverse direction (ie
920             output to input image) since that is what is required by the
921             L routine. The L
922             routine can be used to convert a set of C<$u,$v> points given
923             C<$px> and C<$py>.
924              
925             Options:
926              
927             =for options
928              
929             FIT - which terms to fit? default ones(byte,$nf,$nf)
930              
931             =begin comment
932              
933             old option, caused trouble
934             THRESH - in svd, remove terms smaller than THRESH * max value
935             default is 1.0e-5
936              
937             =end comment
938              
939             =over 4
940              
941             =item FIT
942              
943             C allows you to restrict which terms of the polynomial to fit:
944             only those terms for which the FIT piddle evaluates to true will be
945             evaluated. If a 2D piddle is sent in, then it is
946             used for the x and y polynomials; otherwise
947             C<< $fit->slice(":,:,(0)") >> will be used for C<$px> and
948             C<< $fit->slice(":,:,(1)") >> will be used for C<$py>.
949              
950             =begin comment
951              
952             =item THRESH
953              
954             Remove all singular values whose value is less than C
955             times the largest singular value.
956              
957             =end comment
958              
959             =back
960              
961             The number of points must be at least equal to the number of
962             terms to fit (C<$nf*$nf> points for the default value of C).
963              
964             =for example
965              
966             # points in original image
967             $x = pdl( 0, 0, 100, 100 );
968             $y = pdl( 0, 100, 100, 0 );
969             # get warped to these positions
970             $u = pdl( 10, 10, 90, 90 );
971             $v = pdl( 10, 90, 90, 10 );
972             #
973             # shift of origin + scale x/y axis only
974             $fit = byte( [ [1,1], [0,0] ], [ [1,0], [1,0] ] );
975             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, 2, { FIT => $fit } );
976             print "px = ${px}py = $py";
977             px =
978             [
979             [-12.5 1.25]
980             [ 0 0]
981             ]
982             py =
983             [
984             [-12.5 0]
985             [ 1.25 0]
986             ]
987             #
988             # Compared to allowing all 4 terms
989             ( $px, $py ) = fitwarp2d( $x, $y, $u, $v, 2 );
990             print "px = ${px}py = $py";
991             px =
992             [
993             [ -12.5 1.25]
994             [ 1.110223e-16 -1.1275703e-17]
995             ]
996             py =
997             [
998             [ -12.5 1.6653345e-16]
999             [ 1.25 -5.8546917e-18]
1000             ]
1001              
1002             # A higher-degree polynomial should not affect the answer much, but
1003             # will require more control points
1004              
1005             $x = $x->glue(0,pdl(50,12.5, 37.5, 12.5, 37.5));
1006             $y = $y->glue(0,pdl(50,12.5, 37.5, 37.5, 12.5));
1007             $u = $u->glue(0,pdl(73,20,40,20,40));
1008             $v = $v->glue(0,pdl(29,20,40,40,20));
1009             ( $px3, $py3 ) = fitwarp2d( $x, $y, $u, $v, 3 );
1010             print "px3 =${px3}py3 =$py3";
1011             px3 =
1012             [
1013             [-6.4981162e+08 71034917 -726498.95]
1014             [ 49902244 -5415096.7 55945.388]
1015             [ -807778.46 88457.191 -902.51612]
1016             ]
1017             py3 =
1018             [
1019             [-6.2732159e+08 68576392 -701354.77]
1020             [ 48175125 -5227679.8 54009.114]
1021             [ -779821.18 85395.681 -871.27997]
1022             ]
1023              
1024             #This illustrates an important point about singular value
1025             #decompositions that are used in fitwarp2d: like all SVDs, the
1026             #rotation matrices are not unique, and so the $px and $py returned
1027             #by fitwarp2d are not guaranteed to be the "simplest" solution.
1028             #They do still work, though:
1029              
1030             ($x3,$y3) = applywarp2d($px3,$py3,$u,$v);
1031             print approx $x3,$x,1e-4;
1032             [1 1 1 1 1 1 1 1 1]
1033             print approx $y3,$y;
1034             [1 1 1 1 1 1 1 1 1]
1035              
1036             =head2 applywarp2d
1037              
1038             =for ref
1039              
1040             Transform a set of points using a 2-D polynomial mapping
1041              
1042             =for usage
1043              
1044             ( $x, $y ) = applywarp2d( $px, $py, $u, $v )
1045              
1046             Convert a set of points (stored in 1D piddles C<$u,$v>)
1047             to C<$x,$y> using the 2-D polynomial with coefficients stored in C<$px>
1048             and C<$py>. See L
1049             for more information on the format of C<$px> and C<$py>.
1050              
1051             =cut
1052              
1053             # use SVD to fit data. Assuming no errors.
1054              
1055             =pod
1056              
1057             =begin comment
1058              
1059             Some explanation of the following three subroutines (_svd, _mkbasis,
1060             and fitwarp2d): See Wolberg 1990 (full ref elsewhere in this
1061             documentation), Chapter 3.6 "Polynomial Transformations". The idea is
1062             that, given a set of control points in the input and output images
1063             denoted by coordinates (x,y) and (u,v), we want to create a polynomial
1064             transformation that relates u to linear combinations of powers of x
1065             and y, and another that relates v to powers of x and y.
1066              
1067             The transformations used here and by Wolberg differ slightly, but the
1068             basic idea is something like this. For each u and each v, define a
1069             transform:
1070              
1071             u = (sum over j) (sum over i) a_{ij} x**i * y**j , (eqn 1)
1072             v = (sum over j) (sum over i) b_{ij} x**i * y**j . (eqn 2)
1073              
1074             We can write this in matrix notation. Given that there are M control
1075             points, U is a column vector with M rows. A and B are vectors containing
1076             the N coefficients (related to the degree of the polynomial fit). W
1077             is an MxN matrix of the basis row-vectors (the x**i y**j).
1078              
1079             The matrix equations we are trying to solve are
1080             U = W A , (eqn 3)
1081             V = W B . (eqn 4)
1082              
1083             We need to find the A and B column vectors, those are the coefficients
1084             of the polynomial terms in W. W is not square, so it has no inverse.
1085             But is has a pseudo-inverse W^+ that is NxM. We are going to use that
1086             pseudo-inverse to isolate A and B, like so:
1087              
1088             W^+ U = W^+ W A = A (eqn 5)
1089             W^+ V = W^+ W B = B (eqn 6)
1090              
1091             We are going to get W^+ by performing a singular value decomposition
1092             of W (to use some of the variable names below):
1093              
1094             W = $svd_u x SIGMA x $svd_v->transpose (eqn 7)
1095             W^+ = $svd_v x SIGMA^+ x $svd_u->transpose . (eqn 8)
1096              
1097             Here SIGMA is a square diagonal matrix that contains the singular
1098             values of W that are in the variable $svd_w. SIGMA^+ is the
1099             pseudo-inverse of SIGMA, which is calculated by replacing the non-zero
1100             singular values with their reciprocals, and then taking the transpose
1101             of the matrix (which is a no-op since the matrix is square and
1102             diagonal).
1103              
1104             So the code below does this:
1105              
1106             _mkbasis computes the matrix W, given control coordinates u and v and
1107             the maximum degree of the polynomial (and the terms to use).
1108              
1109             _svd takes the svd of W, computes the pseudo-inverse of W, and then
1110             multiplies that with the U vector (there called $y). The output of
1111             _svd is the A or B vector in eqns 5 & 6 above. Rarely is the matrix
1112             multiplication explicit, unfortunately, so I have added EXPLANATIONs
1113             below.
1114              
1115             =end comment
1116              
1117             =cut
1118              
1119             sub _svd ($$) {
1120 18     18   63 my $basis = shift;
1121 18         30 my $y = shift;
1122             # my $thresh = shift;
1123              
1124             # if we had errors for these points, would normalise the
1125             # basis functions, and the output array, by these errors here
1126              
1127             # perform the SVD
1128 18         2693 my ( $svd_u, $svd_w, $svd_v ) = svd( $basis );
1129              
1130             # DAL, 09/2017: the reason for removing ANY singular values, much less
1131             #the smallest ones, is not clear. I commented the line below since this
1132             #actually removes the LARGEST values in SIGMA^+.
1133             # $svd_w *= ( $svd_w >= ($svd_w->max * $thresh ) );
1134             # The line below would instead remove the SMALLEST values in SIGMA^+, but I can see no reason to include it either.
1135             # $svd_w *= ( $svd_w <= ($svd_w->min / $thresh ) );
1136              
1137             # perform the back substitution
1138             # EXPLANATION: same thing as $svd_u->transpose x $y->transpose.
1139 18         108 my $tmp = $y x $svd_u;
1140              
1141             #EXPLANATION: the division by (the non-zero elements of) $svd_w (the singular values) is
1142             #equivalent to $sigma_plus x $tmp, so $tmp is now SIGMA^+ x $svd_u x $y
1143 18 50       44 if ( $PDL::Bad::Status ) {
1144 18         280 $tmp /= $svd_w->setvaltobad(0.0);
1145 18         97 $tmp->inplace->setbadtoval(0.0);
1146             } else {
1147             # not checked
1148 0         0 my $mask = ($svd_w == 0.0);
1149 0         0 $tmp /= ( $svd_w + $mask );
1150 0         0 $tmp *= ( 1 - $mask );
1151             }
1152              
1153             #EXPLANATION: $svd_v x SIGMA^+ x $svd_u x $y
1154 18         503 return sumover( $svd_v * $tmp );
1155              
1156             } # sub: _svd()
1157              
1158             #_mkbasis returns a piddle in which the k(=j*n+i)_th column is v**j * u**i
1159             #k=0 j=0 i=0
1160             #k=1 j=0 i=1
1161             #k=2 j=0 i=2
1162             #k=3 j=1 i=0
1163             # ...
1164              
1165             #each row corresponds to a control point
1166             #and the rows for each of the control points look like this, e.g.:
1167             # (1) (u) (u**2) (v) (vu) (v(u**2)) (v**2) ((v**2)u) ((v**2)(u**2))
1168             # and so on for the next control point.
1169              
1170             sub _mkbasis ($$$$) {
1171 14     14   19 my $fit = shift;
1172 14         19 my $npts = shift;
1173 14         23 my $u = shift;
1174 14         18 my $v = shift;
1175              
1176 14         52 my $n = $fit->getdim(0) - 1;
1177 14         38 my $ncoeff = sum( $fit );
1178              
1179 14         47 my $basis = zeroes( $u->type, $ncoeff, $npts );
1180 14         36 my $k = 0;
1181 14         41 foreach my $j ( 0 .. $n ) {
1182 39         656 my $tmp_v = $v**$j;
1183 39         246 foreach my $i ( 0 .. $n ) {
1184 117 100       313 if ( $fit->at($i,$j) ) {
1185 73         269 my $tmp = $basis->slice("($k),:");
1186 73         1740 $tmp .= $tmp_v * $u**$i;
1187 73         711 $k++;
1188             }
1189             }
1190             }
1191 14         37 return $basis;
1192              
1193             } # sub: _mkbasis()
1194              
1195             sub PDL::fitwarp2d {
1196 9 50 66 9 0 1120 croak "Usage: (\$px,\$py) = fitwarp2d(x(m);y(m);u(m);v(m);\$nf; { options })"
      33        
1197             if $#_ < 4 or ( $#_ >= 5 and ref($_[5]) ne "HASH" );
1198              
1199 9         36 my $x = shift;
1200 9         16 my $y = shift;
1201 9         16 my $u = shift;
1202 9         16 my $v = shift;
1203 9         16 my $nf = shift;
1204              
1205 9         41 my $opts = PDL::Options->new( { FIT => ones(byte,$nf,$nf) } ); #, THRESH => 1.0e-5 } );
1206 9 100       50 $opts->options( $_[0] ) if $#_ > -1;
1207 9         26 my $oref = $opts->current();
1208              
1209             # safety checks
1210 9         40 my $npts = $x->nelem;
1211 9 50 33     168 croak "fitwarp2d: x, y, u, and v must be the same size (and 1D)"
      33        
      33        
      33        
      33        
      33        
1212             unless $npts == $y->nelem and $npts == $u->nelem and $npts == $v->nelem
1213             and $x->getndims == 1 and $y->getndims == 1 and $u->getndims == 1 and $v->getndims == 1;
1214              
1215             # my $svd_thresh = $$oref{THRESH};
1216             # croak "fitwarp2d: THRESH option must be >= 0."
1217             # if $svd_thresh < 0;
1218              
1219 9         18 my $fit = $$oref{FIT};
1220 9         21 my $fit_ndim = $fit->getndims();
1221 9 50 66     111 croak "fitwarp2d: FIT option must be sent a (\$nf,\$nf[,2]) element piddle"
      33        
      33        
      33        
1222             unless UNIVERSAL::isa($fit,"PDL") and
1223             ($fit_ndim == 2 or ($fit_ndim == 3 and $fit->getdim(2) == 2)) and
1224             $fit->getdim(0) == $nf and $fit->getdim(1) == $nf;
1225              
1226             # how many coeffs to fit (first we ensure $fit is either 0 or 1)
1227 9         206 $fit = convert( $fit != 0, byte );
1228              
1229 9         48 my ( $fitx, $fity, $ncoeffx, $ncoeffy, $ncoeff );
1230 9 100       24 if ( $fit_ndim == 2 ) {
1231 5         8 $fitx = $fit;
1232 5         9 $fity = $fit;
1233 5         20 $ncoeff = $ncoeffx = $ncoeffy = sum( $fit );
1234             } else {
1235 4         15 $fitx = $fit->slice(",,(0)");
1236 4         14 $fity = $fit->slice(",,(1)");
1237 4         20 $ncoeffx = sum($fitx);
1238 4         12 $ncoeffy = sum($fity);
1239 4 50       16 $ncoeff = $ncoeffx > $ncoeffy ? $ncoeffx : $ncoeffy;
1240             }
1241              
1242 9 50       26 croak "fitwarp2d: number of points ($npts) must be >= \$ncoeff ($ncoeff)"
1243             unless $npts >= $ncoeff;
1244              
1245             # create the basis functions for the SVD fitting
1246 9         17 my ( $basisx, $basisy );
1247              
1248 9         25 $basisx = _mkbasis( $fitx, $npts, $u, $v );
1249              
1250 9 100       23 if ( $fit_ndim == 2 ) {
1251 5         11 $basisy = $basisx;
1252             } else {
1253 4         12 $basisy = _mkbasis( $fity, $npts, $u, $v );
1254             }
1255              
1256 9         22 my $px = _svd( $basisx, $x ); # $svd_thresh);
1257 9         42 my $py = _svd( $basisy, $y ); # $svd_thresh);
1258              
1259             # convert into $nf x $nf element piddles, if necessary
1260 9         39 my $nf2 = $nf * $nf;
1261              
1262 9 100 66     63 return ( $px->reshape($nf,$nf), $py->reshape($nf,$nf) )
1263             if $ncoeff == $nf2 and $ncoeffx == $ncoeffy;
1264              
1265             # re-create the matrix
1266 4         15 my $xtmp = zeroes( $nf, $nf );
1267 4         13 my $ytmp = zeroes( $nf, $nf );
1268              
1269 4         11 my $kx = 0;
1270 4         6 my $ky = 0;
1271 4         17 foreach my $i ( 0 .. ($nf - 1) ) {
1272 11         22 foreach my $j ( 0 .. ($nf - 1) ) {
1273 33 100       73 if ( $fitx->at($i,$j) ) {
1274 11         32 $xtmp->set($i,$j, $px->at($kx) );
1275 11         18 $kx++;
1276             }
1277 33 100       67 if ( $fity->at($i,$j) ) {
1278 11         30 $ytmp->set($i,$j, $py->at($ky) );
1279 11         18 $ky++;
1280             }
1281             }
1282             }
1283              
1284 4         73 return ( $xtmp, $ytmp )
1285              
1286             } # sub: fitwarp2d
1287              
1288             *fitwarp2d = \&PDL::fitwarp2d;
1289              
1290             sub PDL::applywarp2d {
1291             # checks
1292 1 50   1 0 11 croak "Usage: (\$x,\$y) = applywarp2d(px(nf,nf);py(nf,nf);u(m);v(m);)"
1293             if $#_ != 3;
1294              
1295 1         4 my $px = shift;
1296 1         2 my $py = shift;
1297 1         2 my $u = shift;
1298 1         3 my $v = shift;
1299 1         3 my $npts = $u->nelem;
1300              
1301             # safety check
1302 1 50 33     20 croak "applywarp2d: u and v must be the same size (and 1D)"
      33        
      33        
1303             unless $npts == $u->nelem and $npts == $v->nelem
1304             and $u->getndims == 1 and $v->getndims == 1;
1305              
1306 1         5 my $nf = $px->getdim(0);
1307 1         3 my $nf2 = $nf * $nf;
1308              
1309             # could remove terms with 0 coeff here
1310             # (would also have to remove them from px/py for
1311             # the matrix multiplication below)
1312             #
1313 1         43 my $mat = _mkbasis( ones(byte,$nf,$nf), $npts, $u, $v );
1314              
1315 1         9 my $x = reshape( $mat x $px->clump(-1)->transpose(), $npts );
1316 1         10 my $y = reshape( $mat x $py->clump(-1)->transpose(), $npts );
1317 1         11 return ( $x, $y );
1318              
1319             } # sub: applywarp2d
1320              
1321             *applywarp2d = \&PDL::applywarp2d;
1322              
1323              
1324              
1325              
1326             =head2 warp2d
1327              
1328             =for sig
1329              
1330             Signature: (img(m,n); double px(np,np); double py(np,np); [o] warp(m,n); { options })
1331              
1332             =for ref
1333              
1334             Warp a 2D image given a polynomial describing the I mapping.
1335              
1336             =for usage
1337              
1338             $out = warp2d( $img, $px, $py, { options } );
1339              
1340             Apply the polynomial transformation encoded in the C<$px> and
1341             C<$py> piddles to warp the input image C<$img> into the output
1342             image C<$out>.
1343              
1344             The format for the polynomial transformation is described in
1345             the documentation for the L routine.
1346              
1347             At each point C, the closest 16 pixel values are combined
1348             with an interpolation kernel to calculate the value at C.
1349             The interpolation is therefore done in the image, rather than
1350             Fourier, domain.
1351             By default, a C kernel is used, but this can be changed
1352             using the C option discussed below
1353             (the choice of kernel depends on the frequency content of the input image).
1354              
1355             The routine is based on the C command from
1356             the Eclipse data-reduction package - see http://www.eso.org/eclipse/ - and
1357             for further details on image resampling see
1358             Wolberg, G., "Digital Image Warping", 1990, IEEE Computer
1359             Society Press ISBN 0-8186-8944-7).
1360              
1361             Currently the output image is the same size as the input one,
1362             which means data will be lost if the transformation reduces
1363             the pixel scale. This will (hopefully) be changed soon.
1364              
1365             =for example
1366              
1367             $img = rvals(byte,501,501);
1368             imag $img, { JUSTIFY => 1 };
1369             #
1370             # use a not-particularly-obvious transformation:
1371             # x = -10 + 0.5 * $u - 0.1 * $v
1372             # y = -20 + $v - 0.002 * $u * $v
1373             #
1374             $px = pdl( [ -10, 0.5 ], [ -0.1, 0 ] );
1375             $py = pdl( [ -20, 0 ], [ 1, 0.002 ] );
1376             $wrp = warp2d( $img, $px, $py );
1377             #
1378             # see the warped image
1379             imag $warp, { JUSTIFY => 1 };
1380              
1381             The options are:
1382              
1383             =for options
1384              
1385             KERNEL - default value is tanh
1386             NOVAL - default value is 0
1387              
1388             C is used to specify which interpolation kernel to use
1389             (to see what these kernels look like, use the
1390             L routine).
1391             The options are:
1392              
1393             =over 4
1394              
1395             =item tanh
1396              
1397             Hyperbolic tangent: the approximation of an ideal box filter by the
1398             product of symmetric tanh functions.
1399              
1400             =item sinc
1401              
1402             For a correctly sampled signal, the ideal filter in the fourier domain is a rectangle,
1403             which produces a C interpolation kernel in the spatial domain:
1404              
1405             sinc(x) = sin(pi * x) / (pi * x)
1406              
1407             However, it is not ideal for the C<4x4> pixel region used here.
1408              
1409             =item sinc2
1410              
1411             This is the square of the sinc function.
1412              
1413             =item lanczos
1414              
1415             Although defined differently to the C kernel, the result is very
1416             similar in the spatial domain. The Lanczos function is defined as
1417              
1418             L(x) = sinc(x) * sinc(x/2) if abs(x) < 2
1419             = 0 otherwise
1420              
1421             =item hann
1422              
1423             This kernel is derived from the following function:
1424              
1425             H(x) = a + (1-a) * cos(2*pi*x/(N-1)) if abs(x) < 0.5*(N-1)
1426             = 0 otherwise
1427              
1428             with C and N currently equal to 2001.
1429              
1430             =item hamming
1431              
1432             This kernel uses the same C as the Hann filter, but with
1433             C.
1434              
1435             =back
1436              
1437             C gives the value used to indicate that a pixel in the
1438             output image does not map onto one in the input image.
1439              
1440             =cut
1441              
1442             # support routine
1443             {
1444             my %warp2d = map { ($_,1) } qw( tanh sinc sinc2 lanczos hamming hann );
1445              
1446             # note: convert to lower case
1447             sub _check_kernel ($$) {
1448 6     6   14 my $kernel = lc shift;
1449 6         11 my $code = shift;
1450             barf "Unknown kernel $kernel sent to $code\n" .
1451             "\tmust be one of [" . join(',',keys %warp2d) . "]\n"
1452 6 50       23 unless exists $warp2d{$kernel};
1453 6         15 return $kernel;
1454             }
1455             }
1456              
1457              
1458              
1459              
1460              
1461             sub PDL::warp2d {
1462 6     6 0 58 my $opts = PDL::Options->new( { KERNEL => "tanh", NOVAL => 0 } );
1463 6 50       28 $opts->options( pop(@_) ) if ref($_[$#_]) eq "HASH";
1464              
1465 6 50 33     30 die "Usage: warp2d( in(m,n), px(np,np); py(np,np); [o] out(m,n), {Options} )"
1466             if $#_<2 || $#_>3;
1467 6         12 my $img = shift;
1468 6         9 my $px = shift;
1469 6         11 my $py = shift;
1470 6 50       26 my $out = $#_ == -1 ? PDL->null() : shift;
1471              
1472             # safety checks
1473 6         18 my $copt = $opts->current();
1474 6         21 my $kernel = _check_kernel( $$copt{KERNEL}, "warp2d" );
1475              
1476 6         49042 &PDL::_warp2d_int( $img, $px, $py, $out, $kernel, $$copt{NOVAL} );
1477 6         81 return $out;
1478             }
1479              
1480              
1481              
1482             *warp2d = \&PDL::warp2d;
1483              
1484              
1485              
1486              
1487              
1488             =head2 warp2d_kernel
1489              
1490             =for ref
1491              
1492             Return the specified kernel, as used by L
1493              
1494             =for usage
1495              
1496             ( $x, $k ) = warp2d_kernel( $name )
1497              
1498             The valid values for C<$name> are the same as the C option
1499             of L.
1500              
1501             =for example
1502              
1503             line warp2d_kernel( "hamming" );
1504              
1505             =cut
1506              
1507              
1508              
1509              
1510              
1511             sub PDL::warp2d_kernel ($) {
1512 0     0 0   my $kernel = _check_kernel( shift, "warp2d_kernel" );
1513              
1514 0           my $nelem = _get_kernel_size();
1515 0           my $x = zeroes( $nelem );
1516 0           my $k = zeroes( $nelem );
1517              
1518 0           &PDL::_warp2d_kernel_int( $x, $k, $kernel );
1519 0           return ( $x, $k );
1520              
1521             # return _get_kernel( $kernel );
1522             }
1523             *warp2d_kernel = \&PDL::warp2d_kernel;
1524              
1525              
1526              
1527             *warp2d_kernel = \&PDL::warp2d_kernel;
1528              
1529              
1530              
1531             ;
1532              
1533              
1534             =head1 AUTHORS
1535              
1536             Copyright (C) Karl Glazebrook 1997 with additions by Robin Williams
1537             (rjrw@ast.leeds.ac.uk), Tim Jeness (timj@jach.hawaii.edu),
1538             and Doug Burke (burke@ifa.hawaii.edu).
1539              
1540             All rights reserved. There is no warranty. You are allowed
1541             to redistribute this software / documentation under certain
1542             conditions. For details, see the file COPYING in the PDL
1543             distribution. If this file is separated from the PDL distribution,
1544             the copyright notice should be included in the file.
1545              
1546             =cut
1547              
1548              
1549              
1550              
1551              
1552             # Exit with OK status
1553              
1554             1;
1555              
1556