Download

# After running pnEvents and MOSEvents, determine the TLMIN/TLMAX values that will encompass data from all
# EPIC cameras, and write these values to the imaging mode events files. This is initially aimed at allowing
# us to produce single images from all exposures within a mosaic.

package EPICImageSize;
use strict;
use English;
use Carp;
use vars
  qw(@ISA $VERSION $name $author $date $version @instList $numberOfStreams);
@ISA = qw(Module);
use ModuleResources;
use ModuleUtil;
use POSIX; # for ceil

# Declare identity, version, author, date, etc.
$name    = __PACKAGE__;
$VERSION = '0.09';
$version = $VERSION;
$author  = 'Ed Chapin, Jose Vicente Perea';
$date    = '2021-03-26';

#
# ChangeLog
# =========
#
# version 0.09 - 2021-03-26 JVP
# ------------
#
# + EPIC Merged Flare Background timeseries
#   Final product moved from EPICEXP to EPICOBS group.
#   Re-defined in Filemap as 'EPIC instrument flaring background'
#
# version 0.08 - 2021-03-23 JVP
# ------------
#
# + Merged flare bkg timeseries
#   Correction: instrument definition in the plot filename
#
# version 0.07 - 2021-03-22 JVP
# ------------
#
# + Plot of the Merged flare bkg timeseries : X000FBKTSR*.PDF/PNG
#   when there are more than 1 exposure per instrument
#
# version 0.06 - 2021-01-18 JVP
# ------------
#
# + Merged flare bkg timeseries to final product: X000FBKTSR
#   when there are more than 1 exposure per instrument
#
# version 0.05 - 2020-12-03 JVP
# ------------
#
# + New module section:
#   If more than one exposure per EPIC instrument, then
#   Look for a common optimized flare background filter threshold from the merged timeseries
#   of flare background timeseries from individual exposures.
#   It has been included in this module because it runs once per all instruments/exposures
#   just before making the EPIC images.
#
# version 0.04 - 2018-07-25 JVP
# ------------
#
# + Writing down the same Legal Limits ( tlmin6/7 , tlmax6/7 ) also into the FWC and OOT imaging mode event list
#
# version 0.03 - 2014-02-19 EC
# ------------
#
# + from PR: account for GTIs from preqgti, re-calculate tlmin/max from X,Y in events files
#
# version 0.02 - 2014-01-23 EC
# ------------
#
# + Remove ppd("000005") call when no imaging events found
#
# version 0.01 - 2013-02-06 EC
# ------------
#
# + First draft.

# Declare list of instruments this module is interested in
@instList = qw(epic);

# Number of streams
sub numberOfStreams
{
    1;
}

#
sub evaluateRules
{
    my $inst = thisInstrument;

    # Ignore this module only if both the pnEvents and MOSEvents modules are completely ignored
    return ignore()
        if allIgnored( module => 'pnEvents' ) and allIgnored( module => 'MOSEvents' );

    # Otherwise we should only start if all pnEvents and MOSEvents instances are either complete
    # or ignored
    start()
        if (allComplete( module => 'pnEvents' ) and allComplete( module => 'MOSEvents' ));
}

