The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Astro::FITS::HdrTrans::NACO;

=head1 NAME

Astro::FITS::HdrTrans::NACO - ESO NACO translations

=head1 SYNOPSIS

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

  %gen = Astro::FITS::HdrTrans::NACO->translate_from_FITS( %hdr );

=head1 DESCRIPTION

This class provides a generic set of translations that are specific to
the NACO camera of the European Southern Observatory.

=cut

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

# Inherit from ESO
use base qw/ Astro::FITS::HdrTrans::ESO /;

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 = (
                 POLARIMETRY   => 0,
                );

# NULL mappings used to override base class implementations
my @NULL_MAP = qw/ /;

# unit mapping implies that the value propogates directly
# to the output with only a keyword name change

my %UNIT_MAP = (
               );


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

=head1 METHODS

=over 4

=item B<this_instrument>

The name of the instrument required to match (case insensitively)
against the INSTRUME/INSTRUMENT keyword to allow this class to
translate the specified headers. Called by the default
C<can_translate> method.

  $inst = $class->this_instrument();

Returns "NAOS+CONICA".

=cut

sub this_instrument {
  return "NAOS+CONICA";
}

=back

=head1 COMPLEX CONVERSIONS

=over 4

=cut

sub to_DEC_SCALE {
  my $self = shift;
  my $FITS_headers = shift;
  my $scale;
  my $scale_def = 0.0271;
  if ( exists ( $FITS_headers->{CDELT2} ) ) {
    $scale = $FITS_headers->{CDELT2};
  } elsif ( exists ( $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) ) {
    $scale = $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} / 3600.0;
  }
  $scale = defined( $scale ) ? $scale: $scale_def;
  return $scale;
}

# If the telescope ofset exists in arcsec, then use it.  Otherwise
# convert the Cartesian offsets to equatorial offsets.
sub to_DEC_TELESCOPE_OFFSET {
  my $self = shift;
  my $FITS_headers = shift;
  my $decoffset = 0.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETD"} ) {
    $decoffset = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETD"};

  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETX"} &&
            exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETY"} ) {

    my $pixscale = 0.0271;
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) {
      $pixscale = $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"};
    }

    # Sometimes the first cumulative offsets are non-zero contrary to the
    # documentation.
    my $expno = 1;
    if ( exists $FITS_headers->{"HIERARCH.ESO.TPL.EXPNO"} ) {
      $expno = $FITS_headers->{"HIERARCH.ESO.TPL.EXPNO"};
    }
    my ( $x_as, $y_as );
    if ( $expno == 1 ) {
      $x_as = 0.0;
      $y_as = 0.0;
    } else {
      $x_as = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETX"} * $pixscale;
      $y_as = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETY"} * $pixscale;
    }

    # Define degrees to radians conversion and obtain the rotation angle.
    my $dtor = atan2( 1, 1 ) / 45.0;

    my $rotangle = $self->rotation($FITS_headers);
    my $cosrot = cos( $rotangle * $dtor );
    my $sinrot = sin( $rotangle * $dtor );

    # Apply the rotation matrix to obtain the equatorial pixel offset.
    $decoffset = -$x_as * $sinrot + $y_as * $cosrot;
  }
              
  # The sense is reversed compared with UKIRT, as these measure the
  # place on the sky, not the motion of the telescope.
  return -1.0 * $decoffset;
}

