Astro::FITS::HdrTrans::ESO - Base class for translation of ESO instruments


Astro-FITS-HdrTrans documentation Contained in the Astro-FITS-HdrTrans distribution.

Index


Code Index:

NAME

Top

Astro::FITS::HdrTrans::ESO - Base class for translation of ESO instruments

SYNOPSIS

Top

  use Astro::FITS::HdrTrans::ESO;

DESCRIPTION

Top

This class provides a generic set of translations that are common to instrumentation from the European Southern Observatory. It should not be used directly for translation of instrument FITS headers.

COMPLEX CONVERSIONS

Top

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)

to_ROTATION

Derives the rotation angle from the rotation matrix.

REVISION

Top

$Id: ESO.pm 14385 2007-08-27 22:01:06Z timj $

SEE ALSO

Top

Astro::FITS::HdrTrans, Astro::FITS::HdrTrans::Base.

AUTHOR

Top

Malcolm J. Currie <mjc@star.rl.ac.uk> Tim Jenness <t.jenness@jach.hawaii.edu>

COPYRIGHT

Top


Astro-FITS-HdrTrans documentation Contained in the Astro-FITS-HdrTrans distribution.
package Astro::FITS::HdrTrans::ESO;

use 5.006;
use warnings;
use strict;
use Carp;

# Inherit from the Base translation class and not HdrTrans itself
# (which is just a class-less wrapper).

use base qw/ Astro::FITS::HdrTrans::FITS /;

use Astro::FITS::HdrTrans::FITS;

use vars qw/ $VERSION /;

$VERSION = "1.50";

# in each class we have three sets of data.
#   - constant mappings
#   - unit mappings
#   - complex mappings

# For a constant mapping, there is no FITS header, just a generic
# header that is constant.
my %CONST_MAP = (
                 SCAN_INCREMENT      => 1,
                 NSCAN_POSITIONS     => 1,
                );

# Unit mapping implies that the value propagates directly
# to the output with only a keyword name change.

my %UNIT_MAP = (
                DEC_SCALE            => "CDELT1",
                RA_SCALE             => "CDELT2",

                # then the spectroscopy...
                SLIT_NAME            => "HIERARCH.ESO.INS.OPTI1.ID",
                X_DIM                => "HIERARCH.ESO.DET.WIN.NX",
                Y_DIM                => "HIERARCH.ESO.DET.WIN.NY",

                # then the general.
                CHOP_ANGLE           => "HIERARCH.ESO.SEQ.CHOP.POSANGLE",
                CHOP_THROW           => "HIERARCH.ESO.SEQ.CHOP.THROW",
                EXPOSURE_TIME        => "EXPTIME",
                NUMBER_OF_EXPOSURES  => "HIERARCH.ESO.DET.NDIT",
                NUMBER_OF_READS      => "HIERARCH.ESO.DET.NCORRS",
                OBSERVATION_NUMBER   => "OBSNUM",
               );

# Create the translation methods.
__PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP );

sub to_AIRMASS_END {
  my $self = shift;
  my $FITS_headers = shift;
  my $end_airmass = 1.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.END"} ) {
    $end_airmass = $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.END"};
  } elsif ( exists $FITS_headers->{AIRMASS} ) {
    $end_airmass = $FITS_headers->{AIRMASS};
  }
  return $end_airmass;
}

sub from_AIRMASS_END {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.TEL.AIRM.END", $generic_headers->{ "AIRMASS_END" };
}

sub to_AIRMASS_START {
  my $self = shift;
  my $FITS_headers = shift;
  my $start_airmass = 1.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.START"} ) {
    $start_airmass = $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.START"};
  } elsif ( exists $FITS_headers->{AIRMASS} ) {
    $start_airmass = $FITS_headers->{AIRMASS};
  }
  return $start_airmass;
}

sub from_AIRMASS_START {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.TEL.AIRM.START", $generic_headers->{ "AIRMASS_START" };
}

sub to_CONFIGURATION_INDEX {
  my $self = shift;
  my $FITS_headers = shift;
  my $instindex = 0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ENC"} ) {
    $instindex = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ENC"};
  }
  return $instindex;
}

sub to_DEC_BASE {
  my $self = shift;
  my $FITS_headers = shift;
  my $dec = 0.0;
  if ( exists ( $FITS_headers->{DEC} ) ) {
    $dec = $FITS_headers->{DEC};
  }
  $dec = defined( $dec ) ? $dec: 0.0;
  return $dec;
}

