├── README.rst ├── addspec-bkg-counts.patch └── addspec.py /README.rst: -------------------------------------------------------------------------------- 1 | Summing/Merging/Adding X-ray point source spectra 2 | ================================================== 3 | 4 | The HEASOFT/ftools/addspec commands adds background spectra counts 5 | inconsistent with poisson statistic. 6 | 7 | You can detect this problem by seeing the number of counts in the various 8 | energy bins in the background spectrum. If they are 0,1,2,3,4, there is no 9 | problem, if they are 0,510,1020,1530 the file is incorrect. 10 | 11 | The python script (python addspec.py filelist prefix) provides and 12 | executes the correct mathpha command to create the correct background file. 13 | 14 | Improvements are welcome. 15 | 16 | Usage 17 | ------ 18 | 19 | :: 20 | 21 | $ addspec.py prefix file1.pha file2.pha file3.pha 22 | 23 | or:: 24 | 25 | $ addspec.py prefix @src.txt 26 | 27 | where src.txt contains the filenames (one line each). 28 | 29 | Licence 30 | -------- 31 | 32 | MIT 33 | -------------------------------------------------------------------------------- /addspec-bkg-counts.patch: -------------------------------------------------------------------------------- 1 | diff --git a/ftools/heasarc/src/addspec/addspec.f b/ftools/heasarc/src/addspec/addspec.f 2 | index 5ec4a7e..067c4ad 100644 3 | --- a/ftools/heasarc/src/addspec/addspec.f 4 | +++ b/ftools/heasarc/src/addspec/addspec.f 5 | @@ -524,9 +524,8 @@ c ========================== THE BACKFILE ============================ 6 | call susbak(chatter,backfil(i), 7 | & bexp(i), bbscal(i), ierr) 8 | if(ierr.NE.0) goto 482 9 | - backtexp = backtexp + bexp(i) 10 | + backtexp = backtexp + bexp(i) * bbscal(i) / backscal(i) 11 | enddo 12 | - backtexp = backtexp*bexpscale 13 | 14 | c --------------------------- MATHPHA ------------------------- 15 | c ... construct the command string and spawn MATHPHA task if necessary 16 | @@ -539,18 +538,11 @@ c ... construct the command string and spawn MATHPHA task if necessary 17 | goto 482 18 | endif 19 | 20 | - factor = backscal(1)*bexpscale/bbscal(1) 21 | - write(factstr,'(g12.6)') factor 22 | - lgstring = '('//backfil(1)//'*'//factstr//')' 23 | + lgstring = backfil(1) 24 | call crmvblk(lgstring) 25 | do i = 2, nfiles 26 | - factor = backscal(i)*bexpscale/bbscal(i) 27 | - write(factstr,'(g12.6)') factor 28 | - call crmvblk(factstr) 29 | ilen = clenact(lgstring) 30 | - ilen2 = clenact(factstr) 31 | - lgstring = lgstring(:ilen) // '+('//backfil(i)// 32 | - & '*'//factstr(:ilen2)//')' 33 | + lgstring = lgstring(:ilen) // '+'//backfil(i)//')' 34 | call crmvblk(lgstring) 35 | enddo 36 | ilen = clenact(lgstring) 37 | @@ -579,8 +571,7 @@ c ... construct the command string and spawn MATHPHA task if necessary 38 | str(8) = 'ncomments = 2' 39 | str(9) = '"comment1 = Created_by_a_spawn_from_'// 40 | & 'DO_ADDSPEC_v'// version//'"' 41 | - str(10) = '"comment2 = NOTE - exposure_time_increased'// 42 | - & '_to_avoid_rounding_errors"' 43 | + str(10) = '"comment2 = Valid in Poisson stat."' 44 | write(str(11),'(a,i12)') 'chatter=',schatter 45 | 46 | nstrs = 11 47 | diff --git a/ftools/heasarc/src/addspec/addspec.hlp b/ftools/heasarc/src/addspec/addspec.hlp 48 | index 9dfb7de..c66371e 100644 49 | --- a/ftools/heasarc/src/addspec/addspec.hlp 50 | +++ b/ftools/heasarc/src/addspec/addspec.hlp 51 | @@ -97,17 +97,6 @@ PHA dataset appropriate for use with the output PHA dataset. This can only be 52 | performed if the input PHA datasets contain the necessary keyword (BACKFILE) 53 | specifying the path and name of the relevant background file for each. 54 | .le 55 | -.ls (bexpscale = 1000) [real] 56 | - A scaling factor to be used to avoid "rounding errors" in the background PHA 57 | -dataset. This factor is only important when qsubback=yes, units=C, and the 58 | -background datasets contain low count rates. In such cases, then this 59 | -parameter is a numerical factor by which the background counts (and 60 | -statistical errors) are multiplied by, prior to rescaling and addition to 61 | -create the output background PHA dataset. The exposure time in the output background 62 | -PHA dataset is also multiplied by this value in order to "correct" for this 63 | -procedure. It is recommended that this parameter has a value of at least the 64 | -default (bexpscale = 1000). 65 | -.le 66 | .ls (properr='no') [character string] 67 | A flag whether the errors are to propagated during the algebra, or 68 | (if properr='no') whether the errors are simply calculated from the 69 | diff --git a/ftools/heasarc/src/addspec/addspec.txt b/ftools/heasarc/src/addspec/addspec.txt 70 | index 698eeea..18a6bf6 100644 71 | --- a/ftools/heasarc/src/addspec/addspec.txt 72 | +++ b/ftools/heasarc/src/addspec/addspec.txt 73 | @@ -108,19 +108,6 @@ PARAMETERS 74 | contain the necessary keyword (BACKFILE) specifying the path 75 | and name of the relevant background file for each. 76 | 77 | - (bexpscale = 1000) [real] 78 | - A scaling factor to be used to avoid "rounding errors" in the 79 | - background PHA dataset. This factor is only important when 80 | - qsubback=yes, units=C, and the background datasets contain low 81 | - count rates. In such cases, then this parameter is a numerical 82 | - factor by which the background counts (and statistical errors) 83 | - are multiplied by, prior to rescaling and addition to create 84 | - the output background PHA dataset. The exposure time in the 85 | - output background PHA dataset is also multiplied by this value 86 | - in order to "correct" for this procedure. It is recommended 87 | - that this parameter has a value of at least the default 88 | - (bexpscale = 1000). 89 | - 90 | (properr='no') [character string] 91 | A flag whether the errors are to propagated during the 92 | algebra, or (if properr='no') whether the errors are simply 93 | -------------------------------------------------------------------------------- /addspec.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2021 Johannes Buchner 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | """ 23 | 24 | import numpy as np 25 | import sys, os 26 | import astropy.io.fits as pyfits 27 | import subprocess 28 | 29 | def remove(filename): 30 | if os.path.exists(filename): 31 | os.unlink(filename) 32 | 33 | def run(*args, **kwargs): 34 | print(" running command:", *args) 35 | subprocess.check_call(*args, **kwargs) 36 | 37 | def sum_pha(outfile, filenames, backscals, areascals, rel_weights, **kwargs): 38 | print() 39 | print("creating '%s' ..." % outfile) 40 | remove(outfile) 41 | run(['mathpha', 'expr=' + '+'.join(filenames), "outfil=" + outfile, 42 | 'units=C', 'exposure=CALC', 'properr=NO', 'errmeth=POISS-0', 'areascal=NULL', 43 | 'ncomments=1', "comment1=Created_by_addspec.py_alpha", 'chatter=5']) 44 | 45 | # update AREASCAL, BACKSCAL keywords: 46 | areascal = (areascals * rel_weights).sum() 47 | backscal = (backscals * rel_weights).sum() 48 | print(" EXPOSURE and counts were summed") 49 | print(" relative weights for scale factors:", rel_weights) 50 | print(" averaged AREASCAL:", areascal) 51 | print(" averaged BACKSCAL:", backscal) 52 | with open('.tmp.modhead', 'w') as fout: 53 | fout.write("AREASCAL %.20f\n" % areascal) 54 | fout.write("BACKSCAL %.20f\n" % backscal) 55 | for k, v in kwargs.items(): 56 | fout.write("%s %s\n" % (k, v)) 57 | 58 | run(['fmodhead', outfile + '[SPECTRUM]', '.tmp.modhead']) 59 | os.unlink('.tmp.modhead') 60 | 61 | 62 | 63 | def main(outprefix, filenames): 64 | 65 | N = len(filenames) 66 | print("files:", N, filenames) 67 | 68 | backscals = np.empty(N) 69 | areascals = np.empty(N) 70 | exposures = np.empty(N) 71 | bbackscals = np.empty(N) 72 | bareascals = np.empty(N) 73 | bexposures = np.empty(N) 74 | arfs = [] 75 | rmfs = [] 76 | barfs = [] 77 | brmfs = [] 78 | 79 | bfilenames = [] 80 | 81 | for i, filename in enumerate(filenames): 82 | header = pyfits.getheader(filename, extname='SPECTRUM') 83 | backscals[i] = header['BACKSCAL'] 84 | areascals[i] = header['AREASCAL'] 85 | exposures[i] = header['EXPOSURE'] 86 | arf = header['ANCRFILE'] 87 | rmf = header['RESPFILE'] 88 | arfs.append(arf) 89 | rmfs.append(rmf) 90 | 91 | backfile = header['BACKFILE'] 92 | bfilenames.append(backfile) 93 | bheader = pyfits.getheader(backfile, extname='SPECTRUM') 94 | bbackscals[i] = bheader['BACKSCAL'] 95 | bareascals[i] = bheader['AREASCAL'] 96 | bexposures[i] = bheader['EXPOSURE'] 97 | barf = bheader['ANCRFILE'] 98 | brmf = bheader['RESPFILE'] 99 | barfs.append(barf if str(barf).lower() != 'none' else arf) 100 | brmfs.append(brmf if str(brmf).lower() != 'none' else rmf) 101 | 102 | del arf, rmf, barf, brmf, backfile 103 | 104 | print("Files:", filenames, bfilenames) 105 | 106 | 107 | print("BACKSCAL:", backscals, bbackscals) 108 | print("AREASCAL:", areascals, bareascals) 109 | print("EXPOSURE:", exposures, bexposures) 110 | 111 | weights = areascals * exposures 112 | rel_weights = weights / weights.sum() 113 | weightstr = ' '.join(['%e' % w for w in rel_weights]) 114 | bweights = bareascals * bexposures 115 | rel_bweights = bweights / bweights.sum() 116 | bweightstr = ' '.join(['%e' % w for w in rel_bweights]) 117 | assert len(arfs) == N 118 | assert len(rmfs) == N 119 | assert len(barfs) == N 120 | assert len(brmfs) == N 121 | 122 | print("combining ARFs:", arfs) 123 | arf = outprefix + '.arf' 124 | run(['addarf', ' '.join(arfs), weightstr, arf, 'clobber=yes']) 125 | barf = outprefix + '_bkg.arf' 126 | run(['addarf', ' '.join(barfs), bweightstr, barf, 'clobber=yes']) 127 | print("combining RMFs:", rmfs) 128 | rmf = outprefix + '.rmf' 129 | run(['addrmf', ' '.join(rmfs), weightstr, rmf, 'clobber=yes']) 130 | brmf = outprefix + '_bkg.rmf' 131 | run(['addrmf', ' '.join(brmfs), bweightstr, brmf, 'clobber=yes']) 132 | 133 | outfile = "%s.pha" % outprefix 134 | boutfile = "%s_bkg.pha" % outprefix 135 | 136 | sum_pha( 137 | outfile = outfile, 138 | filenames = filenames, 139 | areascals = areascals, 140 | backscals = backscals, 141 | rel_weights = rel_weights, 142 | ANCRFILE = arf, 143 | RESPFILE = rmf, 144 | BACKFILE = boutfile, 145 | ) 146 | sum_pha( 147 | outfile = boutfile, 148 | filenames = bfilenames, 149 | areascals = bareascals, 150 | backscals = bbackscals, 151 | rel_weights = rel_bweights, 152 | ANCRFILE = barf, 153 | RESPFILE = brmf, 154 | ) 155 | 156 | if __name__ == '__main__': 157 | if len(sys.argv) < 3: 158 | sys.stderr.write("""SYNOPSIS: addspec.py file1.pha file2.pha ... 159 | or 160 | SYNOPSIS: addspec.py @filelist.txt 161 | 162 | In the second case, each line of filelist.txt contains a file name. 163 | 164 | If as a outprefix "sum" is given, the output files are named 165 | sum.pha sum.arf sum.rmf sum_bkg.pha sum_bkg.arf sum_bkg.rmf 166 | 167 | Johannes Buchner (C) 2021, MIT Licence 168 | """) 169 | sys.exit(1) 170 | 171 | filenames = sys.argv[2:] 172 | if len(filenames) == 1 and filenames[0][0] == '@': 173 | filenames = [filename.rstrip() for filename in open(filenames[0][1:])] 174 | main( 175 | outprefix = sys.argv[1], 176 | filenames = filenames, 177 | ) 178 | --------------------------------------------------------------------------------