# Derive the translation between observing template and recipe name.
sub to_DR_RECIPE {
  my $self = shift;
  my $FITS_headers = shift;
  my $recipe = "QUICK_LOOK";

  # Obtain the observing template.  These are equivalent
  # to the UKIRT OT science programmes and their tied DR recipes.
  # However, there are some wrinkles and variations to be tested.
  my $template = $FITS_headers->{"HIERARCH.ESO.TPL.ID"};
  my $seq = $FITS_headers->{"HIERARCH.ESO.TPL.PRESEQ"};

  if ( $template =~ /_img_obs_AutoJitter/ ||
       $template =~ /_img_obs_GenericOffset/ ) {
    $recipe = "JITTER_SELF_FLAT";

  } elsif ( $template =~ /_img_cal_StandardStar/ ||
            $template =~ /_img_cal_StandardStarOff/ ||
            $template =~ /_img_tec_Zp/ ||
            $template =~ /_img_tec_ZpNoChop/ ||
            $seq =~ /_img_cal_StandardStar/ ||
            $seq =~ /_img_cal_StandardStarOff/ ) {
    $recipe = "JITTER_SELF_FLAT_APHOT";

  } elsif ( $template =~ /_img_obs_AutoJitterOffset/ ||
            $template =~ /_img_obs_FixedSkyOffset/ ) {
    $recipe = "CHOP_SKY_JITTER";

    # The following two perhaps should be using NOD_CHOP and a variant of
    # NOD_CHOP_APHOT to cope with the three source images (central double
    # flux) rather than four.
  } elsif ( $template =~ /_img_obs_AutoChopNod/ ||
            $seq =~ /_img_obs_AutoChopNod/ ) {
    $recipe = "NOD_SELF_FLAT_NO_MASK";

  } elsif ( $template =~ /_img_cal_ChopStandardStar/ ) {
    $recipe = "NOD_SELF_FLAT_NO_MASK_APHOT";

  } elsif ( $template =~ /_cal_Darks/ ||
            $seq =~ /_cal_Darks/ ) {
    $recipe = "REDUCE_DARK";

  } elsif ( $template =~ /_img_cal_TwFlats/ ||
            $template =~ /_img_cal_SkyFlats/ ) {
    $recipe = "SKY_FLAT_MASKED";

  } elsif ( $template =~ /_img_cal_LampFlats/ ) {
    $recipe = "LAMP_FLAT";

    # Imaging spectroscopy.  There appears to be no distinction
    # for flats from target, hence no division into POL_JITTER and
    # SKY_FLAT_POL.
  } elsif ( $template =~ /_pol_obs_GenericOffset/ ||
            $template =~ /_pol_cal_StandardStar/ ) {
    $recipe = "POL_JITTER";

  } elsif ( $template =~ /_pol_obs_AutoChopNod/ ||
            $template =~ /_pol_cal_ChopStandardStar/ ) {
    $recipe = "POL_NOD_CHOP";

  } elsif ( $template =~ /_pol_cal_LampFlats/ ) {
    $recipe = "POL_JITTER";

    # Spectroscopy.  EXTENDED_SOURCE may be more appropriate for
    # the NACO_spec_obs_GenericOffset template.
  } elsif ( $template =~ /_spec_obs_AutoNodOnSlit/ ||
            $template =~ /_spec_obs_GenericOffset/ ||
            $template =~ /_spec_obs_AutoChopNod/ ) {
    $recipe = "POINT_SOURCE";

  } elsif ( $template =~ /_spec_cal_StandardStar/ ||
            $template =~ /_spec_cal_StandardStarNod/ ||
            $template =~ /_spec_cal_AutoNodOnSlit/  ) {
    $recipe = "STANDARD_STAR";

  } elsif ( $template =~ /_spec_cal_NightCalib/ ) {
    $recipe = "REDUCE_SINGLE_FRAME";

  } elsif ( $template =~ /_spec_cal_Arcs/ ||
            $seq =~ /_spec_cal_Arcs/ ) {
    $recipe = "REDUCE_ARC";

  } elsif ( $template =~ /_spec_cal_LampFlats/ ) {
    $recipe = "LAMP_FLAT";
  }
  return $recipe;
}


# Filters appear to be in wheels 4 to 6.  It appears the filter
# in just one of the three.
sub to_FILTER {
  my $self = shift;
  my $FITS_headers = shift;
  my $filter = "empty";

  my $id = 4;
  while ( $filter eq "empty" && $id < 7 ) {
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.OPTI${id}.NAME"} ) {
      $filter = $FITS_headers->{"HIERARCH.ESO.INS.OPTI${id}.NAME"};
    }
    $id++;
  }
  return $filter;
}

# Fixed value for the gain, as that's all the documentation gives.
# the readout mode.
sub to_GAIN {
  10;
}

