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.44 2006/06/28 18:54:42 apsop # HISTORY: Create new target pointing GTI file. # HISTORY: # HISTORY: Revision 1.43 2006/06/15 22:11:52 apsop # HISTORY: Make pointing and slew GTIs more restrictive, so that times beyond end measurements are excluded. # HISTORY: # HISTORY: Revision 1.42 2006/04/27 16:28:12 apsop # HISTORY: Make a lack of any attitude file a fatal error. # HISTORY: # HISTORY: Revision 1.41 2006/03/13 17:39:17 apsop # HISTORY: Use XRT attitude info in 991 seqs. Fix bug in choosing GTI for mean pointing determination. # HISTORY: # HISTORY: Revision 1.40 2006/01/29 19:37:26 apsop # HISTORY: Add 60 secs of buffer at each end of the range for the att/orbit file. # HISTORY: # HISTORY: Revision 1.39 2005/11/20 21:26:10 apsop # HISTORY: Unexpected OO behaviour for $inter_file requires seperate call to append_to. # HISTORY: # HISTORY: Revision 1.38 2005/11/20 20:30:03 apsop # HISTORY: Check for presence of BUS_V column before making and using ACS_DATA extension in attitude file. # HISTORY: # HISTORY: Revision 1.37 2005/11/08 16:58:18 apsop # HISTORY: Change fdiff to ftdiff and set reltol to 10e-8. Use caldb to get alignment file in prefilter and abberator. # HISTORY: # HISTORY: Revision 1.36 2005/09/26 21:32:35 apsop # HISTORY: Make not_pointing GTI file. # HISTORY: # HISTORY: Revision 1.35 2005/07/29 14:04:03 apsop # HISTORY: look for ACS packets that are labeled as head2 # HISTORY: # HISTORY: Revision 1.34 2005/06/01 17:38:51 apsop # HISTORY: More robust algorithm for selecting GTI file to use for determining the mean pointing. # HISTORY: # HISTORY: Revision 1.33 2005/04/19 16:05:48 apsop # HISTORY: Check for existence of gti files before setting keywords. # HISTORY: # HISTORY: Revision 1.32 2005/03/25 21:25:34 apsop # HISTORY: Fix bug in setting TSTOP for GTIs. Set gti extname to GTI. # HISTORY: # HISTORY: Revision 1.31 2005/03/25 20:18:04 apsop # HISTORY: Set TSTART and TSTOP for GTI files. # HISTORY: # 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; use Util::SwiftTags; 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', '', '*'); my $seq = $jobpar->read('sequence'); if( $seq%1000==991 ){ $log->entry("Using xrt attitude data in 991 sequence."); push @xrt, $self->extract_xrt("xrt_att_tmp"); } } ############################################################# # 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; my $inter_file = Util::FITSfile->new($attitude, 'ATTITUDE'); if($inter_file->find_column('BUS_V')){ $inter_file->specs('[col TIME; POSITION; FLAGS; BUS_V; SOURCE]'); $inter_file->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){ my $inter_file = Util::FITSfile->new($acs_merged[0], 'ATTITUDE'); if($inter_file->find_column('BUS_V')){ $inter_file->specs('[col TIME; POSITION; FLAGS; BUS_V; SOURCE]'); $inter_file->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::HEAdas->new('ftdiff') ->params({infile1 => "$attitude\[1]", infile2 => "attitude.tmp\[1]", reltol => 10e-8}); $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 $settling = $filename->get('gti', 's', 'st', 0); my $pointing = $filename->get('gti', 's', 'po', 0); my $not_pointing = $filename->get('gti', 's', 'np', 0); my $ontarget = $filename->get('gti', 's', 'ot', 0); my $nfis = $filename->get('gti', 's', 'nf', 0); my $target_pointing = $filename->get('gti', 's', 'tp', 0); if( (grep /ACS_DATA/, $fits->list_hdus()) ){ 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'}); $log->entry("Producing GTIs for settling and pointing."); $maketime->params({outfile => $settling, prefr => 0, postfr => 1, expr => 'FLAGS == b10x0xxxx'}) ->run(); $maketime->params({outfile => $pointing, prefr => 0, postfr => 0, expr => 'FLAGS == b11x0xxxx'}) ->run(); $maketime->params({outfile => $not_pointing, prefr => 1, postfr => 1, expr => 'FLAGS != b11xxxxxx || FLAGS == bxxx1xxxx'}) ->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 if -f $tempfile; } foreach my $gtifile ($settling, $pointing, $ontarget, $nfis){ next unless -s $gtifile; my $gtifits = Util::FITSfile->new($gtifile, 1); unless( $gtifits->nrows() ){ $log->error([ 1, ATT_NO_GTI ], "GTI file $gtifile is empty. Deleting."); unlink $gtifile; next; } $gtifits->keyword('EXTNAME', 'GTI'); $gtifits->keyword('TSTART', ($gtifits->cols('START')->table())[0] ); $gtifits->keyword('TSTOP', ($gtifits->cols('STOP')->table())[-1] ); } ########################################## # determine the nominal pointing # using method inherited from superclass ########################################## unless( -f $ontarget ){ if( -f $pointing ) { $ontarget = $pointing; }else{ $ontarget = $self->no_gap_gtis($attitude, 100); } } $self->determine_pointing($ontarget); if($ontarget =~ /\.tmp$/ ) { #################################### # delete no-gap temporary GTI file #################################### unlink $ontarget; } ################################################################# # GTI of pointings where we are actually pointing in the nominal # direction. Call this 'tp' for 'target pointing. ################################################################# if( -f $pointing ){ my $maketime = Util::Ftool->new('maketime') ->params({infile => $attitude.'[ATTITUDE]', compact => 'no', outfile => 'gti.tmp', prefr => 1, postfr => 1, expr => 'near(POINTING[1],' .$jobpar->read('ra') .',1.0) ' . ' && near(POINTING[2],' .$jobpar->read('dec') .',1.0)' }) ->run(); Util::Ftool->new('mgtime') ->params({ingtis => $pointing .' gti.tmp', outgti => $target_pointing, merge => 'AND'}) ->run(); unlink 'gti.tmp'; } ################################################################# # 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", "*"), $filename->get("telemetry", "*", "head2", 485) ); 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); ############################################## # Add a buffer of 60 seconds to start and stop ############################################## my $tstart = $fits->keyword("TSTART") - 60.0; my $tstop = $fits->keyword("TSTOP") + 60.0; 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 => 'CALDB', 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 => 'CALDB' }); $log->entry('Run aberrator'); $aberration->run(); } # end of correct_aberration