The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Astro::FITS::HdrTrans::ISAAC;

=head1 NAME

Astro::FITS::HdrTrans::ISAAC - ESO ISAAC translations

=head1 SYNOPSIS

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

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

=head1 DESCRIPTION

This class provides a generic set of translations that are specific to
the ISAAC 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 "ISAAC".

=cut

sub this_instrument {
  return "ISAAC";
}

=back

=head1 COMPLEX CONVERSIONS

=over 4

=item B<to_DEC_TELESCOPE_OFFSET>

If the telescope ofset exists in arcsec, then use it.  Otherwise
convert the Cartesian offsets to equatorial offsets.

=cut

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.148;
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) {
      $pixscale = $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"};
    }

    # Sometimes the first imaging 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 );
    my $mode = uc( $self->get_instrument_mode($FITS_headers) );
    if ( $expno == 1 && ( $mode eq "IMAGE" || $mode eq "POLARIMETRY" ) ) {
      $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;
}

# Filter positions 1 and 2 used for SW and 3 & 4 for LW.
sub to_FILTER {
  my $self = shift;
  my $FITS_headers = shift;
  my $filter = "Ks";
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"} ) {
    $filter = $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"};
  } elsif ( exists $FITS_headers->{"HIERARCH.ESO.INS.FILT3.ID"} ) {
    $filter = $FITS_headers->{"HIERARCH.ESO.INS.FILT3.ID"};
  }
  return $filter;
}

# Fixed values for the gain depend on the camera (SW or LW), and for LW
# the readout mode.
sub to_GAIN {
  my $self = shift;
  my $FITS_headers = shift;
  my $gain = 4.6;
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.MODE"} ) {
    if ( $FITS_headers->{"HIERARCH.ESO.INS.MODE"} =~ /SW/ ) {
      $gain = 4.6;
    } else {
      if ( exists $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} ) {
        if ( $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} =~ /LowBias/ ) {
          $gain = 8.7;
        } else {
          $gain = 7.8;
        }
      }
    }
  }
  return $gain;
}

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.INS.GRAT.ORDER"} ) {
    my $order = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"};
    if ( $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"} eq "LR" ) {
      if ( $order == 6 ) {
        $dispersion = 2.36e-4;
      } elsif ( $order == 5 ) {
        $dispersion = 2.83e-4;
      } elsif ( $order == 4 ) {
        $dispersion = 3.54e-4;
      } elsif ( $order == 3 ) {
        $dispersion = 4.72e-4;
      } elsif ( $order == 2 ) {
        $dispersion = 7.09e-4;
      } elsif ( $order == 1 ) {
        if ( exists $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"} ) {
          my $filter = $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"};
          if ( $filter =~/SL/ ) {
            $dispersion = 1.412e-3;
          } else {
            $dispersion = 1.45e-3;
          }
        } else {
          $dispersion = 1.41e-3;
        }
      }

      # Medium dispersion
    } elsif ( $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"} eq "MR" ) {
      if ( $order == 6 ) {
        $dispersion = 3.7e-5;
      } elsif ( $order == 5 ) {
        $dispersion = 4.6e-5;
      } elsif ( $order == 4 ) {
        $dispersion = 5.9e-5;
      } elsif ( $order == 3 ) {
        $dispersion = 7.8e-5;
      } elsif ( $order == 2 ) {
        $dispersion = 1.21e-4;
      } elsif ( $order == 1 ) {
        if ( exists $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"} ) {
          my $filter = $FITS_headers->{"HIERARCH.ESO.INS.FILT1.ID"};
          if ( $filter =~/SL/ ) {
            $dispersion = 2.52e-4;
          } else {
            $dispersion = 2.39e-4;
          }
        } else {
          $dispersion = 2.46e-4;
        }
      }
    }
  }
  #   }
  return $dispersion;
}     

# 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.148;
    if ( exists $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"} ) {
      $pixscale = $FITS_headers->{"HIERARCH.ESO.INS.PIXSCALE"};
    }

    # Sometimes the first imaging 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 );
    my $mode = uc( $self->get_instrument_mode($FITS_headers) );
    if ( $expno == 1 && ( $mode eq "IMAGE" || $mode eq "POLARIMETRY" ) ) {
      $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;
}

