File Coverage

blib/lib/Astro/FITS/HdrTrans/INGRID.pm
Criterion Covered Total %
statement 18 216 8.3
branch 0 54 0.0
condition 0 27 0.0
subroutine 7 37 18.9
pod 30 31 96.7
total 55 365 15.0


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Astro::FITS::HdrTrans::INGRID - WHT INGRID translations
5              
6             =head1 SYNOPSIS
7              
8             use Astro::FITS::HdrTrans::INGRID;
9              
10             %gen = Astro::FITS::HdrTrans::INGRID->translate_from_FITS( %hdr );
11              
12             =head1 DESCRIPTION
13              
14             This class provides a generic set of translations that are specific to
15             the INGRID camera of the William Herschel Telescope.
16              
17             =cut
18              
19             use 5.006;
20 10     10   8569477 use warnings;
  10         32  
21 10     10   49 use strict;
  10         20  
  10         261  
22 10     10   54 use Carp;
  10         14  
  10         218  
23 10     10   48  
  10         19  
  10         659  
24             # Inherit from FITS.
25             use base qw/ Astro::FITS::HdrTrans::FITS /;
26 10     10   59  
  10         17  
  10         1349  
27             use vars qw/ $VERSION /;
28 10     10   55  
  10         16  
  10         17544  
29             $VERSION = "1.65";
30              
31             # For a constant mapping, there is no FITS header, just a generic
32             # header that is constant.
33             my %CONST_MAP = (
34             POLARIMETRY => 0,
35             OBSERVATION_MODE => 'imaging',
36             WAVEPLATE_ANGLE => 0,
37             );
38              
39             # NULL mappings used to override base-class implementations.
40             my @NULL_MAP = qw/ /;
41              
42             # Unit mapping implies that the value propogates directly
43             # to the output with only a keyword name change.
44              
45             my %UNIT_MAP = (
46             AIRMASS_END => "AIRMASS",
47             AIRMASS_START => "AIRMASS",
48             EXPOSURE_TIME => "EXPTIME",
49             FILTER => "INGF1NAM",
50             INSTRUMENT => "DETECTOR",
51             NUMBER_OF_EXPOSURES => "COAVERAG",
52             NUMBER_OF_READS => "NUMREADS",
53             OBSERVATION_NUMBER => "RUN"
54             );
55              
56              
57             # Create the translation methods.
58             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
59              
60             =head1 METHODS
61              
62             =over 4
63              
64             =item B<this_instrument>
65              
66             The name of the instrument required to match (case insensitively)
67             against the INSTRUME/INSTRUMENT keyword to allow this class to
68             translate the specified headers. Called by the default
69             C<can_translate> method.
70              
71             $inst = $class->this_instrument();
72              
73             Returns "INGRID".
74              
75             =cut
76              
77             return qr/^INGRID/;
78             }
79 20     20 1 91  
80             =back
81              
82             =head1 COMPLEX CONVERSIONS
83              
84             =over 4
85              
86             =item B<to_DEC_BASE>
87              
88             Converts the base declination from sexagesimal d:m:s to decimal
89             degrees using the C<CAT-DEC> keyword, defaulting to 0.0.
90              
91             =cut
92              
93             my $self = shift;
94             my $FITS_headers = shift;
95             my $dec = 0.0;
96 0     0 1   my $sexa = $FITS_headers->{"CAT-DEC"};
97 0           if ( defined( $sexa ) ) {
98 0           $dec = $self->dms_to_degrees( $sexa );
99 0           }
100 0 0         return $dec;
101 0           }
102              
103 0           =item B<to_DEC_SCALE>
104              
105             Sets the declination scale in arcseconds per pixel. The C<CCDYPIXE>
106             and C<INGPSCAL> headers are used when both are defined. Otherwise it
107             returns a default value of 0.2387 arcsec/pixel, assuming north is up.
108              
109             =cut
110              
111             my $self = shift;
112             my $FITS_headers = shift;
113             my $decscale = 0.2387;
114              
115 0     0 1   # Assumes either x-y scales the same or the y corresponds to
116 0           # declination.
117 0           my $ccdypixe = $self->via_subheader( $FITS_headers, "CCDYPIXE" );
118             my $ingpscal = $self->via_subheader( $FITS_headers, "INGPSCAL" );
119             if ( defined $ccdypixe && defined $ingpscal ) {
120             $decscale = $ccdypixe * 1000.0 * $ingpscal;
121 0           }
122 0           return $decscale;
123 0 0 0       }
124 0            
125             =item B<to_DEC_TELESCOPE_OFFSET>
126 0            
127             Sets the declination telescope offset in arcseconds. It uses the
128             C<CAT-DEC> and C<DEC> keywords to derive the offset, and if either
129             does not exist, it returns a default of 0.0.
130              
131             =cut
132              
133             my $self = shift;
134             my $FITS_headers = shift;
135             my $decoffset = 0.0;
136             if ( exists $FITS_headers->{"CAT-DEC"} && exists $FITS_headers->{DEC} ) {
137              
138 0     0 1   # Obtain the reference and telescope declinations positions measured in degrees.
139 0           my $refdec = $self->dms_to_degrees( $FITS_headers->{"CAT-DEC"} );
140 0           my $dec = $self->dms_to_degrees( $FITS_headers->{DEC} );
141 0 0 0        
142             # Find the offsets between the positions in arcseconds on the sky.
143             $decoffset = 3600.0 * ( $dec - $refdec );
144 0           }
145 0            
146             # The sense is reversed compared with UKIRT, as these measure the
147             # place son the sky, not the motion of the telescope.
148 0           return -1.0 * $decoffset
149             }
150              
151             =item B<to_DETECTOR_READ_TYPE>
152              
153 0           Returns the UKIRT-like detector type "STARE" or "NDSTARE" from the
154             FITS C<REDMODE> and C<NUMREADS> keywords.
155              
156             This is guesswork at present.
157              
158             =cut
159              
160             my $self = shift;
161             my $FITS_headers = shift;
162             my $read_type;
163             my $readout_mode = $FITS_headers->{READMODE};
164             my $nreads = $FITS_headers->{NUMREADS};
165             if ( $readout_mode =~ /^mndr/i ||
166 0     0 1   ( $readout_mode =~ /^cds/i && $nreads == 1 ) ) {
167 0           $read_type = "STARE";
168 0           } elsif ( $readout_mode =~ /^cds/i ) {
169 0           $read_type = "NDSTARE";
170 0           }
171 0 0 0       return $read_type;
    0 0        