# This is guesswork at present.  It's rather tied to the UKIRT names
# and we need generic names or use instrument-specific values in
# instrument-specific primitives, and pass the actual value for the
# night log.  Could do with separate CHOPPING, BIAS booleans
# to indicate whether or not chopping is enabled and whether or not the
# detector mode needs a bias removed, like UKIRT's STARE mode.
sub to_DETECTOR_READ_TYPE {
  my $self = shift;
  my $FITS_headers = shift;
  my $read_type;
  my $chop = $FITS_headers->{"HIERARCH.ESO.TEL.CHOP.ST"};
  $chop = defined( $chop ) ? $chop : 0;
  my $detector_mode = exists( $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} ) ?
    $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} : "NDSTARE";
  if ( $detector_mode =~ /Uncorr/ ) {
    if ( $chop ) {
      $read_type = "CHOP";
    } else {
      $read_type = "STARE";
    }
  } else {
    if ( $chop ) {
      $read_type = "NDCHOP";
    } else {
      $read_type = "NDSTARE";
    }
  }
  return $read_type;
}

# Equinox may be absent for calibrations such as darks.
sub to_EQUINOX {
  my $self = shift;
  my $FITS_headers = shift;
  my $equinox = 0;
  if ( exists $FITS_headers->{EQUINOX} ) {
    $equinox = $FITS_headers->{EQUINOX};
  }
  return $equinox;
}

sub to_GRATING_NAME{
  my $self = shift;
  my $FITS_headers = shift;
  my $name = "UNKNOWN";
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"} ) {
    $name = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"};
  }
  return $name;
}

sub to_GRATING_ORDER{
  my $self = shift;
  my $FITS_headers = shift;
  my $order = 1;
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"} ) {
    $order = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"};
  }
  return $order;
}

sub to_GRATING_WAVELENGTH{
  my $self = shift;
  my $FITS_headers = shift;
  my $wavelength = 0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.WLEN"} ) {
    $wavelength = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.WLEN"};
  }
  return $wavelength;
}

sub to_NUMBER_OF_OFFSETS {
  my $self = shift;
  my $FITS_headers = shift;
  return $FITS_headers->{"HIERARCH.ESO.TPL.NEXP"} + 1;
}

sub from_NUMBER_OF_OFFSETS {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.TPL.NEXP",  $generic_headers->{ "NUMBER_OF_OFFSETS" } - 1;
}

sub to_OBSERVATION_MODE {
  my $self = shift;
  my $FITS_headers = shift;
  return $self->get_instrument_mode($FITS_headers);
}

sub from_OBSERVATION_MODE {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.DPR.TECH",  $generic_headers->{ "OBSERVATION_MODE" };
}

# OBJECT, SKY, and DARK need no change.
sub to_OBSERVATION_TYPE {
  my $self = shift;
  my $FITS_headers = shift;
  my $type = $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"};
  $type = exists( $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"} ) ? uc( $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"} ) : "OBJECT";
  if ( $type eq "STD" ) {
    $type = "OBJECT";
  } elsif ( $type eq "SKY,FLAT" || $type eq "FLAT,SKY" ) {
    $type = "SKY";
  } elsif ( $type eq "LAMP,FLAT" || $type eq "FLAT,LAMP" ) {
    $type = "LAMP";
  } elsif ( $type eq "LAMP" || $type eq "WAVE,LAMP" ) {
    $type = "ARC";
  }
  return $type;
}

sub from_OBSERVATION_TYPE {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.DPR.TYPE",  $generic_headers->{ "OBSERVATION_TYPE" };
}

# Cater for OBJECT keyword with unhelpful value.
sub to_OBJECT {
  my $self = shift;
  my $FITS_headers = shift;
  my $object = undef;

  # The object name should be in OBJECT...
  if ( exists $FITS_headers->{OBJECT} ) {
    $object = $FITS_headers->{OBJECT};

    # Sometimes it's the generic STD for standard.
    if ( $object =~ /STD/ ) {
      if ( exists $FITS_headers->{"HIERARCH.ESO.OBS.TARG.NAME"} ) {
        $object = $FITS_headers->{"HIERARCH.ESO.OBS.TARG.NAME"};
      } else {
        $object = undef;
      }
    }
  }
  return $object;
}

sub to_RA_BASE {
  my $self = shift;
  my $FITS_headers = shift;
  my $ra = 0.0;
  if ( exists ( $FITS_headers->{RA} ) ) {
    $ra = $FITS_headers->{RA};
  }
  $ra = defined( $ra ) ? $ra: 0.0;
  return $ra;
}

