package Subs::Attitude;
##############################################################################
#
# DESCRIPTION: Extract attitude data. Swift attitude data appears
# DESCRIPTION: in several places in the telemetry. This subroutine
# DESCRIPTION: collects all the attitude data into a single quaternion-
# DESCRIPTION: based attitude file and removed redundant information.
# DESCRIPTION:
# DESCRIPTION: The main source of attitude data is the "ACS packets"
# DESCRIPTION: (APID 404). The BAT also places ACS records in the
# DESCRIPTION: headers of each LDP and in the body of a special
# DESCRIPTION: LDP generated suring slews.
# DESCRIPTION: The XRT places ACS records in the header for each CCD
# DESCRIPTION: frame, however these are represented by "floats" instead
# DESCRIPTION: of "doubles", so they are not used if higher accuracy
# DESCRIPTION: data are available.
#
# HISTORY: $Log: Attitude.pm,v $
# HISTORY: Revision 1.30 2005/03/15 20:04:42 apsop
# HISTORY: Fix bug in call for getting xrt eng hk file name.
# HISTORY:
# HISTORY: Revision 1.30 2005/03/15 19:03:26 apsop
# HISTORY: Check for presence of xrt no position message when making tdrss cat file.
# HISTORY:
# HISTORY: Revision 1.29 2005/02/18 01:53:09 apsop
# HISTORY: Only use secondary attitude data if primary data is not available. Fix up xrt modes.
# HISTORY:
# HISTORY: Revision 1.28 2005/02/14 19:23:39 apsop
# HISTORY: Remove batatt call. Instead pickup bat attitude info from bat2fits output.
# HISTORY:
# HISTORY: Revision 1.27 2005/02/10 02:52:14 apsop
# HISTORY: Get start and stop times from attitude file, as StartEndTimes has not been run yet.
# HISTORY:
# HISTORY: Revision 1.26 2005/02/08 18:24:17 apsop
# HISTORY: Abberation correction using abberator tool with time test. Move calculation of attorb file to this class as it is needed for abberator.
# HISTORY:
# HISTORY: Revision 1.25 2005/01/12 17:19:13 apsop
# HISTORY: Exclude safeholds from settling and pointing GTIs. Include 10arcmin flag in pointing GTI.
# HISTORY:
# HISTORY: Revision 1.24 2004/12/10 02:16:09 apsop
# HISTORY: Do a diff of attitude files after normalization, so we have a record of what changed.
# HISTORY:
# HISTORY: Revision 1.23 2004/12/02 18:55:50 apsop
# HISTORY: Normalize quaternions before passing to aspect.
# HISTORY:
# HISTORY: Revision 1.22 2004/11/02 21:12:15 apsop
# HISTORY: Set DATE keyword in attitude file.
# HISTORY:
# HISTORY: Revision 1.21 2004/09/03 00:26:01 apsop
# HISTORY: Temporarily remove bat lc attitude information from attitude file.
# HISTORY:
# HISTORY: Revision 1.20 2004/08/16 15:23:09 apsop
# HISTORY: Turn on writing of HISTORY keywords.
# HISTORY:
# HISTORY: Revision 1.19 2004/07/06 19:59:32 apsop
# HISTORY: Add test for existence of nfi GTI file.
# HISTORY:
# HISTORY: Revision 1.18 2004/06/29 14:34:02 apsop
# HISTORY: New ontarget gti is pointing ANDed with nfi. Use as input to aspect tool.
# HISTORY:
# HISTORY: Revision 1.17 2004/06/14 14:26:23 apsop
# HISTORY: Write in name of alignment file into attitude file.
# HISTORY:
# HISTORY: Revision 1.16 2004/06/10 19:55:03 apsop
# HISTORY: Add updating of TSTART/TSTOP keywords in second extension.
# HISTORY:
# HISTORY: Revision 1.15 2004/05/28 19:16:07 apsop
# HISTORY: Changes to attitude file format
# HISTORY:
# HISTORY: Revision 1.14 2004/05/06 20:02:33 dah
# HISTORY: Add version number back into the header comments.
# HISTORY:
# HISTORY: Revision 1.13 2004/05/04 16:30:51 dah
# HISTORY: Set ra/dec to 0/0 if not attitude data. Issue error.
# HISTORY:
# HISTORY: Revision 1.12 2004/04/28 13:45:50 dah
# HISTORY: Change xrt header and engineering file modes to fit new standard.
# HISTORY:
# HISTORY: Revision 1.11 2004/04/16 20:21:18 dah
# HISTORY: Begin using embedded history records
# HISTORY:
#
# VERSION: 0.0
#
#
##############################################################################
use Subs::SwiftSub;
use Subs::NominalPointing;
use Util::AttTool;
@ISA = ('Subs::NominalPointing', 'Subs::SwiftSub');
use strict;
sub new {
my $proto=shift;
my $self=$proto->SUPER::new();
my $procpar =$self->procpar();
$self->{DESCRIPTION}="Extracting attitude data";
return $self;
}
##################
# METHODS:
##################
sub body {
my $self=shift;
my $log =$self->log();
my $filename=$self->filename();
my $procpar =$self->procpar();
my $jobpar =$self->jobpar();
##############################################
# extract attitude files from various sources
##############################################
my @acs = $self->extract_acs("acs_att_tmp");
my @bat = $filename->get('attitude', 'bat', '', '*');
my @xrt;
my @batlc;
unless( @acs || @bat ){
$log->entry("No primary atttiude data, using secondary sources.");
push @xrt, $self->extract_xrt("xrt_att_tmp");
push @bat, $filename->get('attlpd', 'bat', '', '*');
}else{
unlink $filename->get('attlpd', 'bat', '', '*');
}
#############################################################
# merge just the attitude files from the ACS type packets
###########################################################
my $acslist = Util::FITSlist->new(@acs, @bat);
my @acs_merged;
unless($acslist->count() == 0) {
$log->entry("Merging ". join(" ", $acslist->files()) );
push @acs_merged, 'acs_merged.tmp';
my $merge_out = $acslist->merge($acs_merged[0]);
if($merge_out ne $acs_merged[0]) {
############################
# there was only one file
###########################
rename $merge_out, $acs_merged[0];
}else{
##########################
# delete the files
##########################
foreach ($acslist->files() ) {
$log->entry("Deleting $_");
unlink $_;
}
}
}else{
my $index = 0;
foreach my $lcfile ($filename->get('batlcatt', 'bat', '', '*'),
$filename->get('tdrsslcatt', 'bat', '', '*')){
push @batlc, "bat_lcatt_${index}.tmp";
Util::FITSfile->new($lcfile, 'ATTITUDE', '[col TIME; QPARAM; POINTING; SOURCE(B)=4]')
->copy($batlc[-1]);
unlink $lcfile;
$index++;
}
}
#######################################
# merge all the attitude data
#######################################
my $attlist = Util::FITSlist->new(@acs_merged, @batlc, @xrt);
if($attlist->count() ==0) {
$log->error(2, 'No attitude files produced, setting ra,dec to 0,0');
$jobpar->set({ra => 0.0, dec => 0.0});
return;
} else {
$log->entry("Merging ". join(" ", $attlist->files()) );
}
my $attitude = $filename->get('attitude', 's');
my $merged=$attlist->merge($attitude, '', 'TIME', 'QPARAM', 'POINTING', 'SOURCE');
###############################################################################
# Make a seperate extension with just the extra columns from the ACS packets
###############################################################################
if($merged ne $attitude) {
############################
# there was only one file
###########################
rename $merged, $attitude;
Util::FITSfile->new($attitude, 'ATTITUDE', '[col TIME; POSITION; FLAGS; BUS_V; SOURCE]')
->append_to($attitude);
my $delcol = Util::Ftool->new('fdelcol')
->params({infile => $attitude.'[1]',
confirm => 'no',
proceed => 'yes'});
$delcol->params({colname => 'POSITION'})->run();
$delcol->params({colname => 'FLAGS'})->run();
$delcol->params({colname => 'BUS_V'})->run();
}else{
if(@acs_merged){
Util::FITSfile->new($acs_merged[0], 'ATTITUDE', '[col TIME; POSITION; FLAGS; BUS_V; SOURCE]')
->append_to($attitude);
}
##########################
# delete the files
##########################
foreach ($attlist->files() ) {
$log->entry("Deleting $_");
unlink $_;
}
}
##########################################
# TIME sort the merged attitude file and
# remove overdetermined values
##########################################
$log->entry("Sorting and uniqing $attitude");
my $now = Util::Date->new();
my $Tnow = $now->date() .'T'. $now->time();
my $fits = Util::FITSfile->new($attitude, 0);
$fits->keyword('DATE', $Tnow);
$fits->keyword('MJDREFI', 51910, 'MJD reference day');
$fits->keyword('MJDREFF', 7.428703700000000E-04, 'MJD reference (fraction of day)');
$fits->ext(1);
$fits->keyword('DATE', $Tnow);
$fits->keyword('MJDREFI', 51910, 'MJD reference day');
$fits->keyword('MJDREFF', 7.428703700000000E-04, 'MJD reference (fraction of day)');
$fits->sort();
if($fits->nhdus() > 2){
$fits->ext(2);
$fits->cols('TIME');
$fits->sort('unique')
->keyword('EXTNAME', 'ACS_DATA');
my $nrows = $fits->nrows();
my $tstart = $fits->rows(1 )->table();
my $tstop = $fits->rows($nrows)->table();
$fits->keyword('DATE', $Tnow);
$fits->keyword('TSTART', $tstart, 'Time of first attitude record');
$fits->keyword('TSTOP' , $tstop, 'Time of last attitude record');
$fits->ext(1);
}
Util::AttTool->new("att_thin")
->command_line($attitude)
->run();
$log->entry("normalizing quaternions");
my $colfilter = '[col *; QPARAM = QPARAM / sqrt(sum(QPARAM*QPARAM))]';
Util::HEAdas->new('ftcopy')
->params({
infile => $attitude . $colfilter,
outfile => 'attitude.tmp',
})
->run;
###############################################################
# Run diff so that we have a record in the log of what changed
###############################################################
my $diff = Util::Ftool->new('fdiff')
->params({file1 => "$attitude\[1]",
file2 => "attitude.tmp\[1]",
verbose => 'yes'});
$diff->run();
rename('attitude.tmp', $attitude);
##########################################
# set TSTART and TSTOP in the merged file
##########################################
$fits = Util::FITSfile->new($attitude, 1)
->cols("TIME");
my $nrows = $fits->nrows();
my $tstart = $fits->rows(1 )->table();
my $tstop = $fits->rows($nrows)->table();
$fits->keyword('TSTART', $tstart, 'Time of first attitude record');
$fits->keyword('TSTOP' , $tstop, 'Time of last attitude record');
my $align_fits = Util::FITSfile->new($filename->fetch_cal('alignment'), 0);
$fits->keyword('ALGN_NAM', $align_fits->keyword('FILENAME'),
'Name of alignment teldef file used.');
#################################################
# Make settling and pointing gtis
#################################################
my $tempfile = 'acs.tmp';
Util::HEAdas->new('ftsort')
->params({infile => $attitude.'[ACS_DATA]',
outfile => $tempfile,
unique => 'yes',
columns => 'TIME, FLAGS'})
->run();
my $maketime = Util::Ftool->new('maketime')
->params({infile => $tempfile.'[ACS_DATA]',
compact => 'no'});
my $settling = $filename->get('gti', 's', 'st', 0);
my $pointing = $filename->get('gti', 's', 'po', 0);
my $ontarget = $filename->get('gti', 's', 'ot', 0);
my $nfis = $filename->get('gti', 's', 'nf', 0);
$log->entry("Producing GTIs for settling and pointing.");
$maketime->params({outfile => $settling,
expr => 'FLAGS == b10x0xxxx'})
->run();
$maketime->params({outfile => $pointing,
expr => 'FLAGS == b11x0xxxx'})
->run();
if( -s $nfis ){
Util::Ftool->new('mgtime')
->params({ingtis => $pointing .' '. $nfis,
outgti => $ontarget,
merge => 'AND'})
->run();
}else{
Util::FITSfile->new($pointing)
->copy($ontarget);
}
unlink $tempfile;
##########################################
# determine the nominal pointing
# using method inherited from superclass
##########################################
unless( -f $ontarget) {
$log->entry("No science data GTI file $ontarget");
$ontarget = $self->no_gap_gtis($attitude, 100);
}
$self->determine_pointing($ontarget);
if($ontarget =~ /\.tmp$/ ) {
####################################
# delete no-gap temporary GTI file
####################################
unlink $ontarget;
}
#################################################################
# Only do abberation correction in velocity adding was turned on
#################################################################
my $velocity_add = Util::Date->new('2005-01-31T21:57:00');
$self->correct_aberration()
if $tstart < $velocity_add->seconds();
############################################################
# calculate attitude/orbit calculated filtering quantities
############################################################
$self->calculate_att_orb();
} # end of body method
#############################################################################
# Extract an attitude file from the ACS packets
#############################################################################
sub extract_acs {
my $self=shift;
my $base=shift;
my $log =$self->log();
my $filename=$self->filename();
my $procpar =$self->procpar();
my $jobpar =$self->jobpar();
my @list=();
##############################
# get the ACS packets file
##############################
my @acs = $filename->get("telemetry", "*", "acs", "*");
unless(@acs && -f $acs[0]) {
$log->entry("No ACS packets available");
return @list;
}
##########################
# run acs2fits
##########################
my $index=0;
foreach (@acs) {
#######################
# get the file name
#######################
my $file = "$base.$index";
$log->entry("Creating $file from $_");
unlink $file;
#############################
# set up the extraction tool
#############################
my $teldef = $filename->fetch_cal('alignment');
my $acs2fits=Util::AttTool->new('acs2fits');
$acs2fits->command_line("-infile $_",
"-alignfile $teldef",
"-outfile $file");
#####################################
# run the tool and check for errors
#####################################
$acs2fits->run();
if($acs2fits->had_error() ) {
#############
# error
#############
if(-f $file ) {
$log->entry("Removing $file");
unlink $file;
}
} else {
#############
# success
#############
push @list, ($file);
}
} # end of loop over input files
return @list;
} # end of extract_acs method
#############################################################################
# Extract an attitude file from the XRT FITS files
#############################################################################
sub extract_xrt {
my $self=shift;
my $base=shift;
my $log =$self->log();
my $filename=$self->filename();
my $procpar =$self->procpar();
my $jobpar =$self->jobpar();
my @list=();
###########################################################
# types of XRT HK files with attitude data in them
###########################################################
my @modes = ('48a', '4e0', '4e1', '4e2', '4f0', '500', '534', '536', '53a', '540');
### "48Ah", "4E0h", "4E1h", "4E2h",
### "4F0h", "500h", "534h", "536h", "53Ah", "540h");
my @files=();
my @head_files = ( $filename->get('hk', 'xrt', 'hd', '*') );
push @files, map $_ .= "[1]", @head_files;
my $eng_hk = $filename->get('enhk', 'xrt', '', 0);
if( -f $eng_hk ){
my $fitsEng = Util::FITSfile->new($eng_hk);
my $nhdus = $fitsEng->nhdus();
for(my $iext=1; $iext < $nhdus; $iext++){
$fitsEng->ext($iext);
my $name = $fitsEng->keyword('EXTNAME');
if( grep(/^hk${name}x$/, @modes) ){
push @files, $eng_hk . "[$name]";
}
}
}
unless(@files) {
$log->entry("No attitude data in the XRT FITS files");
return @list;
}
###################################
# extract the attitude files
###################################
my $index=0;
foreach (@files) {
#######################
# get the file name
#######################
my $file = "$base.".$index++;
$log->entry("Creating $file from $_");
unlink $file;
#############################
# set up the extraction tool
#############################
my $teldef = $filename->fetch_cal('alignment');
my $tool=Util::AttTool->new('xrtatt');
$tool->command_line("-infile '$_'",
"-alignfile $teldef",
"-outfile $file");
#####################################
# run the tool and check for errors
#####################################
$tool->run();
if($tool->had_error() ) {
#############
# error
#############
if(-f $file ) {
$log->entry("Removing $file");
unlink $file;
}
} else {
#############
# success
#############
push @list, ($file);
}
} # end of loop over input files
return @list;
} # end of extract_xrt method
#############################################################################
# create a GTI file from an attitude file which excludes gaps in the
# attitude records
#############################################################################
sub no_gap_gtis {
my $self = shift;
my $att = shift;
my $gap = shift;
my $log =$self->log();
$log->entry("Creating GTI file which excludes attitude ".
"record gaps > $gap s");
#############################################
# read the time records in the attitude file
#############################################
my $fits = Util::FITSfile->new($att, 1);
my @time = $fits->cols("TIME")->table();
unless(@time) {
$log->entry("No attitude records");
return "";
}
#######################################
# open an ASCII file to hold the data
#######################################
my $data = "att_gtis.tmp";
unlink $data;
open DATA, ">$data";
my $start=$time[0];
my $i;
my $nrows = $fits->nrows();
for($i=0; $i<$nrows; $i++) {
my $start = $time[$i];
###############################################################
# skip over consecutive rows to find the start of the next gap
###############################################################
while ($i < $nrows && $time[$i] - $time[$i-1] < $gap ) { $i++ }
####################
# record the GTI
####################
print DATA "$start $time[$i-1]\n";
###############################################################
# skip over the gap
###############################################################
while ($i < $nrows && $time[$i] - $time[$i-1] >= $gap ) { $i++ }
} # end of loop over attitude records
close DATA;
my $header = "att_gtis_header.tmp";
open HEADER, ">$header";
print HEADER "START 1D\n";
print HEADER "STOP 1D\n";
close HEADER;
my $gti = "att_gtis_fits.tmp";
my $fcreate = Util::Ftool->new("fcreate");
$fcreate->params({cdfile => $header,
datafile => $data,
outfile => $gti,
headfile => " ",
tbltype => "binary",
nskip => 0,
nrows => 0,
history => "yes",
morehdr => 0,
extname => "GTI",
anull => " ",
inull => 0,
clobber => "yes"})
->run();
unlink $data;
unlink $header;
unless($fcreate->had_error() ) { return $gti }
else { return "" }
} # end of no_gap_gtis;
#############################################################################
# Calculate attitude/orbit derived filter quantities
#############################################################################
sub calculate_att_orb {
my $self=shift;
my @columns=@_;
my $log =$self->log();
my $filename=$self->filename();
my $procpar =$self->procpar();
my $jobpar =$self->jobpar();
$log->entry("Calculating attitude/orbit filtering quantities");
#######################################
# make sure we have an attitude file
# othewise there's no sense bothering
#######################################
my $attitude = $filename->get('attitude', 's');
unless( -f $attitude ) {
$log->entry("No attitude data available");
return;
}
##################################################################
# assemble the mission zero time into a string to feed prefilter
##################################################################
my $epoch = $procpar->read("refdate")."T".$procpar->read("reftime");
my $fits = Util::FITSfile->new($attitude, 1);
my $tstart = $fits->keyword("TSTART");
my $tstop = $fits->keyword("TSTOP");
my $attorb = $filename->get('attorb', 's');
unlink $attorb;
my $cols = 'ALL';
$cols = join(' ', @columns) if @columns;
#####################################
# set up and run prefilter
#####################################
my $prefilter = Util::HEAdas->new("prefilter");
$prefilter->params({outname => $attorb,
columns => $cols,
orbmode => "TLE_TEXT2",
orbname => $filename->fetch_orbit(),
attname => $attitude,
alignfile => $filename->fetch_cal('alignment'),
leapname => $filename->fetch_cal("leapsec"),
rigname => $filename->fetch_cal("rigidity"),
start => $tstart,
end => $tstop,
interval => 1.0, # was 30.0
attextrap => '32',
origin => 'GSFC',
ranom => $jobpar->read("ra"),
decnom => $jobpar->read("dec"),
missepoch => $epoch,
compcols => "TIME",
compapplyquat => "no",
chatter => 4,
clobber => "yes" });
$log->entry("Running ". $prefilter->name() );
$prefilter->run();
} # end of calculate_att_orb
#############################################################################
# Calculate attitude/orbit derived filter quantities
#############################################################################
sub correct_aberration {
my $self=shift;
my $log =$self->log();
my $filename=$self->filename();
my $procpar =$self->procpar();
my $jobpar =$self->jobpar();
$log->entry('Doing aberration correction of attitude file');
#######################################
# make sure we have an attitude file
# othewise there's no sense bothering
#######################################
my $attitude = $filename->get('attitude', 's');
unless( -f $attitude ) {
$log->entry('No attitude data available');
return;
}
$log->entry('Preliminary prefilter run');
$self->calculate_att_orb('TIME', 'POSITION', 'VELOCITY');
my $aberration = Util::HEAdas->new('aberrator')
->params({infile => $attitude,
orbfile => $filename->get('attorb', 's'),
alignfile => $filename->fetch_cal('alignment')
});
$log->entry('Run aberrator');
$aberration->run();
} # end of correct_aberration