172             }
173 0            
174             =item B<to_DR_RECIPE>
175 0            
176             Returns the data-reduction recipe name. The selection depends on the
177 0           values of the C<OBJECT> and C<OBSTYPE> keywords. The default is
178             "QUICK_LOOK". A dark returns "REDUCE_DARK", and an object's recipe is
179             "JITTER_SELF_FLAT".
180              
181             =cut
182              
183             # No clue what the recipe is apart for a dark and assume a dither
184             # pattern means JITTER_SELF_FLAT.
185             my $self = shift;
186             my $FITS_headers = shift;
187             my $recipe = "QUICK_LOOK";
188              
189             # Look for a dither pattern. These begin D-<n>/<m>: where
190             # <m> represents the number of jitter positions in the group
191             # and <n> is the number within the group.
192 0     0 1   my $object = $FITS_headers->{OBJECT};
193 0           if ( $object =~ /D-\d+\/\d+/ ) {
194 0           $recipe = "JITTER_SELF_FLAT";
195             } elsif ( $FITS_headers->{OBSTYPE} =~ /DARK/i ) {
196             $recipe = "REDUCE_DARK";
197             }
198              
199 0           return $recipe;
200 0 0         }
    0          
201 0            
202             =item B<to_EQUINOX>
203 0            
204             Returns the equinox in decimal years. It's taken from the C<CAT-EQUI>
205             keyword, if it exists, defaulting to 2000.0 otherwise.
206 0            
207             =cut
208              
209             my $self = shift;
210             my $FITS_headers = shift;
211             my $equinox = 2000.0;
212             if ( exists $FITS_headers->{"CAT-EQUI"} ) {
213             $equinox = $FITS_headers->{"CAT-EQUI"};
214             $equinox =~ s/[BJ]//;
215             }
216             return $equinox;
217 0     0 1   }
218 0            
219 0           =item B<to_GAIN>
220 0 0          
221 0           Returns the gain in electrons per data number. This is taken from
222 0           the C<GAIN> keyword, with a default of 4.1.
223              
224 0           =cut
225              
226             my $self = shift;
227             my $FITS_headers = shift;
228             my $gain = 4.1;
229             my $subval = $self->via_subheader( $FITS_headers, "GAIN" );
230             $gain = $subval if defined $subval;
231             return $gain;
232             }
233              
234             =item B<to_NUMBER_OF_OFFSETS>
235 0     0 1    
236 0           Returns the number of offsets. It uses the UKIRT convention so
237 0           it is equivalent to the number of dither positions plus one.
238 0           The value is derived from the C<OBJECT> keyword, with a default of 6.
239 0 0          
240 0           =cut
241              
242             my $self = shift;
243             my $FITS_headers = shift;
244             my $noffsets = 5;
245              
246             # Look for a dither pattern. These begin D-<n>/<m>: where
247             # <m> represents the number of jitter positions in the group
248             # and <n> is the number within the group.
249             my $object = $FITS_headers->{OBJECT};
250             if ( $object =~ /D-\d+\/\d+/ ) {
251              
252 0     0 1   # Extract the string between the solidus and the colon. Add one
253 0           # to match the UKIRT convention.
254 0           $noffsets = substr( $object, index( $object, "/" ) + 1 );
255             $noffsets = substr( $noffsets, 0, index( $noffsets, ":" ) );
256             }
257             return $noffsets + 1;
258             }
259 0            
260 0 0         =item B<to_OBJECT>
261              
262             Reeturns the object name. It is extracted from the C<OBJECT> keyword.
263              
264 0           =cut
265 0            
266             my $self = shift;
267 0           my $FITS_headers = shift;
268             my $object = $FITS_headers->{OBJECT};
269              
270             # Look for a dither pattern. These begin D-<n>/<m>: where
271             # <m> represents the number of jitter positions in the group
272             # and <n> is the number within the group. We want to extract
273             # the actual object name.
274             if ( $object =~ /D-\d+\/\d+/ ) {
275             $object = substr( $object, index( $object, ":" ) + 2 );
276             }
277 0     0 1   return $object;
278 0           }
279 0            
280             =item B<to_OBSERVATION_TYPE>
281              
282             Determines the observation type from the C<OBSTYPE> keyword provided it is
283             "TARGET" for an object dark frame.
284              
285 0 0         =cut
286 0            
287             my $self = shift;
288 0           my $FITS_headers = shift;
289             my $obstype = uc( $FITS_headers->{OBSTYPE} );
290             if ( $obstype eq "TARGET" ) {
291             $obstype = "OBJECT";
292             }
293             return $obstype;
294             }
295              
296             =item B<to_RA_BASE>
297              
298             Converts the base right ascension from sexagesimal h:m:s to decimal degrees
299 0     0 1   using the C<CAT-RA> keyword, defaulting to 0.0.
300 0            
301 0           =cut
302 0 0          
303 0           my $self = shift;
304             my $FITS_headers = shift;
305 0           my $ra = 0.0;
306             my $sexa = $FITS_headers->{"CAT-RA"};
307             if ( defined( $sexa ) ) {
308             $ra = $self->hms_to_degrees( $sexa );
309             }
310             return $ra;
311             }
312              
313             =item B<to_RA_SCALE>
314              
315             Sets the right-ascension scale in arcseconds per pixel. The C<CCDXPIXE>
316 0     0 1   and C<INGPSCAL> headers are used when both are defined. Otherwise it
317 0           returns a default value of 0.2387 arcsec/pixel, assuming east is to
318 0           the left.
319 0            
320 0 0         =cut
321 0            
322             my $self = shift;
323 0           my $FITS_headers = shift;
324             my $rascale = -0.2387;
325              
326             # Assumes either x-y scales the same or the x corresponds to right
327             # ascension, and right ascension decrements with increasing x.
328             my $ccdxpixe = $self->via_subheader( $FITS_headers, "CCDXPIXE" );
329             my $ingpscal = $self->via_subheader( $FITS_headers, "INGPSCAL" );
330             if ( defined $ccdxpixe && defined $ingpscal ) {
331             $rascale = $ccdxpixe * -1000.0 * $ingpscal;
332             }
333             return $rascale;
334             }
335              
336 0     0 1   =item B<to_RA_TELESCOPE_OFFSET>
337 0            
338 0           Sets the right-ascension telescope offset in arcseconds. It uses the
339             C<CAT-RA>, C<RA>, C<CAT-DEC> keywords to derive the offset, and if any
340             of these keywords does not exist, it returns a default of 0.0.
341              
342 0           =cut
343 0            
344 0 0 0       my $self = shift;
345 0           my $FITS_headers = shift;
346             my $raoffset = 0.0;
347 0            
348             if ( exists $FITS_headers->{"CAT-DEC"} &&
349             exists $FITS_headers->{"CAT-RA"} && exists $FITS_headers->{RA} ) {
350              
351             # Obtain the reference and telescope sky positions measured in degrees.
352             my $refra = $self->hms_to_degrees( $FITS_headers->{"CAT-RA"} );
353             my $ra = $self->hms_to_degrees( $FITS_headers->{RA} );
354             my $refdec = $self->dms_to_degrees( $FITS_headers->{"CAT-DEC"} );
355              
356             # Find the offset between the positions in arcseconds on the sky.
357             $raoffset = 3600.0 * ( $ra - $refra ) * $self->cosdeg( $refdec );
358             }
359 0     0 1    
360 0           # The sense is reversed compared with UKIRT, as these measure the
361 0           # place son the sky, not the motion of the telescope.
362             return -1.0 * $raoffset;
363 0 0 0       }
      0        