sub to_ROTATION {
  my $self = shift;
  my $FITS_headers = shift;
  return $self->rotation( $FITS_headers );
}

sub to_SLIT_ANGLE {
  my $self = shift;
  my $FITS_headers = shift;
  my $slitangle = 0.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
    $slitangle =  $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
  }
  return $slitangle;
}

sub from_SLIT_ANGLE {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.ADA.POSANG",  $generic_headers->{ "SLIT_ANGLE" };
}

sub to_STANDARD {
  my $self = shift;
  my $FITS_headers = shift;
  my $standard = 0;
  my $type = $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"};
  if ( uc( $type ) =~ /STD/ ) {
    $standard = 1;
  }
  return $standard;
}

sub from_STANDARD {
  my $self = shift;
  my $generic_headers = shift;
  "STANDARD",  $generic_headers->{ "STANDARD" };
}

sub to_UTDATE {
  my $self = shift;
  my $FITS_headers = shift;
  return $self->get_UT_date( $FITS_headers );
}

sub to_UTEND {
  my $self = shift;
  my $FITS_headers = shift;

  # Obtain the start time.
  my $start = $self->to_UTSTART( $FITS_headers );

  # Approximate end time.
  return $self->_add_seconds( $start, $FITS_headers->{EXPTIME} );
}

sub to_UTSTART {
  my $self = shift;
  my $FITS_headers = shift;
  my $return;
  if ( exists $FITS_headers->{'DATE-OBS'} ) {
    $return = $self->_parse_iso_date( $FITS_headers->{'DATE-OBS'} );
  } elsif (exists $FITS_headers->{UTC}) {

    # Converts the UT date in YYYYMMDD format obytained from the headers to a
    # date object at midnight.  Then add the seconds past midnight to the
    # object.
    my $base = $self->to_UTDATE( $FITS_headers );
    my $basedate = $self->_utdate_to_object( $base );
    $return = $self->_add_seconds( $basedate, $FITS_headers->{UTC} );

  } elsif ( exists( $FITS_headers->{"HIERARCH.ESO.OBS.START"} ) ) {

    # Use the backup of the observation start header, which is encoded in
    # FITS data format, i.e. yyyy-mm-ddThh:mm:ss.
    $return = $self->_parse_iso_date( $FITS_headers->{"HIERARCH.ESO.OBS.START"});
  }
  return $return;
}

sub to_WAVEPLATE_ANGLE {
  my $self = shift;
  my $FITS_headers = shift;
  my $polangle = 0.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
    $polangle = $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.ROT.OFFANGLE"} ) {
    $polangle = $FITS_headers->{"HIERARCH.ESO.SEQ.ROT.OFFANGLE"};
  } elsif ( exists $FITS_headers->{CROTA1} ) {
    $polangle = abs( $FITS_headers->{CROTA1} );
  }
  return $polangle;
}

sub from_WAVEPLATE_ANGLE {
  my $self = shift;
  my $generic_headers = shift;
  "HIERARCH.ESO.ADA.POSANG",  $generic_headers->{ "WAVEPLATE_ANGLE" };
}

# Use the nominal reference pixel if correctly supplied, failing that
# take the average of the bounds, and if these headers are also absent,
# use a default which assumes the full array.
sub to_X_REFERENCE_PIXEL{
  my $self = shift;
  my $FITS_headers = shift;
  my $xref;
  if ( exists $FITS_headers->{CRPIX1} ) {
    $xref = $FITS_headers->{CRPIX1};
  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} && exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"} ) {
    my $xl = $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"};
    my $xu = $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"};
    $xref = $self->nint( ( $xl + $xu ) / 2 );
  } else {
    $xref = 504;
  }
  return $xref;
}

sub to_X_LOWER_BOUND {
  my $self = shift;
  my $FITS_headers = shift;
  return $self->nint( $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} );
}

sub from_X_REFERENCE_PIXEL {
  my $self = shift;
  my $generic_headers = shift;
  "CRPIX1", $generic_headers->{"X_REFERENCE_PIXEL"};
}

sub to_X_UPPER_BOUND {
  my $self = shift;
  my $FITS_headers = shift;
  return $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} - 1 + $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"};
}

sub to_Y_LOWER_BOUND {
  my $self = shift;
  my $FITS_headers = shift;
  return $self->nint( $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} );
}