# Using Table 10 of the NACO USer's Guide.
sub to_GRATING_DISPERSION {
  my $self = shift;
  my $FITS_headers = shift;
  my $dispersion = 0.0;
  if ( exists $FITS_headers->{CDELT1} ) {
    $dispersion = $FITS_headers->{CDELT1};
  } else {
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"} &&
         exists $FITS_headers->{"HIERARCH.ESO.OPTI7.NAME"} ) {
      my $order = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"};
      my $camera = $FITS_headers->{"HIERARCH.ESO.OPTI7.NAME"};

      if ( $camera eq "S54" ) {
        if ( $order == 1 ) {
          $dispersion = 1.98e-3;
        } elsif ( $order == 2 ) {
          $dispersion = 6.8e-4;
        } elsif ( $order == 3 ) {
          $dispersion = 9.7e-4;
        }

      } elsif ( $camera eq "L54" ) {
        $dispersion = 3.20e-3;

      } elsif ( $camera eq "S27" ) {
        if ( $order == 1 ) {
          $dispersion = 9.5e-4;
        } elsif ( $order == 2 ) {
          $dispersion = 5.0e-4;
        }
      }
    }
  }
  return $dispersion;
}

sub to_RA_SCALE {
  my $self = shift;
  my $FITS_headers = shift;
  my $scale;
  my $scale_def = -0.0271;
  if ( exists ( $FITS_headers->{CDELT1} ) ) {
    $scale = $FITS_headers->{CDELT1};
  } elsif ( exists ( $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) ) {
    $scale = - $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} / 3600.0;
  }
  $scale = defined( $scale ) ? $scale: $scale_def;
  return $scale;
}

# If the telescope offset exists in arcsec, then use it.  Otherwise
# convert the Cartesian offsets to equatorial offsets.
sub to_RA_TELESCOPE_OFFSET {
  my $self = shift;
  my $FITS_headers = shift;
  my $raoffset = 0.0;
  if ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETA"} ) {
    $raoffset = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETA"};

  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETX"} &&
            exists $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETY"} ) {

    my $pixscale = 0.0271;
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) {
      $pixscale = $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"};
    }

    # Sometimes the first cumulative offsets are non-zero contrary to the
    # documentation.
    my $expno = 1;
    if ( exists $FITS_headers->{"HIERARCH.ESO.TPL.EXPNO"} ) {
      $expno = $FITS_headers->{"HIERARCH.ESO.TPL.EXPNO"};
    }
    my ( $x_as, $y_as );
    if ( $expno == 1 ) {
      $x_as = 0.0;
      $y_as = 0.0;
    } else {
      $x_as = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETX"} * $pixscale;
      $y_as = $FITS_headers->{"HIERARCH.ESO.SEQ.CUMOFFSETY"} * $pixscale;
    }

    # Define degrees to radians conversion and obtain the rotation angle.
    my $dtor = atan2( 1, 1 ) / 45.0;

    my $rotangle = $self->rotation($FITS_headers);
    my $cosrot = cos( $rotangle * $dtor );
    my $sinrot = sin( $rotangle * $dtor );

    # Apply the rotation matrix to obtain the equatorial pixel offset.
    $raoffset = -$x_as * $cosrot + $y_as * $sinrot;
  }
              
  # The sense is reversed compared with UKIRT, as these measure the
  # place on the sky, not the motion of the telescope.
  return -1.0 * $raoffset;
}

# Just translate to shorter strings for ease and to fit within the
# night log.
sub to_SPEED_GAIN {
  my $self = shift;
  my $FITS_headers = shift;
  my $spd_gain = "HighSens";
  my $detector_mode = exists( $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} ) ?
    $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} : $spd_gain;
  if ( $detector_mode eq "HighSensitivity" ) {
    $spd_gain = "HighSens";
  } elsif ( $detector_mode eq "HighDynamic" ) {
    $spd_gain = "HighDyn";
  } elsif ( $detector_mode eq "HighBackground" ) {
    $spd_gain = "HighBack";
  }
  return $spd_gain;
}

# Translate to the SLALIB name for reference frame in spectroscopy.
sub to_TELESCOPE {
  my $self = shift;
  my $FITS_headers = shift;
  my $telescope = "VLT4";
  if ( exists $FITS_headers->{TELESCOP} ) {
    my $scope = $FITS_headers->{TELESCOP};
    if ( defined( $scope ) ) {
      $telescope = $scope;
      $telescope =~ s/ESO-//;
      $telescope =~ s/-U//g;
    }
  }
  return $telescope;
}

=back

=head1 REVISION

 $Id: SOFI.pm 14879 2008-02-13 21:51:31Z timj $

=head1 SEE ALSO

C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>.

=head1 AUTHOR

Malcolm J. Currie E<lt>mjc@star.rl.ac.ukE<gt>
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-2005 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;