#!/usr/bin/env python # """ This file is part of ESA's XMM-Newton Scientific Analysis System (SAS). SAS 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 3 of the License, or (at your option) any later version. SAS 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 SAS. If not, see . Name = 'srcTimeSeriesEnergyBands' VERSION = '1.4'; author = 'Jose Vicente Perea'; date = '2018-06-07'; ChangeLog ========= Version 1.4 - 2018-06-07 (JVP) ------------ + Include Uncorrected Light Curves only if the corresponding Corrected ones exist. We could have the uncorrected LCs per E band for a Corrected LC not selected for the final products (ex.: source spectrum with not enough number of counts). Version 1.3 - 2018-05-16 (JVP) ------------ + Important change to ensure good merging of tables. Tables join option : join_type='right' Version 1.2 - 2018-05-08 (JVP) ------------ + Final FITS file : write RATE table and modified header in different 'open file' instances to avoid writing conflicts Version 1.1 - 2018-05-04 (JVP) ------------ + Light Curves per Energy Band and per Exposure a intermediate/'srcTimeSeriesEnergyBands-correctedLightCurve-' + + '-plus_Rates_per_Energy_band.ftz' Some verbose messages included Module searching order removed : sys.path.insert() Version 1.0 - 2018-04-24 (JVP) ------------ + Initial version Task for Light Curves merging per Energy band """ __version__ = "1.4" __author__ = "Jose V. Perea" __copyright__ = "ESA (C) 2000-2018" __license__ = "GPL" #print( __doc__ ) #------------------------------------------------------------------------------------------------ def usage(): #------------------------------------------------------------------------------------------------ ''' Help. ''' print(""" usage: srcTimeSeriesEnergyBands.py --allEnergyRangeCorrLC=<...> --SrcLCsperEnergyBand=<...> --BkgLCsperEnergyBand=<...> [options] output: ... INTRODUCTION The srcTimeSeriesEnergyBands task reads all the EPIC whole Energy band corrected (epiclccorr output) Light Curve FITS files (all exposures), then all uncorrected Light Curves per Energy band. Uncorrected LCs are corrected using the 'BKGRATIO' value from the corrected ones. All the resulting corrected LCs (whole E range LC + LCs per E band) are merged in the same FITS file table. A mereged EPIC LCs plot is also created. OPTIONS: Input: --allEnergyRangeCorrLC= Input EPIC corrected Light Curves Output from 'epiclccorr' --SrcLCsperEnergyBand Uncorrected Source Light Curves. All Energy bands --BkgLCsperEnergyBand Uncorrected Background Light Curves. All Energy bands Output: -o, --outfits= Output FITS file. All Light Curves included ( whole E range LC + LCs per E band ) --plotting= Plot all the merged Light Curves (Yes/No, Y/N). Default: Yes --plotfile= Plot file name. Default: figure.png --plotformat={png,pdf} Output figure format. Default: png --interactplot Output plot in an interactive graphics window (Y) or in a PNG file (N) (not implemented yet) --verbose= Increase verbosity (Yes/No, Y/N) -v, --version Show version message Help: -h, --help Print this help. DETAILS ... LICENCE ... DOCUMENTATION ... ... =================================================================================================== """) #------------------------------------------------------------------------------------------------ def error(string): ''' prints the string in red. ''' #import sys #sys.stdout.write("\033[39;31m" + string + "\033[39;29m") # Red color string sys.stdout.write( string ) # No color string def version(version_num): ''' prints version number. ''' #import sys #versionString = sys.argv[0].split('/')[-1] + ' ' + version_num versionString = '{0} ({0}-{1}) [-]'.format(sys.argv[0].split('/')[-1], version_num) print(versionString) def main(arguments): # ---------------------------------------------------------------------------------------- ''' This is the main function doing the full loop. ''' import getopt ### sys.path.insert(0,"/lhome/jperea/ppsdev/pcms/pipeline/python/") import ModuleUtil version_num = __version__ # Version number to be updated verbose = False allEnergyRangeCorrLC = '' SrcLCsperEnergyBand = '' BkgLCsperEnergyBand = '' outfits = 'output.fits' pn_timing = False plotting = True points_density = 512 plotfile = "figure.png" plotformat = 'png' interactplot = False arguments = arguments.split() progName = arguments[0] argsList = arguments[1:] try: opts, args = getopt.getopt(argsList, "vo:h", ["allEnergyRangeCorrLC=", "SrcLCsperEnergyBand=", "BkgLCsperEnergyBand=", "outfits=", \ "plotting=", "plotfile=", "plotformat=", "interactplot=", \ "version", "verbose=", "help"]) except getopt.GetoptError as err: usage() error("ERROR: Could not understand the options!\n\n") print(err) # will print something like "option -a not recognized" print("================================================================================================") return for o, a in opts: if o in ("-v", "--version"): version(version_num) return 0 if o in ("-V", "--verbose"): verbose = ModuleUtil.varCheck(client = progName, inVar = a).checkBool() if o in ("-h", "--help"): usage() return 0 if o in ("--allEnergyRangeCorrLC"): allEnergyRangeCorrLC = a if o in ("--SrcLCsperEnergyBand"): SrcLCsperEnergyBand = a if o in ("--BkgLCsperEnergyBand"): BkgLCsperEnergyBand = a if o in ("-o", "--outfits"): outfits = a if o in ("--plotting"): plotting = ModuleUtil.varCheck(client = progName, inVar = a).checkBool() if o in ("--plotfile"): plotfile = a if o in ("--plotformat"): plotformat = a # If plotfile name already has a valid extension (png or pdf): keep it. if plotfile.lower().endswith(('.png', '.pdf')): plotfile = plotfile else: plotfile = plotfile + '.' + plotformat # Convert input string in lists allEnergyRangeCorrLC = allEnergyRangeCorrLC.split(',') SrcLCsperEnergyBand = SrcLCsperEnergyBand.split(',') BkgLCsperEnergyBand = BkgLCsperEnergyBand.split(',') if verbose: import datetime time1 = datetime.datetime.utcnow() print('\nImporting main modules ...\t', time1) import warnings import numpy as np from astropy.table import Table, join, vstack from astropy.io import fits from astropy.time import Time from astropy.coordinates import SkyCoord from astropy import units as u if verbose: time2 = datetime.datetime.utcnow() print('......Done\t\t\t', time2, '\nImport modules time = ',time2 - time1, '\n') # ################################################################################################################################################## uncorrectedSrcRateSet_all_set = SrcLCsperEnergyBand uncorrectedBkgRateSet_all_set = BkgLCsperEnergyBand correctedAllEnergyRange_all_set = allEnergyRangeCorrLC # ################################################################################################################################################## # # Inputs for the Python task: # # - correctedTemp_all_set[0] # - uncorrectedSrcRateSet_all_set # - uncorrectedBkgRateSet_all_set # # Previous has to be done from the Perl module: LCMerge.pm : # # Input for each SRC and EXPOSURE ?: # # WARNING: SRC_NUM = 0 ==> Timing # # Number of Exposures # #allInputLCs = uncorrectedSrcRateSet_all_set + uncorrectedBkgRateSet_all_set + correctedAllEnergyRange_all_set exposures = [] correctedLCfiles = {} uncorrectedSrcRateSets = {} uncorrectedBkgRateSets = {} for file in correctedAllEnergyRange_all_set: exp = fits.getval(file, 'EXPIDSTR', 0) correctedLCfiles[exp] = file if exp not in exposures: exposures.append(exp) for exp in exposures: uncorrectedSrcRateSets[exp] = [] uncorrectedBkgRateSets[exp] = [] skipped_exps = [] for file in uncorrectedSrcRateSet_all_set: # Dict per exposures containing all the uncorrected LCs, bamds: 1,2,3,4,5. Source exp = fits.getval(file, 'EXPIDSTR', 0) if exp in exposures: uncorrectedSrcRateSets[exp].append(file) else: skipped_exps.append(exp) print('There is not corrected LC for exposures', set(skipped_exps),'. Skipping this exposure.' ) for file in uncorrectedBkgRateSet_all_set: # Dict per exposures containing all the uncorrected LCs, bamds: 1,2,3,4,5. Background exp = fits.getval(file, 'EXPIDSTR', 0) if exp in exposures: uncorrectedBkgRateSets[exp].append(file) # Corrected Time Series ( from 'epiclccorr' ) # print('--------- Corrected Light Curves. All Energy range -----------------------------------------------') bkgratio = {} TIMEDEL = {} exposureTimes = [] correctTIME = {} correctRATE = {} correctERROR = {} FRACEXP = {} correctBACKV = {} correctBACKE = {} for exp, correctedLCfile in correctedLCfiles.items(): print(exp, '\t', correctedLCfile) with fits.open( correctedLCfile ) as correctedRate: bkgratio[exp] = correctedRate['RATE'].header['BKGRATIO'] TIMEDEL[exp] = correctedRate['RATE'].header['TIMEDEL'] exposureTimes.append(correctedRate['RATE'].header['EXPOSURE']) correctTIME[exp] = correctedRate['RATE'].data['TIME'] correctRATE[exp] = correctedRate['RATE'].data['RATE'] correctERROR[exp] = correctedRate['RATE'].data['ERROR'] FRACEXP[exp] = correctedRate['RATE'].data['FRACEXP'] correctBACKV[exp] = correctedRate['RATE'].data['BACKV'] correctBACKE[exp] = correctedRate['RATE'].data['BACKE'] print('\tBKGRATIO from Corrected TS = ', bkgratio[exp], '\n') totalExposureTime = sum( exposureTimes ) print('\tTotal Exposure times from all Corrected TSs = ', totalExposureTime) print('--------- Corrected Light Curves. All Energy range -----------------------------------------------') #------------------------------------------------------------------- # Return a Dict where : # keys = Exposures # values = Data tables ( 'TIME', 'RATE', 'RATE_ERR' ) # def dict_of_tables_from_files( LCs_files ): output = {} energy_band_ranges = {} band_name = 99 for file in LCs_files: with fits.open( file ) as RateSet: try: pilo = RateSet['RATE'].header['CHANMIN'] pihi = RateSet['RATE'].header['CHANMAX'] except: print('CHANMIN or CHANMAX keywords missing in file.') print('Mandatory keywords to know what is the energy range of each input Time Series. Exiting.') sys.exit(1) try: id_band = RateSet['RATE'].header['ID_BAND'] except KeyError: band_name += 1 id_band = band_name print( 'ID_BAND keyword missing in file. An ID_BAND created: ', id_band ) #print('\t', file, '\t\tID_BAND, pilo, pihi = ', id_band, pilo, pihi ) energy_band_ranges[id_band] = (pilo, pihi) # ID_BAND = (pilo, pihi) definition t = RateSet['RATE'].data['TIME'] r = RateSet['RATE'].data['RATE'] Dr = RateSet['RATE'].data['ERROR'] table = Table([t, r, Dr], names=['TIME', 'RATE', 'RATE_ERR']) output[ id_band ] = table return (output, energy_band_ranges) # output = { '1': table, '2': table, ..., '5': table }, where table = ['TIME', 'RATE', 'RATE_ERR'] columns # energy_band_ranges = { 'id_band_1': (pilo, pihi), 'id_band_2': (pilo, pihi), ...} #------------------------------------------------------------------- # Find the Min and Max values of RATE* columns # (for plotting) # def getMinMax(hdu): minmax = {} allValues = [] for exp, hdul in hdu.items(): table = hdul.data minmax[exp] = [] for i in ['', '1','2','3','4','5']: minmax[exp].append( min( table['RATE'+i] ) ) minmax[exp].append( max( table['RATE'+i] ) ) allValues += minmax[exp] #print('\n', allValues, '\n', len(allValues) ) ylimits = [ min(allValues), max(allValues) ] return ylimits #------------------------------------------------------------------- # Plot all Time Series in different panels # def plot_all_LCs( hdu, energy_ranges = '' ): import matplotlib matplotlib.use('Agg') # To avoid problems with DISPLAY variable # raise RuntimeError('Invalid DISPLAY variable') # RuntimeError: Invalid DISPLAY variable import matplotlib.pyplot as plt for exp, bands in energy_ranges.items(): n_bands = len(bands) # Plot Time Series only if the number of Energy Bands is < 5 if n_bands > 5: print('Exposure '+ exp + ' has more than 5 energy bands. Plot not produced.') sys.exit(0) #fig, ax = plt.subplots(3, 2) fig, ax = plt.subplots(6, sharex=True ) fig.suptitle('TimeSeries per Energy Band', fontsize=12) fig.set_size_inches(8.0,10.0) #pos = [(x, y) for x in [0,1,2] for y in [0,1]] # Panel Positions array #pos = [0,1,2,3,4] pos = [1,2,3,4,5] colors = {0: 'tab:red', 1: 'tab:orange', 2: 'tab:green', 3: 'tab:blue', 4: 'tab:purple'} ###### ylimits = getMinMax( hdu ) new_time_bin = (TSTOP - TSTART) / ( points_density - 1 ) # (Total Exposure time. All Exposures included) / ( # points in plot ) t_ref = TSTART #===================================================================================================================== # Reference Time in UTC: # Reference values from the first Exposure EPIC LC # timesys, timeref, mjdref = TT LOCAL 50814.0 if 'mjdref' in locals() and 'timesys' in locals(): t_ref_utc = Time(mjdref + t_ref/86400,format='mjd',scale=(timesys).lower(), precision=0).utc.iso # Conversion to UTC if verbose: print('Minimum TSTART from all Timeseries: Reference Time (UTC) = ', t_ref_utc) else: t_ref_utc = t_ref # No UTC conversion if verbose: print('Minimum TSTART from all Timeseries: Reference Time could not be converted to UTC = ', t_ref) #===================================================================================================================== for exp, hdul in hdu.items(): if verbose: print('Plotting data from Exposure : ', exp) table = hdul.data time_bin = TIMEDEL[exp] t_bin_frac = int(np.round( new_time_bin / time_bin )) if t_bin_frac <= 1: rebin = False # ==> No re-binning print(' No re-binning needed for this Light Curve. Input / Output time bins ratio : ', t_bin_frac) else: rebin = True # All Energy range (Band 8) : tsr = Table([ table['TIME'], table['RATE'], table['ERROR'], table['FRACEXP'] ], names=['TIME', 'RATE', 'ERROR', 'FRACEXP']) if rebin: if verbose: print('\tStart re-binning for ALL Bands (band = 8) ...') ntsr = ModuleUtil.TSrebin(tsr, time_bin, new_time_bin) # import module 'ModuleUtil' required else: ntsr = tsr ntsr['TIME'] = ntsr['TIME'] - t_ref ranges = energy_ranges[exp] sortedEnergyRanges = sorted( ranges, key=ranges.get ) # Energy ID_BAND's sorted by Energy #ax[5].set_title('Band 8: {}-{} eV'.format( ranges[sortedEnergyRanges[0]][0] , ranges[sortedEnergyRanges[-1]][1]), fontsize=10, fontweight='bold') ax[0].annotate ('Band 8: {}-{} eV'.format( ranges[sortedEnergyRanges[0]][0] , ranges[sortedEnergyRanges[-1]][1]), xy=(0.01, 0.03), xycoords='axes fraction', fontsize=10) ax[0].errorbar( ntsr['TIME'], ntsr['RATE'], ntsr['ERROR'], fmt='.', c='k', ms=3, ecolor='k') #ax[2,1].set_ylim( ylimits ) #xticks = ax[5].get_xticks() idx = -1 for id_band in sortedEnergyRanges: idx += 1 #if verbose: print('idx: ', idx, '\tpos: ',pos[idx], '\tid_band: ',id_band, '\tranges: ',ranges[id_band]) # Re-Binning for Plot --------------- tsr = Table([ table['TIME'], table['RATE'+str(id_band)], table['RATE'+str(id_band)+'_ERR'], table['FRACEXP'] ], names=['TIME', 'RATE', 'ERROR', 'FRACEXP']) if rebin: if verbose: print('\tStart re-binning for ID_BAND: ', id_band) ntsr = ModuleUtil.TSrebin(tsr, time_bin, new_time_bin) # import module 'ModuleUtil' required else: ntsr = tsr ntsr['TIME'] = ntsr['TIME'] - t_ref if verbose: print('\t\tRe-Binning, Exp: ' + exp + '\tID_BAND: '+ str(id_band) + '\ttime_bin = ', time_bin, '\tnew_time_bin = ', new_time_bin) #------------------------------------ #ax[ pos[idx] ].set_title('Band '+str(id_band)+': {}-{} eV'.format(ranges[id_band][0], ranges[id_band][1]), fontsize=10) ax[ pos[idx] ].annotate('Band '+str(id_band)+': {}-{} eV'.format(ranges[id_band][0], ranges[id_band][1]), xy=(0.01, 0.03), xycoords='axes fraction', fontsize=10) ax[ pos[idx] ].errorbar( ntsr['TIME'], ntsr['RATE'], ntsr['ERROR'], fmt='.', c=colors[idx], ms=3, ecolor='k') #ax[ pos[idx] ].scatter( table['TIME'], table['RATE'+str(id_band)], c=colors[idx], s=3) #ax[ pos[idx] ].plot ( table['TIME'], table['RATE'+str(id_band)], c=colors[idx], lw=1) #ax[ pos[idx] ].scatter( table['TIME'], table['BACK'+str(id_band)+'V'], c='k', s=2) #ax[ pos[idx] ].set_xticks(xticks) fig.subplots_adjust(hspace=0.0) # Horizontal shift between plots plt.annotate('OBS_ID: {0}'.format(obs_id), xy=(0.13, 0.93), xycoords='figure fraction') plt.annotate('Instrument: {0}'.format(instrument), xy=(0.13, 0.91), xycoords='figure fraction') if 'srcnum' in locals(): plt.annotate('EPIC SRCNUM: {0}'.format(srcnum), xy=(0.58, 0.93), xycoords='figure fraction') plt.annotate('RA,Dec: {0}'.format(coords_hmsdms), xy=(0.58, 0.91), xycoords='figure fraction') if pn_timing: plt.annotate('EPN Timing', xy=(0.58, 0.93), xycoords='figure fraction') plt.annotate("rate (count/s)", xy=(0.05, 0.6), xycoords='figure fraction', rotation=90) plt.xlabel('time (s) after {0} (UTC)'.format(t_ref_utc)) #plt.show() print('\nSaving figure ', plotfile, ' ...') #plt.savefig(figure, orientation='portrait', papertype='a4') plt.savefig(plotfile) #------------------------------------------------------------------- print('\n--------- Un-Corrected Light Curves per Energy band -----------------------------------------------') SrcUncorrected_Band = {} BkgUncorrected_Band = {} src_energy_ranges = {} bkg_energy_ranges = {} print('Reading files for Source ...') for exp, uncorrectedSrcRates_per_Band in uncorrectedSrcRateSets.items(): (SrcUncorrected_Band[exp], src_energy_ranges[exp]) = dict_of_tables_from_files( uncorrectedSrcRates_per_Band ) # SrcUncorrected_Band['EXPOSURE'] = [ Ban 1 table, Band 2 table, ... ] print('Reading files for Background ...') for exp, uncorrectedBkgRates_per_Band in uncorrectedBkgRateSets.items(): (BkgUncorrected_Band[exp], bkg_energy_ranges[exp]) = dict_of_tables_from_files( uncorrectedBkgRates_per_Band ) # BkgUncorrected_Band['EXPOSURE'] = [ Ban 1 table, Band 2 table, ... ] # # Check Energy bands of Src and Bkg files # If Energy bands in Src an Bkg are not the same we can not get the Corrected Time Series per Energy band # print('Checking Src and Bkg Energy Bands ...') for exp, bands in src_energy_ranges.items(): if src_energy_ranges[exp] != bkg_energy_ranges[exp]: print('Energy bands for Source and Background differ in exposure', exp, '. Exiting.') print('\t\t{band: (pilo, pihi), band: (pilo, pihi), ...}') print('Src bands:\t', src_energy_ranges[exp]) print('Bkg bands:\t', bkg_energy_ranges[exp]) sys.exit(1) for id_band, ranges in bands.items(): print(exp, '\tID_BAND: ',id_band, '\tEnergy range: ',ranges, 'eV') print('-----------------------------------------------------------') np.seterr(divide='ignore', invalid='ignore') # Set Treatment for "division by zero" and "invalid floating-point operation" to 'ignore' correctedTables = {} for exp in SrcUncorrected_Band.keys(): if verbose: print('\t\t EXPOSURE = ', exp) if verbose: print('\t\t BKGRATIO = ', bkgratio[exp]) if verbose: print('\t\t FRACEXP = ', FRACEXP[exp]) correctedTables[exp] = {} bands = src_energy_ranges[exp].keys() for band in bands: if verbose: print('\t\t\t Band = ', band) #--------------------------------------------------------------------- # Src and Bkg TIME, RATE, and ERROR's # t = SrcUncorrected_Band[exp][band]['TIME'] r = SrcUncorrected_Band[exp][band]['RATE'] Dr = SrcUncorrected_Band[exp][band]['RATE_ERR'] b = BkgUncorrected_Band[exp][band]['RATE'] Db = BkgUncorrected_Band[exp][band]['RATE_ERR'] srcRATE = (r - (b/bkgratio[exp])) / (FRACEXP[exp]) srcRATE_ERROR = ( 1/FRACEXP[exp] ) * ( (Dr)**2 + (Db/bkgratio[exp])**2 )**(1/2) bkgRATE = b/( bkgratio[exp]*FRACEXP[exp] ) bkgRATE_ERROR = Db/( bkgratio[exp]*FRACEXP[exp] ) # Corrrected Time Series per Energy Band: bandstr = str(band) correctedTables[exp][band] = Table([t, srcRATE, srcRATE_ERROR, bkgRATE, bkgRATE_ERROR], names=['TIME', 'RATE'+bandstr, 'RATE'+bandstr+'_ERR', 'BACK'+bandstr+'V', 'BACK'+bandstr+'E']) if verbose: print('\t\t\t\t Table size = ', len( correctedTables[exp][band] )) # #--------------------------------------------------------------------- # Write down 'finalTableJoin' into the original Time Series file : correctedLCfile #----------------------------------------------------------------------------------------------------------------- # # 1) Copy the file: # from shutil import copy intermediate = 'intermediate/' # To be modified in a final SAS task # Loop running over the EXPOSUREs # correctedLCfiles (product/) # 1) Copy to intermediate/ # 2) Add RATE1, RATE2, etc to Corrected from each EXPOSURE # 3) Save it as temporal file. One file per EXPOSURE # hdu = {} orig_cols = {} new_cols = {} orig_header = {} print('Creating intermediate Light Curve files per exposure') for exp, correctedLCfile in correctedLCfiles.items(): # Loop running over the EXPOSUREs and the proper files # Copy filename = correctedLCfile.split('/')[-1] file = intermediate + 'srcTimeSeriesEnergyBands-correctedLightCurve-' + filename.split('.')[0] + '-plus_Rates_per_Energy_band.ftz' if verbose: print(exp, '\tCopy ', correctedLCfile, 'to', file) copy( correctedLCfile, file ) print('Populating intermediate file ', file , 'with Corrected Lihgt Curves per Energy band') # i) Open/Read the All Energy Range Time Series File (Band 8): *SRCTSR* -> hdu table # ii) Join the Corrected Time Series per Energy band (correctedTables[band]) to the previous hdu table # iii) Paste the new column values to the input FITS file with fits.open( file, mode='update' ) as hdul: orig_header[exp] = hdul['RATE'].header hdu[exp] = hdul['RATE'].data orig_cols[exp] = hdu[exp].columns # Columns Definitions: fitsColumnDefinitions = [] for band in correctedTables[exp].keys(): hdu[exp] = join(hdu[exp] , correctedTables[exp][band], keys='TIME', join_type='right') bandstr = str(band) fitsColumnDefinitions.append( fits.Column( name='RATE'+bandstr , format='E', unit='count/s', array=hdu[exp]['RATE'+bandstr]) ) fitsColumnDefinitions.append( fits.Column( name='RATE'+bandstr+'_ERR', format='E', unit='count/s', array=hdu[exp]['RATE'+bandstr+'_ERR']) ) fitsColumnDefinitions.append( fits.Column( name='BACK'+bandstr+'V' , format='E', unit='count/s', array=hdu[exp]['BACK'+bandstr+'V']) ) fitsColumnDefinitions.append( fits.Column( name='BACK'+bandstr+'E' , format='E', unit='count/s', array=hdu[exp]['BACK'+bandstr+'E']) ) fitsColumnDefinitions.append( fits.Column( name='EXP_ID' , format='A4' , array= [exp] * len(hdu[exp]) ) ) fitsColumnDefinitions.append( fits.Column( name='TIMEDEL' , format='D', unit='s' , array= [ TIMEDEL[exp] ] * len(hdu[exp]) ) ) new_cols[exp] = fits.ColDefs( fitsColumnDefinitions ) # Update the RATE extension => hdu = hdul['RATE'].data hdu[exp] = fits.BinTableHDU.from_columns( orig_cols[exp] + new_cols[exp], header=orig_header[exp] ) hdul['RATE'] = hdu[exp] hdul.flush() # Flush to the Complete HDUList == hdul print() #----------------------------------------------------------------------------------------------------------------- ################################################################################################################ # # Tables from table = Table.read( file, hdu='RATE' ) # print('Merging all intermediate Corrected Light Curves into the final product') table = {} allTables = [] times = {} for exp, correctedLCfile in correctedLCfiles.items(): #file = intermediate + correctedLCfile.split('/')[-1] filename = correctedLCfile.split('/')[-1] file = intermediate + 'srcTimeSeriesEnergyBands-correctedLightCurve-' + filename.split('.')[0] + '-plus_Rates_per_Energy_band.ftz' if verbose: print( 'Reading ...', exp, ' ... ', file ) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=Warning) # To ignore: WARNING: UnitsWarning: 'cts' did not parse as fits unit table[exp] = Table.read( file, hdu='RATE' ) times[exp] = ( table[exp]['TIME'][0], table[exp]['TIME'][-1] ) # First and Last time values # Vertical stack of all Exposure tables # 1) Sort the exposure names ( dict keys ) by Start Time ( times dict values ) sortedExposures = sorted( times, key=times.get ) # 2) Vertical stacking of teh tables ( table[exp] ) for exp in sortedExposures: allTables.append( table[exp] ) TSTART = allTables[0]['TIME'][0] # First Time value from the 1st LC TSTOP = allTables[-1]['TIME'][-1] # Last Time value from the last LC allTable = vstack( allTables, metadata_conflicts='silent' ) # Convert 'Table object' to 'BinTableHDU object' allTableHDU = fits.table_to_hdu( allTable ) # BinTableHDU object which is complient with writing the FITS Extension and Flush : # Lost: Header keywords comments # Write the Final Time Series file: # # Get the 1st Exposure file: exp_1st = sortedExposures[0] # 1st exposure == 1st exposure in time #file_1st = intermediate + correctedLCfiles[exp_1st].split('/')[-1] file_1st = correctedLCfiles[exp_1st] print('Copy (1st exposure Light Curve) ', file_1st, ' to ', outfits) # Copy copy( file_1st, outfits ) print('\nWriting all into the Final FITS file : ', outfits, '\n') # Write down the final stacked table into the 1st Exposure FITS file: with fits.open( outfits, mode='update' ) as hdul: # --- Get some important info from the original LC file --- if file_1st.find('PN') != -1 and file_1st.find('SRCTSR0000.F') != -1: pn_timing = True instrument = hdul['RATE'].header['INSTRUME'] obs_id = hdul['RATE'].header['OBS_ID'] objectID = hdul['RATE'].header['OBJECT'] #print('===========> ', instrument, obs_id, objectID, pn_timing) # Get keywords fo Source coordinates if instrument == 'EPN' or instrument == 'EMOS1' or instrument == 'EMOS2': try: ra_deg = hdul['RATE'].header['SRC_RA'] dec_deg = hdul['RATE'].header['SRC_DEC'] srcnum = hdul['RATE'].header['SRCNUM'] coord = SkyCoord(ra=ra_deg*u.degree, dec=dec_deg*u.degree) coords_hmsdms = coord.to_string('hmsdms') #print('===========> ', srcnum, ra_deg, dec_deg) except KeyError: pass # Get keywords for Time conversion try: timesys = hdul['RATE'].header['TIMESYS'] timeref = hdul['RATE'].header['TIMEREF'] mjdref = hdul['RATE'].header['MJDREF'] #print('===========> ', timesys, timeref, mjdref) except KeyError: pass # Write the Final Table into the FITS HDU extension 'RATE' = hdul['RATE'] hdul['RATE'] = allTableHDU hdul.flush() # - - - - # - - - - FITS Header modifications with fits.open( outfits, mode='update' ) as hdul: header = hdul['RATE'].header header['EXP_ID'] = ' '.join(exposures) header['EXPIDSTR'] = ' '.join(exposures) header['TSTART'] = TSTART header['TSTOP'] = TSTOP header['EXPOSURE'] = totalExposureTime header.remove('SLCTEXPR') header.remove('TIMEDEL') header.remove('BKGRATIO') try: header.remove('FLCUTTHR') except: pass header.remove('AVRATE') header.remove('CHISQUAR') header.remove('CHI2PROB') header.remove('N_POINTS') header.remove('FVAR') header.remove('FVARERR') i=0 for exp in correctedLCfiles.keys(): i +=1 header['BKGRAT'+str(i)] = ( bkgratio[exp], 'Ratio of BKG to SRC collection areas for ' + exp) for id_band, ranges in src_energy_ranges[exp].items(): chanmin_label = 'CHANMIN' + str(id_band) chanmax_label = 'CHANMAX' + str(id_band) header['CHAMI'+str(id_band)] = ( ranges[0], 'Minimum of the energy column ID_BAND '+ str(id_band) +', ' + exp) header['CHAMA'+str(id_band)] = ( ranges[1], 'Maximum of the energy column ID_BAND '+ str(id_band) +', ' + exp) # Write the Final modified Header into the FITS HDU extension 'RATE' = hdul['RATE'] hdul['RATE'].header = header hdul.flush() # - - - - if plotting: print ('Plotting Time Series ...') #dataFromFile = Table.read( outfits, hdu='RATE' ) #plot_all_LCs( dataFromFile ) plot_all_LCs( hdu, energy_ranges = src_energy_ranges ) sys.exit() ################################################################################################################## ################################################################################################################## # ------------------------------------------------------------------------------------------------------------------------ if __name__=='__main__': import sys main(" ".join(sys.argv)) # ------------------------------------------------------------------------------------------------------------------------