# Use the nominal reference pixel if correctly supplied, failing that
# take the average of the bounds, and if these headers are also absent,
# use a default which assumes the full array.
sub to_Y_REFERENCE_PIXEL{
  my $self = shift;
  my $FITS_headers = shift;
  my $yref;
  if ( exists $FITS_headers->{CRPIX2} ) {
    $yref = $FITS_headers->{CRPIX2};
  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} && exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"} ) {
    my $yl = $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"};
    my $yu = $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"};
    $yref = $self->nint( ( $yl + $yu ) / 2 );
  } else {
    $yref = 491;
  }
  return $yref;
}

sub from_Y_REFERENCE_PIXEL {
  my $self = shift;
  my $generic_headers = shift;
  "CRPIX2", $generic_headers->{"Y_REFERENCE_PIXEL"};
}

sub to_Y_UPPER_BOUND {
  my $self = shift;
  my $FITS_headers = shift;
  return $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} - 1 + $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"};
}

# Supplementary methods for the translations
# ------------------------------------------

# Get the observation mode.
sub get_instrument_mode {
  my $self = shift;
  my $FITS_headers = shift;
  my $mode = uc( $FITS_headers->{"HIERARCH.ESO.DPR.TECH"} );
  if ( $mode eq "IMAGE" || $mode eq "POLARIMETRY" ) {
    $mode = "imaging";
  } elsif ( $mode =~ /SPECTRUM/ ) {
    $mode = "spectroscopy";
  }
  return $mode;
}

# Returns the UT date in YYYYMMDD format.
sub get_UT_date {
  my $self = shift;
  my $FITS_headers = shift;

  # This is UT start and time.
  my $dateobs = $FITS_headers->{"DATE-OBS"};

  # Extract out the data in yyyymmdd format.
  return substr( $dateobs, 0, 4 ) . substr( $dateobs, 5, 2 ) . substr( $dateobs, 8, 2 )
}

# Returns the UT time of start of observation in decimal hours.
sub get_UT_hours {
  my $self = shift;
  my $FITS_headers = shift;

  # This is approximate.  UTC is time in seconds.
  my $startsec = 0.0;
  if ( exists ( $FITS_headers->{UTC} ) ) {
    $startsec  = $FITS_headers->{UTC};

    # Use the backup of the observation start header, which is encoded in
    # FITS data format, i.e. yyyy-mm-ddThh:mm:ss.  So convert ot seconds.
  } elsif ( exists( $FITS_headers->{"HIERARCH.ESO.OBS.START"} ) ) {
    my $t = $FITS_headers->{"HIERARCH.ESO.OBS.START"};
    $startsec = substr( $t, 11, 2 ) * 3600.0 +
      substr( $t, 14, 2 ) * 60.0 + substr( $t, 17, 2  );
  }

  # Convert from seconds to decimal hours.
  return $startsec / 3600.0;
}

sub rotation {
  my $self = shift;
  my $FITS_headers = shift;
  my $rotangle;

  # Define degrees-to-radians conversion.
  my $dtor = atan2( 1, 1 ) / 45.0;

  # The PC matrix first.
  if ( exists $FITS_headers->{PC001001} ) {
    my $pc11 = $FITS_headers->{PC001001};
    my $pc21 = $FITS_headers->{PC002001};
    $rotangle = $dtor * atan2( -$pc21 / $dtor, $pc11 / $dtor );

    # Instead try CD matrix.  Testing for existence of first column should
    # be adequate.
  } elsif ( exists $FITS_headers->{CD1_1} && exists $FITS_headers->{CD2_1}) {

    my $cd11 = $FITS_headers->{CD1_1};
    my $cd12 = $FITS_headers->{CD1_2};
    my $cd21 = $FITS_headers->{CD2_1};
    my $cd22 = $FITS_headers->{CD2_2};
    my $sgn;
    if ( ( $cd11 * $cd22 - $cd12 * $cd21 ) < 0 ) {
      $sgn = -1;
    } else {
      $sgn = 1;
    }
    my $cdelt1 = $sgn * sqrt( $cd11**2 + $cd21**2 );
    my $sgn2;
    if ( $cdelt1 < 0 ) {
      $sgn2 = -1;
    } else {
      $sgn2 = 1;
    }
    $rotangle = atan2( -$cd21 * $dtor, $sgn2 * $cd11 * $dtor ) / $dtor;

    # Orientation may be encapsulated in the slit position angle for
    # spectroscopy.
  } else {
    if ( uc( $self->get_instrument_mode($FITS_headers) ) eq "SPECTROSCOPY" &&
         exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
      $rotangle = $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
    } else {
      $rotangle = 180.0;
    }
  }
  return $rotangle;
}

1;