package Astro::FITS::HdrTrans::JCMT_GSD;
=head1 NAME
Astro::FITS::HdrTrans::JCMT_GSD - JCMT GSD Header translations
=head1 DESCRIPTION
Converts information contained in JCMT heterodyne instrument headers
to and from generic headers. See Astro::FITS::HdrTrans for a list of
generic headers.
=cut
use 5.006;
use warnings;
use strict;
use Carp;
use Time::Piece;
# Inherit from Base
use base qw/ Astro::FITS::HdrTrans::Base /;
use vars qw/ $VERSION /;
$VERSION = "1.50";
# for a constant mapping, there is no FITS header, just a generic
# header that is constant
my %CONST_MAP = (
INST_DHS => 'HET_GSD',
COORDINATE_UNITS => 'decimal',
EQUINOX => 'current',
TELESCOPE => 'JCMT',
);
# NULL mappings used to override base class implementations
my @NULL_MAP = ();
# unit mapping implies that the value propogates directly
# to the output with only a keyword name change
my %UNIT_MAP = (
AMBIENT_TEMPERATURE => "C5AT",
APERTURE => "C7AP",
AZIMUTH_START => "C4AZ",
BACKEND => "C1BKE",
BACKEND_SECTIONS => "C3NRS",
CHOP_FREQUENCY => "C4FRQ",
CHOP_THROW => "C4THROW",
COORDINATE_SYSTEM => "C4CSC",
COORDINATE_TYPE => "C4LSC",
CYCLE_LENGTH => "C3CL",
# DEC_BASE => "",
ELEVATION_START => "C4EL",
# FILENAME => "GSDFILE",
FILTER => "C7FIL",
FREQUENCY_RESOLUTION => "C12FR",
FRONTEND => "C1RCV",
HUMIDITY => "C5RH",
NUMBER_OF_CYCLES => "C3NCI",
NUMBER_OF_SUBSCANS => "C3NIS",
OBJECT => "C1SNA1",
OBSERVATION_MODE => "C6ST",
OBSERVATION_NUMBER => "C1SNO",
PROJECT => "C1PID",
RA_BASE => "C4RADATE",
RECEIVER_TEMPERATURE => "C12RT",
ROTATION => "CELL_V2Y",
REST_FREQUENCY => "C12RF",
SEEING => "C7SEEING",
SWITCH_MODE => "C6MODE",
SYSTEM_TEMPERATURE => "C12SST",
TAU => "C7TAU225",
USER_AZ_CORRECTION => "UAZ",
USER_EL_CORRECTION => "UEL",
VELOCITY => "C7VR",
VELOCITY_REFERENCE_FRAME => "C12VREF",
VELOCITY_TYPE => "C12VDEF",
X_BASE => "C4RX",
Y_BASE => "C4RY",
X_DIM => "C6XNP",
Y_DIM => "C6YNP",
X_REQUESTED => "C4SX",
Y_REQUESTED => "C4SY",
X_SCALE => "C6DX",
Y_SCALE => "C6DY",
);
# Create the translation methods
__PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
=head1 METHODS
=over 4
=item B<can_translate>
Returns true if the supplied headers can be handled by this class.
$cando = $class->can_translate( \%hdrs );
For this class, the method will return true if the B<C1RCV> header exists
and matches the regular expression C</^rx(a|b|w)/i>.
=cut
sub can_translate {
my $self = shift;
my $headers = shift;
if ( exists( $headers->{'C1RCV'} ) &&
defined( $headers->{'C1RCV'} ) &&
( $headers->{'C1RCV'} =~ /^rx(a|b|w)/i ||
$headers->{'C1RCV'} =~ /^fts/i ) ) {
return 1;
} else {
return 0;
}
}
=back
=head1 COMPLEX CONVERSIONS
These methods are more complicated than a simple mapping. We have to
provide both from- and to-FITS conversions All these routines are
methods and the to_ routines all take a reference to a hash and return
the translated value (a many-to-one mapping) The from_ methods take a
reference to a generic hash and return a translated hash (sometimes
these are many-to-many)
=over 4
=item B<to_INSTRUMENT>
Sets the C<INSTRUMENT> generic header. For RxA3i, sets the value
to RXA3. For RxB, sets the value to RXB3.
=cut
sub to_INSTRUMENT {
my $self = shift;
my $FITS_headers = shift;
my $return;
if ( exists( $FITS_headers->{'C1RCV'} ) ) {
$return = $FITS_headers->{'C1RCV'};
if ( $return =~ /^rxa3/i ) {
$return = "RXA3";
} elsif ( $return =~ /^rxb/i ) {
$return = "RXB3";
}
}
return $return;
}
=item B<to_OBSERVATION_ID>
Calculate a unique Observation ID.
=cut
# Note this routine is generic for JCMT heterodyne instrumentation.
# Would be completely generic if BACKEND was not used in preference to instrument.
sub to_OBSERVATION_ID {
my $self = shift;
my $FITS_headers = shift;
my $backend = lc( $self->to_BACKEND( $FITS_headers ) );
my $obsnum = $self->to_OBSERVATION_NUMBER( $FITS_headers );
my $dateobs = $self->to_UTSTART( $FITS_headers );
my $datetime = $dateobs->datetime;
$datetime =~ s/-//g;
$datetime =~ s/://g;
my $obsid = join('_', $backend, $obsnum, $datetime);
return $obsid;
}
=item B<to_UTDATE>
Translates the C<C3DAT> header into a YYYYMMDD integer.
=cut
sub to_UTDATE {
my $self = shift;
my $FITS_headers = shift;
my $return;
if ( exists( $FITS_headers->{'C3DAT'} ) ) {
$FITS_headers->{'C3DAT'} =~ /(\d{4})\.(\d\d)(\d{1,2})/;
my $day = (length($3) == 2) ? $3 : $3 . "0";
my $ut = "$1-$2-$day";
$return = sprintf("%04d%02d%02d", $1, $2, $3);
}
return $return;
}
=item B<from_UTDATE>
Translates YYYYMMDD integer to C3DAT header.
=cut
sub from_UTDATE {
my $self = shift;
my $generic_headers = shift;
my %return_hash;
if (exists $generic_headers->{UTDATE}) {
my $date = $generic_headers->{UTDATE};
return () unless defined $date;
$return_hash{UTDATE} = sprintf("%04d.%02d%02d",
substr($date,0,4),
substr($date,4,2),
substr($date,6,2));
}
return %return_hash;
}
=item B<to_UTSTART>
Translates the C<C3DAT> and C<C3UT> headers into a C<Time::Piece> object.
=cut
sub to_UTSTART {
my $self = shift;
my $FITS_headers = shift;
my $return;
if ( exists( $FITS_headers->{'C3DAT'} ) && defined( $FITS_headers->{'C3DAT'} ) &&
exists( $FITS_headers->{'C3UT'} ) && defined( $FITS_headers->{'C3UT'} ) ) {
my $hour = int( $FITS_headers->{'C3UT'} );
my $minute = int ( ( $FITS_headers->{'C3UT'} - $hour ) * 60 );
my $second = int ( ( ( ( $FITS_headers->{'C3UT'} - $hour ) * 60 ) - $minute ) * 60 );
$FITS_headers->{'C3DAT'} =~ /(\d{4})\.(\d\d)(\d{1,2})/;
my $day = (length($3) == 2) ? $3 : $3 . "0";
$return = Time::Piece->strptime(sprintf("%4u-%02u-%02uT%02u:%02u:%02u", $1, $2, $day, $hour, $minute, $second ),
"%Y-%m-%dT%T");
}
return $return;
}
=item B<to_UTEND>
Translates the C<C3DAT>, C<C3UT>, C<C3NIS>, C<C3CL>, C<C3NCP>, and C<C3NCI> headers
into a C<Time::Piece> object.
=cut
sub to_UTEND {
my $self = shift;
my $FITS_headers = shift;
my ($t, $expt);
if ( exists( $FITS_headers->{'C3DAT'} ) && defined( $FITS_headers->{'C3DAT'} ) &&
exists( $FITS_headers->{'C3UT'} ) && defined( $FITS_headers->{'C3UT'} ) ) {
my $hour = int( $FITS_headers->{'C3UT'} );
my $minute = int ( ( $FITS_headers->{'C3UT'} - $hour ) * 60 );
my $second = int ( ( ( ( $FITS_headers->{'C3UT'} - $hour ) * 60 ) - $minute ) * 60 );
$FITS_headers->{'C3DAT'} =~ /(\d{4})\.(\d\d)(\d{1,2})/;
my $day = (length($3) == 2) ? $3 : $3 . "0";
$t = Time::Piece->strptime(sprintf("%4u-%02u-%02uT%02u:%02u:%02u", $1, $2, $day, $hour, $minute, $second ),
"%Y-%m-%dT%T");
}
$expt = $self->to_EXPOSURE_TIME( $FITS_headers );
$t += $expt;
return $t;
}
=item B<to_BANDWIDTH_MODE>
Uses the C3NRS (number of backend sections), C3NFOC (number of
frontend output channels) and C3NCH (number of channels) to form a
string that is of the format 250MHzx2048. To obtain this, the
bandwidth (250MHz in this example) is calculated as 125MHz * C3NRS /
C3NFOC. The number of channels is taken directly and not manipulated
in any way.
If appropriate, the bandwidth may be given in GHz.
=cut
sub to_BANDWIDTH_MODE {
my $self = shift;
my $FITS_headers = shift;
my $return;
if ( exists( $FITS_headers->{'C3NRS'} ) && defined( $FITS_headers->{'C3NRS'} ) &&
exists( $FITS_headers->{'C3NFOC'} ) && defined( $FITS_headers->{'C3NFOC'} ) &&
exists( $FITS_headers->{'C3NCH'} ) && defined( $FITS_headers->{'C3NCH'} ) ) {
my $bandwidth = 125 * $FITS_headers->{'C3NRS'} / $FITS_headers->{'C3NFOC'};
if ( $bandwidth >= 1000 ) {
$bandwidth /= 1000;
$return = sprintf( "%dGHzx%d", $bandwidth, $FITS_headers->{'C3NCH'} );
} else {
$return = sprintf( "%dMHzx%d", $bandwidth, $FITS_headers->{'C3NCH'} );
}
}
return $return;
}
=item B<to_EXPOSURE_TIME>
=cut
sub to_EXPOSURE_TIME {
my $self = shift;
my $FITS_headers = shift;
my $expt = 0;
if ( exists( $FITS_headers->{'C6ST'} ) && defined( $FITS_headers->{'C6ST'} ) ) {
my $c6st = uc( $FITS_headers->{'C6ST'} );
if ( $c6st eq 'RASTER' ) {
if ( exists( $FITS_headers->{'C3NSAMPL'} ) && defined( $FITS_headers->{'C3NSAMPL'} ) &&
exists( $FITS_headers->{'C3CL'} ) && defined( $FITS_headers->{'C3CL'} ) &&
exists( $FITS_headers->{'C3NPP'} ) && defined( $FITS_headers->{'C3NPP'} ) ) {
my $c3nsampl = $FITS_headers->{'C3NSAMPL'};
my $c3cl = $FITS_headers->{'C3CL'};
my $c3npp = $FITS_headers->{'C3NPP'};
# raster.
$expt = 15 + $c3nsampl * $c3cl * ( 1 + 1 / sqrt( $c3npp ) ) * 1.4;
}
} elsif ( $c6st eq 'PATTERN' or $c6st eq 'GRID' ) {
my $c6mode = '';
if ( exists( $FITS_headers->{'C6MODE'} ) && defined( $FITS_headers->{'C6MODE'} ) ) {
$c6mode = $FITS_headers->{'C6MODE'};
} else {
$c6mode = 'BEAMSWITCH';
}
if ( exists( $FITS_headers->{'C3NSAMPL'} ) && defined( $FITS_headers->{'C3NSAMPL'} ) &&
exists( $FITS_headers->{'C3NCYCLE'} ) && defined( $FITS_headers->{'C3NCYCLE'} ) &&
exists( $FITS_headers->{'C3CL'} ) && defined( $FITS_headers->{'C3CL'} ) ) {
my $c3nsampl = $FITS_headers->{'C3NSAMPL'};
my $c3ncycle = $FITS_headers->{'C3NCYCLE'};
my $c3cl = $FITS_headers->{'C3CL'};
if ( $c6mode eq 'POSITION_SWITCH' ) {
# position switch pattern/grid.
$expt = 6 + $c3nsampl * $c3ncycle * $c3cl * 1.35;
} elsif ( $c6mode eq 'BEAMSWITCH' ) {
# beam switch pattern/grid.
$expt = 6 + $c3nsampl * $c3ncycle * $c3cl * 1.35;
} elsif ( $c6mode eq 'CHOPPING' ) {
if ( exists( $FITS_headers->{'C1RCV'} ) && defined( $FITS_headers->{'C1RCV'} ) ) {
my $c1rcv = uc( $FITS_headers->{'C1RCV'} );
if ( $c1rcv eq 'RXA3I' ) {
# fast frequency switch pattern/grid, receiver A.
$expt = 15 + $c3nsampl * $c3ncycle * $c3cl * 1.20;
} elsif ( $c1rcv eq 'RXB' ) {
# slow frequency switch pattern/grid, receiver B.
$expt = 18 + $c3nsampl * $c3ncycle * $c3cl * 1.60;
}
}
}
}
} else {
my $c6mode;
if ( exists( $FITS_headers->{'C6MODE'} ) && defined( $FITS_headers->{'C6MODE'} ) ) {
$c6mode = $FITS_headers->{'C6MODE'};
} else {
$c6mode = 'BEAMSWITCH';
}
if ( exists( $FITS_headers->{'C3NSAMPL'} ) && defined( $FITS_headers->{'C3NSAMPL'} ) &&
exists( $FITS_headers->{'C3NCYCLE'} ) && defined( $FITS_headers->{'C3NCYCLE'} ) &&
exists( $FITS_headers->{'C3CL'} ) && defined( $FITS_headers->{'C3CL'} ) ) {
my $c3nsampl = $FITS_headers->{'C3NSAMPL'};
my $c3ncycle = $FITS_headers->{'C3NCYCLE'};
my $c3cl = $FITS_headers->{'C3CL'};
if ( $c6mode eq 'POSITION_SWITCH' ) {
# position switch sample.
$expt = 4.8 + $c3nsampl * $c3ncycle * $c3cl * 1.10;
} elsif ( $c6mode eq 'BEAMSWITCH' ) {
# beam switch sample.
$expt = 4.8 + $c3nsampl * $c3ncycle * $c3cl * 1.25;
} elsif ( $c6mode eq 'CHOPPING' ) {
if ( exists( $FITS_headers->{'C1RCV'} ) && defined( $FITS_headers->{'C1RCV'} ) ) {
my $c1rcv = uc( $FITS_headers->{'C1RCV'} );
if ( $c1rcv eq 'RXA3I' ) {
# fast frequency switch sample, receiver A.
$expt = 3 + $c3nsampl * $c3ncycle * $c3cl * 1.10;
} elsif ( $c1rcv eq 'RXB' ) {
# slow frequency switch sample, receiver B.
$expt = 3 + $c3nsampl * $c3ncycle * $c3cl * 1.40;
}
}
}
}
}
}
return $expt;
}
=item B<to_SYSTEM_VELOCITY>
Translate the C<C12VREF> and C<C12VDEF> headers into one combined header.
=cut
sub to_SYSTEM_VELOCITY {
my $self = shift;
my $FITS_headers = shift;
my $return;
if ( exists( $FITS_headers->{'C12VREF'} ) && defined( $FITS_headers->{'C12VREF'} ) &&
exists( $FITS_headers->{'C12VDEF'} ) && defined( $FITS_headers->{'C12VDEF'} ) ) {
$return = substr( $FITS_headers->{'C12VDEF'}, 0, 3 ) . substr( $FITS_headers->{'C12VREF'}, 0, 3 );
}
return $return;
}
=back
=head1 REVISION
$Id$
=head1 AUTHOR
Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>
=head1 COPYRIGHT
Copyright (C) 2008 Science and Technology Facilities Council.
Copyright (C) 2003-2007 Particle Physics and Astronomy Research Council.
All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful,but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place,Suite 330, Boston, MA 02111-1307, USA
=cut
1;