364              
365             =item B<to_ROTATION>
366              
367 0           Returns the orientation of the detector in degrees anticlockwise
368 0           from north via east.
369 0            
370             =cut
371              
372 0           my $self = shift;
373             my $FITS_headers = shift;
374             return $self->rotation( $FITS_headers );
375             }
376              
377 0           =item B<to_SPEED_GAIN>
378              
379             Returns the speed gain. This is either "Normal" or "HiGain", the
380             selection depending on the value of the C<CCDSPEED> keyword.
381              
382             =cut
383              
384             # Fixed values for the gain depend on the camera (SW or LW), and for LW
385             # the readout mode.
386             my $self = shift;
387             my $FITS_headers = shift;
388 0     0 1   my $spd_gain;
389 0           my $speed = $FITS_headers->{CCDSPEED};
390 0           if ( $speed =~ /SLOW/ ) {
391             $spd_gain = "Normal";
392             } else {
393             $spd_gain = "HiGain";
394             }
395             return $spd_gain;
396             }
397              
398             =item B<to_STANDARD>
399              
400             Returns whether or not the observation is of a standard source. It is
401             deemed to be a standard when the C<OBSTYPE> keyword is "STANDARD".
402              
403 0     0 1   =cut
404 0            
405 0           my $self = shift;
406 0           my $FITS_headers = shift;
407 0 0         my $standard = 0;
408 0           my $type = $FITS_headers->{OBSTYPE};
409             if ( uc( $type ) eq "STANDARD" ) {
410 0           $standard = 1;
411             }
412 0           return $standard;
413             }
414              
415             =item B<to_UTDATE>
416              
417             Returns the UT date as C<Time::Piece> object. It copes with non-standard
418             format in C<DATE-OBS>.
419              
420             =cut
421              
422             my $self = shift;
423 0     0 1   my $FITS_headers = shift;
424 0           return $self->get_UT_date( $FITS_headers );
425 0           }
426 0            
427 0 0         =item B<to_UTEND>
428 0            
429             Returns the UT time of the end of the observation as a C<Time::Piece> object.
430 0            
431             =cut
432              
433             my $self = shift;
434             my $FITS_headers = shift;
435              
436             # This is the approximate end UT.
437             my $start = $self->to_UTSTART( $FITS_headers );
438             return $self->_add_seconds( $start, $FITS_headers->{EXPTIME} );
439             }
440              
441 0     0 1   =item B<to_UTSTART>
442 0            
443 0           Returns an estimated UT time of the start of the observation as a
444             C<Time::Piece> object. The start time is derived from the C<DATE-OBS>
445             keyword and if C<DATE-OBS> only supplies a date, the time from the
446             C<UTSTART> keyword is appended before conversaion to a C<Time::Piece>
447             object.
448              
449             =cut
450              
451             my $self = shift;
452             my $FITS_headers = shift;
453 0     0 1   my $return;
454 0           if ( exists $FITS_headers->{'DATE-OBS'} ) {
455             my $iso;
456             if ( $FITS_headers->{'DATE-OBS'} =~ /T/ ) {
457 0           # standard format
458 0           $iso = $FITS_headers->{'DATE-OBS'};
459             } elsif ( exists $FITS_headers->{UTSTART} ) {
460             $iso = $FITS_headers->{'DATE-OBS'}. "T" . $FITS_headers->{UTSTART};
461             }
462             $return = $self->_parse_iso_date( $iso ) if $iso;
463             }
464             return $return;
465             }
466              
467             =item B<to_X_LOWER_BOUND>
468              
469             Returns the lower bound along the X-axis of the area of the detector
470             as a pixel index.
471              
472 0     0 1   =cut
473 0            
474 0           my $self = shift;
475 0 0         my $FITS_headers = shift;
476 0           my @bounds = $self->getbounds( $FITS_headers );
477 0 0         return $bounds[ 0 ];
    0          