#
sub performAction
{
    info("Module version number: $version");

    # Find all imaging event files
    my @evFiles= ( findFile(class => 'product', content => 'EPIC PN imaging mode event list'),
                   findFile(class => 'product', content => 'EPIC MOS imaging mode event list') );

    # Find attitude GTI file
    my $attGTIFile = findFile(
                              class => 'intermediate'
                              , instrument => 'all'
                              , content => 'attitude GTI'
                              , required => 'true'
                              );

    info("global Attitude GTI File: $attGTIFile");

    # Return immediately if there are no event files
    if ($#evFiles<0)
    {
         return success();
    }

    # load in the GTI data
    my $gti = readFITSTable( file => $attGTIFile,
                             extension => "STDGTI",
                             colname => [qw( START STOP )] );

    # find absolute minimum and maximum data ranges of X,Y columns,
    # TLMIN/TLMAX, defining the actual image size

    my ($tmin6, $tmax6, $tmin7, $tmax7);

    foreach my $evFile (@evFiles)
    {

        my $ev = readFITSTable( file => $evFile,
                                extension => "EVENTS",
                                colname => [qw( TIME X Y )] );

        my $xnull = readFITSKeyword( file => $evFile
                                     ,extension => 'EVENTS'
                                     ,keyword => 'TNULL6');

        my $ynull = readFITSKeyword( file => $evFile
                                     ,extension => 'EVENTS'
                                     ,keyword => 'TNULL7');

        info("X,Y NULL values: $xnull , $ynull\n");

        my $xmin;
        my $xmax;
        my $ymin;
        my $ymax;
        my $init=1;  # unset once values are set to something

        # Loop over events
        for my $j ( 0 .. $#{$ev->{TIME}} ) {

            my $time = ${$ev->{TIME}}[$j];
            my $x = ${$ev->{X}}[$j];
            my $y = ${$ev->{Y}}[$j];

            # Loop over GTIs
            for my $i ( 0 .. $#{$gti->{START}} ) {

                # Is the event within the GTI?
                if ( $time >= ${$gti->{START}}[$i] &&
                     $time <= ${$gti->{STOP}}[$i] &&
                     $x != $xnull &&
                     $y != $ynull ) {

                    if ( $init ) {
                        $xmin = $x;
                        $xmax = $x;
                        $ymin = $y;
                        $ymax = $y;
                        undef $init;
                        info("Initial: $xmin $xmax $ymin $ymax");
                    } else {
                        if ($x < $xmin) { $xmin = $x; }
                        if ($x > $xmax) { $xmax = $x; }
                        if ($y < $ymin) { $ymin = $y; }
                        if ($y > $ymax) { $ymax = $y; }

                        unless( defined($xmin) && defined($xmax) && defined($ymin) && defined($ymax) ) {
                            info("Huh? Undefined bounds: xmin=$xmin xmax=$xmax ymin=$ymin ymax=$ymax");
                        }
                    }
                }
            }
        }

        info("First pass $evFile: xmin=$xmin xmax=$xmax ymin=$ymin ymax=$ymax");

        # Calculate quantized values

        my $thistmin6;
        my $thistmax6;
        my $thistmin7;
        my $thistmax7;

        if( defined($xmin) && defined($xmax) && defined($ymin) && defined($ymax) ) {
            my $xminscaled = ceil(-1.0*$xmin/3600.0);
            my $xmaxscaled = ceil(($xmax-51840)/3600.0);
            my $yminscaled = ceil(-1.0*$ymin/3600.0);
            my $ymaxscaled = ceil(($ymax-51840)/3600.0);

            $thistmin6= 1-3600*( ($xminscaled > 0) ? $xminscaled : 0 );
            $thistmax6 = 51840+3600*( ($xmaxscaled > 0) ? $xmaxscaled : 0);
            $thistmin7= 1-3600*( ($yminscaled > 0) ? $yminscaled : 0 );
            $thistmax7 = 51840+3600*( ($ymaxscaled > 0) ? $ymaxscaled : 0);

            # the previous code should be doing the following pseudocode:
            #$thistmin6= 1-3600*max(0,ceil(-1.0*$xmin/3600.0)) ;
            #$thistmax6 = 51840+3600*max(0,ceil(($xmax-51840)/3600.0));
            #$thistmin7 = 1-3600*max(0,ceil(-1.0*$ymin/3600.0));
            #$thistmax7 = 51840+3600*max(0,ceil(($ymax-51840)/3600.0));
        } else {
            info("No valid events during GTIs found in $evFile.");
        }

         # Old method based on FITS keywords
        #my $thistmin6 = int(readFITSKeyword(file => $evFile, extension => 'EVENTS', keyword => 'TLMIN6'));
        #my $thistmax6 = int(readFITSKeyword(file => $evFile, extension => 'EVENTS', keyword => 'TLMAX6'));
        #my $thistmin7 = int(readFITSKeyword(file => $evFile, extension => 'EVENTS', keyword => 'TLMIN7'));
        #my $thistmax7 = int(readFITSKeyword(file => $evFile, extension => 'EVENTS', keyword => 'TLMAX7'));

        info("First pass $evFile: tmin6=$thistmin6 tmax6=$thistmax6 tmin7=$thistmin7 tmax7=$thistmax7");

        if ($evFile eq $evFiles[0])
        {
            # initialize values if this is the first file
            $tmin6 = $thistmin6;
            $tmax6 = $thistmax6;
            $tmin7 = $thistmin7;
            $tmax7 = $thistmax7;
        }
        else
        {
            # otherwise check to see if we need to extend the extrema
            $tmin6 = $thistmin6 if $thistmin6 < $tmin6;
            $tmax6 = $thistmax6 if $thistmax6 > $tmax6;
            $tmin7 = $thistmin7 if $thistmin7 < $tmin7;
            $tmax7 = $thistmax7 if $thistmax7 > $tmax7;
        }

        info("Current max ranges: $tmin6 $tmax6 $tmin7 $tmax7");
    }

    unless( defined($tmin6) && defined($tmax6) && defined($tmin7) && defined($tmax7) ) {
        info("No valid events found, so TLMIN/MAX will not be updated");
        return success();
    }

    info("Full set of EPIC events encompassed by TLMIN6=$tmin6 TLMAX6=$tmax6 TLMIN7=$tmin7 TLMAX7=$tmax7");

    # Loop again over events files and write out the extra for the maximum legal ranges TLMIN/TLMAX
    foreach my $evFile (@evFiles)
    {

        # addattribute won't let us write TLMIN/MAX keywords as they are special keywords

        #doCommand( 'addattribute'
        #           ,attributename => [qw(TLMIN6 TLMAX6 TLMIN7 TLMAX7)]
        #           ,attributetype => [qw(integer integer integer integer)]
        #           ,integervalue => [ $tmin6, $tmax6, $tmin7, $tmax7 ]
        #           , set => ["$evFile:EVENTS"]
        #    ) or exception();

        info("Writing TLMIN/MAX{6,7} to $evFile. TLMIN6=$tmin6 TLMAX6=$tmax6 TLMIN7=$tmin7 TLMAX7=$tmax7");
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMIN6" => {value => $tmin6});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMAX6" => {value => $tmax6});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMIN7" => {value => $tmin7});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMAX7" => {value => $tmax7});

    }

    # Loop also over imaging events files for FWC and OOT
    my @evFilesFWCandOOT= ( findFile(class => 'intermediate', content => 'EPIC PN imaging mode FWC event list'),
                            findFile(class => 'intermediate', content => 'EPIC PN imaging mode OOT event list'),
			    findFile(class => 'intermediate', content => 'EPIC MOS imaging mode FWC event list') );

    foreach my $evFile (@evFilesFWCandOOT)
    {
        info("Writing TLMIN/MAX{6,7} to $evFile. TLMIN6=$tmin6 TLMAX6=$tmax6 TLMIN7=$tmin7 TLMAX7=$tmax7");
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMIN6" => {value => $tmin6});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMAX6" => {value => $tmax6});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMIN7" => {value => $tmin7});
        FITSUtils::writeKeyword($evFile, "EVENTS", "TLMAX7" => {value => $tmax7});

    }

    ###########################################################################################
    # Looking for a common flare background filtering threshold for cases with #exposures > 1
    #
    foreach my $inst (qw(epn emos1 emos2)){
         my @FBKTSRFiles = findFile(
                                    class => 'product'
                                    , instrument => $inst
                                    #, exp_id => $exp_id
                                    , content => 'EPIC flare background timeseries'
                                    , format => 'FITS'
                                    );
         foreach my $f (@FBKTSRFiles){
                if( ! hasFITSKeyword(file => $f, extension => 'RATE', keyword => 'BACKSCAL') ){
                     info("Wrong EPIC flare background timeseries $f. BACKSCAL keyword not found. File excluded from timeseries list.");
                     @FBKTSRFiles = grep {$_ ne $f} @FBKTSRFiles;}	# File $f removed from @FBKTSRFiles list
            }

         if ( scalar(@FBKTSRFiles) > 1){
            info("There are more than one exposure for $inst.");
            info("Looking for a common optimized flare background filter threshold from the merged timeseries");

            my $mergedBackRates = newFile(
                                       class => 'product'
                                       , instrument => $inst
                                       , content => 'EPIC instrument flaring background'
                                       , format => 'FITS'
                                       );
                                       # No exp_id set => merged timeseries (X000)

            my $ratesFiles = join(',', @FBKTSRFiles);
            info("Merging timeseries: $ratesFiles");
            doCommand(
                      'mergeEPICflareBkgTS.py'
                      , ratesFiles => $ratesFiles
                      , mergedRates => $mergedBackRates
                      ) or return exception();

            info("Merged timeseries: $mergedBackRates");

            my $snDiagFile = newFile(
                                     class => 'intermediate'
                                     , instrument => $inst
                                     , content => 'Merged Flare GTI SN diagnostic'
                                     , band => 8
                                     , format => 'FITS'
                                     );

            my $retval = 0;
            my $rcut = `bkgoptrate -V 0 -w 0 tssettabname=${mergedBackRates}:RATE doinsertkwds=yes dooutputsntab=yes ignorelowcnttail=yes snsettabname=${snDiagFile}:SN_TO_BKGCUT`;
            info("CMD: bkgoptrate -V 0 -w 0  tssettabname=${mergedBackRates}:RATE doinsertkwds=yes dooutputsntab=yes ignorelowcnttail=yes snsettabname=${snDiagFile}:SN_TO_BKGCUT");
            $retval = $?;
            if ($retval ne 0) { return exception("Error $retval returned by bkgoptrate: $!"); }

            my $optFlareGti;
            if ($rcut eq '') {
                info("bkgoptrate call returns no value, skipping optimised flare GTI generation for merged timeseries...");
            } else {
                info("bkgoptrate returns optimum cut threshold: $rcut");

                    # append SN_TO_BKGCUT table to FBKTSR product
                    doCommand(
                              'ftappend'
                              , infile => "${snDiagFile}[SN_TO_BKGCUT]"
                              , outfile => $mergedBackRates
                              ) or return exception();

                    # add FLCUTTHR header value
                    doCommand(
                              'addattribute'
                              , set => "${mergedBackRates}:RATE"
                              , attributename => 'FLCUTTHR'
                              , attributetype => 'real'
                              , attributecomment => '"\"Optimised flare cut threshold\""'
                              , realvalue => $rcut
                              ) or return exception();


                    # Now generate the optimised flare GTI files
                    $optFlareGti = newFile(
                                           class => 'intermediate'
                                           , instrument => $inst
                                           , content => "Optimised flare GTI from merged timeseries"
                                           , band => 8
                                           , format => 'FITS'
                                           );

                    info("Creating GTI files with this threshold: $rcut");
                    doCommand(
                              'tabgtigen'
                              , table => $mergedBackRates
                              , gtiset => $optFlareGti
                              , expression => "RATE < ${rcut}"
                              , mingtisize => 100
                              ) or return exception();

                    info("Make full in-band, source-free merged light curve plots");
                    #
                    if ( hasFITSExtension(file => $mergedBackRates, extension => "RATE") &&
                         hasFITSExtension(file => $mergedBackRates, extension => "SN_TO_BKGCUT") ){
                         my $flareBkgPDF0 = newFile(
                                      class => 'product'
                                      , instrument => $inst
                                      , content => 'EPIC instrument flaring background'
                                      , format => 'PDF'
                                      );
                         my $flareBkgPNG0 = newFile(
                                      class => 'product'
                                      , instrument => $inst
                                      , content => 'EPIC instrument flaring background'
                                      , format => 'PNG'
                                      );
                         doCommand(
                                   'plotFlrBkgTimeSeries.py'
                                   , rateset  => $mergedBackRates
                                   , plotfile => $flareBkgPNG0
                                   , pdfplotfile => $flareBkgPDF0
                                   , plotformat => "both"
                                  ) or return exception();
                    } else {
                            info("No RATE or SN_TO_BKGCUT extensions found in source-free merged light curve. No plot created.");
                    }

            }

         }
    }


    # Raise success flag
    return success();
}
1;