# 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 =~ /ISAAC[SL]W_img_obs_AutoJitter/ ||
       $template =~ /ISAAC[SL]W_img_obs_GenericOffset/ ) {
    $recipe = "JITTER_SELF_FLAT";

  } elsif ( $template eq "ISAACSW_img_cal_StandardStar" ||
            $template eq "ISAACLW_img_cal_StandardStarOff" ||
            $template eq "ISAACSW_img_tec_Zp" ||
            $template eq "ISAACLW_img_tec_ZpNoChop" ||
            $seq eq "ISAAC_img_cal_StandardStar" ||
            $seq eq "ISAACLW_img_cal_StandardStarOff" ) {
    $recipe = "JITTER_SELF_FLAT_APHOT";

  } elsif ( $template =~ /ISAAC[SL]W_img_obs_AutoJitterOffset/ ) {
    $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 eq "ISAACLW_img_obs_AutoChopNod" ||
            $seq eq "ISAACLW_img_obs_AutoChopNod" ) {
    $recipe = "NOD_SELF_FLAT_NO_MASK";

  } elsif ( $template eq "ISAACLW_img_cal_StandardStar" ||
            $template =~ /^ISAACLW_img_tec_Zp/ ||
            $seq eq "ISAACLW_img_cal_StandardStar" ) {
    $recipe = "NOD_SELF_FLAT_NO_MASK_APHOT";

  } elsif ( $template =~ /ISAAC[SL]W_img_cal_Darks/ ||
            $seq eq "ISAAC_img_cal_Darks" ) {
    $recipe = "REDUCE_DARK";

  } elsif ( $template =~ /ISAAC[SL]W_img_cal_TwFlats/ ) {
    $recipe = "SKY_FLAT_MASKED";

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

    # Spectroscopy.  EXTENDED_SOURCE may be more appropriate for
    # the ISAACSW_spec_obs_GenericOffset template.
  } elsif ( $template =~ /ISAAC[SL]W_spec_obs_AutoNodOnSlit/ ||
            $template =~ /ISAAC[SL]W_spec_obs_GenericOffset/ ||
            $template eq "ISAACLW_spec_obs_AutoChopNod" ) {
    $recipe = "POINT_SOURCE";

  } elsif ( $template =~ /ISAAC[SL]W_spec_cal_StandardStar/ ||
            $template eq "ISAACLW_spec_cal_StandardStarNod" ||
            $template =~ /ISAAC[SL]W_spec_cal_AutoNodOnSlit/  ) {
    $recipe = "STANDARD_STAR";

  } elsif ( $template =~ /ISAAC[SL]W_spec_cal_NightCalib/ ) {
    if ( $self->_to_OBSERVATION_TYPE() eq "LAMP" ) {
      $recipe = "LAMP_FLAT";
    } elsif ( $self->_to_OBSERVATION_TYPE() eq "ARC" ) {
      $recipe = "REDUCE_ARC";
    } else {
      $recipe = "REDUCE_SINGLE_FRAME";
    }

  } elsif ( $template =~ /ISAAC[SL]W_spec_cal_Arcs/ ||
            $seq eq "ISAAC_spec_cal_Arcs" ) {
    $recipe = "REDUCE_ARC";

  } elsif ( $template =~ /ISAAC[SL]W_spec_cal_Flats/ ) {
    $recipe = "LAMP_FLAT";
  }
  return $recipe;
}

# Fixed values for the gain depend on the camera (SW or LW), and for LW
# the readout mode.
sub to_SPEED_GAIN {
  my $self = shift;
  my $FITS_headers = shift;
  my $spd_gain = "Normal";
  if ( exists $FITS_headers->{"HIERARCH.ESO.INS.MODE"} ) {
    if ( $FITS_headers->{"HIERARCH.ESO.INS.MODE"} =~ /SW/ ) {
      $spd_gain = "Normal";
    } else {
      if ( exists $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} ) {
        if ( $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} =~ /LowBias/ ) {
          $spd_gain = "HiGain";
        } else {
          $spd_gain = "Normal";
        }
      }
    }
  }
  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 = "VLT1";
  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;