478             }
479 0            
480             =item B<to_X_REFERENCE_PIXEL>
481 0            
482             Specifies the X-axis reference pixel near the frame centre. It uses
483 0 0         the nominal reference pixel if that is correctly supplied, failing
484             that it takes the average of the bounds, and if these headers are also
485 0           absent, it uses a default which assumes the full array.
486              
487             =cut
488              
489             my $self = shift;
490             my $FITS_headers = shift;
491             my $xref;
492             my @bounds = $self->getbounds( $FITS_headers );
493             if ( $bounds[ 0 ] > 1 || $bounds[ 1 ] < 1024 ) {
494             $xref = nint( ( $bounds[ 0 ] + $bounds[ 1 ] ) / 2 );
495             } else {
496 0     0 1   $xref = 512;
497 0           }
498 0           return $xref;
499 0           }
500              
501             =item B<to_X_UPPER_BOUND>
502              
503             Returns the upper bound along the X-axis of the area of the detector
504             as a pixel index.
505              
506             =cut
507              
508             my $self = shift;
509             my $FITS_headers = shift;
510             my @bounds = $self->getbounds( $FITS_headers );
511             return $bounds[ 1 ];
512 0     0 1   }
513 0            
514 0           =item B<to_Y_LOWER_BOUND>
515 0            
516 0 0 0       Returns the lower bound along the Y-axis of the area of the detector
517 0           as a pixel index.
518              
519 0           =cut
520              
521 0           my $self = shift;
522             my $FITS_headers = shift;
523             my @bounds = $self->getbounds( $FITS_headers );
524             return $bounds[ 2 ];
525             }
526              
527             =item B<to_Y_REFERENCE_PIXEL>
528              
529             Specifies the Y-axis reference pixel near the frame centre. It uses
530             the nominal reference pixel if that is correctly supplied, failing
531             that it takes the average of the bounds, and if these headers are also
532 0     0 1   absent, it uses a default which assumes the full array.
533 0            
534 0           =cut
535 0            
536             my $self = shift;
537             my $FITS_headers = shift;
538             my $yref;
539             my @bounds = $self->getbounds( $FITS_headers );
540             if ( $bounds[ 2 ] > 1 || $bounds[ 3 ] < 1024 ) {
541             $yref = nint( ( $bounds[ 2 ] + $bounds[ 3 ] ) / 2 );
542             } else {
543             $yref = 512;
544             }
545             return $yref;
546 0     0 1   }
547 0            
548 0           =item B<to_Y_UPPER_BOUND>
549 0            
550             Returns the upper bound along the Y-axis of the area of the detector
551             as a pixel index.
552              
553             =cut
554              
555             my $self = shift;
556             my $FITS_headers = shift;
557             my @bounds = $self->getbounds( $FITS_headers );
558             return $bounds[ 3 ];
559             }
560              
561             =back
562 0     0 1    
563 0           # Supplementary methods for the translations
564 0           # ------------------------------------------
565 0            
566 0 0 0       =head1 HELPER ROUTINES
567 0            
568             These are INGRID-specific helper routines.
569 0            
570             =over 4
571 0            
572             =item B<dms_to_degrees>
573              
574             Converts a sky angle specified in d:m:s format into decimal degrees.
575             The argument is the sexagesimal-format angle.
576              
577             =cut
578              
579             my $self = shift;
580             my $sexa = shift;
581             my $dms;
582 0     0 1   if ( defined( $sexa ) ) {
583 0           my @pos = split( /:/, $sexa );
584 0           $dms = $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600.;
585 0           }
586             return $dms;
587             }
588              
589             # Obtain the detector bounds from a section in [xl:xu,yl:yu] syntax.
590             # If the RTDATSEC header is absent, use a default which corresponds
591             # to the full array.
592             my $self = shift;
593             my $FITS_headers = shift;
594             my @bounds = ( 1, 1024, 1, 1024 );
595             if ( exists $FITS_headers->{RTDATSEC} ) {
596             my $section = $FITS_headers->{RTDATSEC};
597             $section =~ s/\[//;
598             $section =~ s/\]//;
599             $section =~ s/,/:/g;
600             @bounds = split( /:/, $section );
601             }
602             return @bounds;
603             }
604              
605             =item B<get_UT_date>
606              
607 0     0 1   Returns the UT date in YYYYMMDD format. It parses the non-standard
608 0           ddMmmyy C<DATE-OBS> keyword.
609 0            
610 0 0         =cut
611 0            
612 0           my $self = shift;
613             my $FITS_headers = shift;
614 0            
615             # This is UT start and time.
616             my $dateobs = $FITS_headers->{"DATE-OBS"};
617              
618             # Extract out the data in yyyymmdd format.
619             return substr( $dateobs, 0, 4 ) . substr( $dateobs, 5, 2 ) . substr( $dateobs, 8, 2 )
620             }
621 0     0 0    
622 0           =item B<hms_to_degrees>
623 0            
624 0 0         Converts a sky angle specified in h:m:s format into decimal degrees.
625 0           It takes no account of latitude. The argument is the sexagesimal
626 0           format angle.
627 0            
628 0           =cut
629 0            
630             my $self = shift;
631 0           my $sexa = shift;
632             my $hms;
633             if ( defined( $sexa ) ) {
634             my @pos = split( /:/, $sexa );
635             $hms = 15.0 * ( $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600. );
636             }
637             return $hms;
638             }
639              
640             =item B<rotation>
641              
642 0     0 1   Derives the rotation angle in degrees from the C<ROTSKYPA> keyword, with a
643 0           default of 0.0.
644              
645             =cut
646 0            
647             my $self = shift;
648             my $FITS_headers = shift;
649 0           my $rotangle = 0.0;
650              
651             if ( exists $FITS_headers->{ROTSKYPA} ) {
652             $rotangle = $FITS_headers->{ROTSKYPA};
653             }
654             return $rotangle;
655             }
656              
657             =back
658              
659             =head1 SEE ALSO
660              
661 0     0 1   C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>.
662 0            
663 0           =head1 AUTHOR
664 0 0          
665 0           Malcolm J. Currie E<lt>mjc@star.rl.ac.ukE<gt>
666 0           Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
667             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
668 0            
669             =head1 COPYRIGHT
670              
671             Copyright (C) 2008 Science and Technology Facilities Council.
672             Copyright (C) 2003-2005 Particle Physics and Astronomy Research Council.
673             All Rights Reserved.
674              
675             This program is free software; you can redistribute it and/or modify it under
676             the terms of the GNU General Public License as published by the Free Software
677             Foundation; either Version 2 of the License, or (at your option) any later
678             version.
679 0     0 1    
680 0           This program is distributed in the hope that it will be useful,but WITHOUT ANY
681 0           WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
682             PARTICULAR PURPOSE. See the GNU General Public License for more details.
683 0 0          
684 0           You should have received a copy of the GNU General Public License along with
685             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
686 0           Place, Suite 330, Boston, MA 02111-1307, USA.
687              
688             =cut
689              
690             1;