aboutsummaryrefslogtreecommitdiffstats
path: root/astro/xmm/xspec_bkgcorr_xmm.tcl
blob: 240d4a20f857f86750127f5319deb740052034b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
#####################################################################
## XSPEC TCL script
##
## To apply background spectrum correction to MOS1 and MOS2 background.
## It fakes the XB (Galatic + CXB) and NXB (SP + instrument lines)
## spectra according to the current fitting results, and then invoke
## "mathpha" to make the final corrected background spectrum.
##
## NOTE:
## The exposure time for the faked spectra is multiplied by a scale
## factor in order to obtain satisfactory statistics for the higher
## energy regime.  The same scale factor is also applied to the original
## FWC background spectrum, considering that many channels have "counts"
## smaller than one, especially in the higher energy regime.  Otherwise,
## many of these values will be rounded to *zero* by the "mathpha"
## operating in the "COUNT" mode.
## See also the following description to variable "SCALE_FACTOR",
## procedures "spectrum_rate2counts" and "scale_spectrum".
##
##
## NOTES:
## Work with XSPEC v12.x
## Based on "xspec_bkgcorr_v2.tcl" for Chandra background spectrum correction
##
## Weitian LI <liweitianux@gmail.com>
## Created: 2015-11-07
## Updated: 2015-11-12
##
## ChangeLogs:
## 2015-11-10:
##   * Remove "diag_rsp"
##   * Add proc update_header:
##     Update specified keywords instead of just copy the original header
##   * Use lists to record the information for all data groups,
##     therefore this tool can handle any number of data groups.
## 2015-11-11:
##   * Add back "diag_rsp"
## 2015-11-12:
##   * Update to use "ftkeypar" and "fthedit"
##   * Add procedure "scale_spectrum" to scale the FWC background
##   * Add procedure "make_filename"
##   * Add procedure "spectrum_rate2counts" to convert RATE spectrum to
##     COUNTS, therefore this script can properly handle PN spectrum
##   * Update script description
#####################################################################


## Global variables {{{
set datagrp_total [ tcloutr datagrp ]

# Lists to record the information of each data group
set rootname       {}
set rmf            {}
set arf            {}
set diag_rsp       {}
set src_spec       {}
set bkg_spec       {}
set src_exposure   {}
set bkg_exposure   {}
set src_backscale  {}
set bkg_backscale  {}
set modelpars      {}
set back_modelname {}
set back_modelcomp {}
set back_modelpars {}
# This "modelcomp" is not a list
set modelcomp      {}

# FITS header keywords needed to be copyed to the corrected spectrum
set KEYWORDS       {TELESCOP INSTRUME FILTER}

# Scale factor for faking spectra
# By lengthen the exposure time for the fake process, the total counts
# of the faked spectra is much larger, therefore the statistical issuses
# due to small counts (especially in the higher energy regime) is mitigated.
set SCALE_FACTOR   100
## Global variables }}}

## Procedures
# Make new filename by appending the suffix to the stem of filename.
# e.g., "stem.ext" + "sfix" => "stem_sfix.ext"
proc make_filename {filename suffix} {
    regexp -- {(.+)\.([^\.]+)} $filename match stem_ ext_
    set newname "${stem_}_${suffix}.${ext_}"
    return $newname
}

# Determine root name and get other filenames (e.g., rmf, arf, back)
proc get_filenames {datagrp} {
    global rootname
    global src_spec
    global bkg_spec
    global rmf
    global arf
    global diag_rsp
    set src_spec_ [ exec basename [ tcloutr filename $datagrp ] ]
    regexp -- {(.+)_(bin|grp|group)([0-9]*)\.(pi|fits)} $src_spec_ match rootname_ grptype_ grpval_ fext_
    set bkg_spec_ [ tcloutr backgrnd $datagrp ]
    set response_ [ tcloutr response $datagrp ]
    set rmf_      [ lindex $response_ 0 ]
    set diag_rsp_ [ lindex $response_ 1 ]
    set arf_      [ lindex [ tcloutr arf      $datagrp ] 0 ]
    lappend rootname $rootname_
    lappend src_spec $src_spec_
    lappend bkg_spec $bkg_spec_
    lappend rmf      $rmf_
    lappend arf      $arf_
    lappend diag_rsp $diag_rsp_
}

# Get exposure and backscale values
proc get_exposure_backscale {datagrp} {
    global src_exposure
    global bkg_exposure
    global src_backscale
    global bkg_backscale
    set src_exposure_ [ tcloutr expos $datagrp s ]
    set bkg_exposure_ [ tcloutr expos $datagrp b ]
    scan [ tcloutr backscal $datagrp s ] "%f" src_backscale_
    scan [ tcloutr backscal $datagrp b ] "%f" bkg_backscale_
    lappend src_exposure  $src_exposure_
    lappend bkg_exposure  $bkg_exposure_
    lappend src_backscale $src_backscale_
    lappend bkg_backscale $bkg_backscale_
}

# Get the model components
proc get_modelcomp {} {
    global datagrp_total
    global modelcomp
    # NOTE: "model" contains all the model components corresponding to
    # echo data group, and contains also the background model components.
    # Therefore, the "model" contents repeats the same model components
    # for each data group.
    # e.g.,
    # if data group 1 has model components: "wabs*apec", and there are
    # altogether 2 data groups, then the "model" content is:
    #     "wabs(apec) wabs(apec)"
    # which is repeated!
    set model [ tcloutr model ]
    set indices [ lsearch -all $model {*:} ]
    if { [ llength $indices ] == 0 } {
        set modelcomp $model
    } else {
        set modelcomp [ lrange $model 0 [ expr {[ lindex $indices 0 ] - 1} ] ]
    }
    # Deal with the repeated model components if exist multiple data groups
    set comp_length [ expr { [ llength $modelcomp ] / $datagrp_total } ]
    set modelcomp [ lrange $modelcomp 0 [ expr {$comp_length - 1} ] ]
}

# Get the model parameters
proc get_modelpars {datagrp} {
    global datagrp_total
    global modelpars
    set modelpars_ ""
    # NOTE: "modpar" is the total number of parameters,
    # not just for one data group
    set npar_ [ expr { [ tcloutr modpar ] / $datagrp_total } ]
    set par_begin_ [ expr {$npar_ * ($datagrp - 1) + 1} ]
    set par_end_   [ expr {$npar_ + $par_begin_ - 1} ]
    for {set i $par_begin_} {$i <= $par_end_} {incr i} {
        scan [ tcloutr param $i ] "%f" pval_
        set modelpars_ "$modelpars_ $pval_ &"
    }
    lappend modelpars $modelpars_
}

# Get the model name, components and parameters of the background model
# corresponding to the given data group
proc get_backmodel {datagrp} {
    global back_modelname
    global back_modelcomp
    global back_modelpars
    set model_ [ tcloutr model ]
    set indices_ [ lsearch -all $model_ {*:} ]
    set name_idx_ [ lindex $indices_ [ expr {$datagrp - 1} ] ]
    set back_modelname_ [ regsub {:\s*} [ lindex $model_ $name_idx_ ] "" ]
    set ncomp_ [ tcloutr modcomp $back_modelname_ ]
    set back_modelcomp_ [ lrange $model_ [ expr {$name_idx_ + 1} ] [ expr {$name_idx_ + $ncomp_ } ] ]
    set npar_ [ tcloutr modpar $back_modelname_ ]
    set back_modelpars_ ""
    for {set i 1} {$i <= $npar_} {incr i} {
        scan [ tcloutr param ${back_modelname_}:$i ] "%f" pval_
        set back_modelpars_ "$back_modelpars_ $pval_ &"
    }
    lappend back_modelname $back_modelname_
    lappend back_modelcomp $back_modelcomp_
    lappend back_modelpars $back_modelpars_
}

# Save current XSPEC fitting results
proc save_xspec_fit {} {
    set now [ exec date +%Y%m%d%H%M ]
    set xspec_outfile "xspec_${now}.xcm"
    if { [ file exists ${xspec_outfile} ] } {
        exec mv -fv ${xspec_outfile} ${xspec_outfile}_bak
    }
    save all "${xspec_outfile}"
}

# Load model
proc load_model {comp pars} {
    model clear
    model $comp & $pars /*
}

# Set the norm of the ICM APEC component to be zero,
# which is the last parameter of current loaded model.
proc set_icm_norm {} {
    set npar_ [ tcloutr modpar ]
    newpar $npar_ 0.0
    puts "ICM_apec_norm:$npar_: [ tcloutr param $npar_ ]"
}

# Fake spectrum
proc fake_spectrum {outfile rmf arf exptime} {
    if {[ file exists ${outfile} ]} {
        exec mv -fv ${outfile} ${outfile}_bak
    }
    data none
    fakeit none & $rmf & $arf & y &  & $outfile & $exptime & /*
}

# Convert a spectrum of "RATE" to a spectrum of "COUNTS" by multiplying
# the exposure time.  The "STAT_ERR" column is also scaled if exists.
# NOTE:
# The MOS1/2 FWC background spectrum generated by "mos_back" has column
# "COUNTS", however, the PN FWC background spectrum generated by "pn_back"
# has column "RATE".
# Therefore, the PN background spectra should be converted to "COUNTS" format,
# since the other faked CXB & NXB spectra are also in "COUNTS" format.
# The exposure time is acquired from the "EXPOSURE" keyword in the header
# of input spectrum.
#
# Return:
#   * 0  : input spectrum is already in "COUNTS" format, no conversion needed
#   * 1  : input spectrum is in "RATE" format, and is converted to "COUNTS"
#          format and write to outfile
#   * -1 : invalid input spectrum
proc spectrum_rate2counts {outfile infile} {
    if {[ file exists ${outfile} ]} {
        exec mv -fv ${outfile} ${outfile}_bak
    }
    # Get number of columns/fields
    exec ftkeypar "${infile}+1" TFIELDS
    set fields_ [ exec pget ftkeypar value ]
    set colnames {}
    for {set i 1} {$i <= $fields_} {incr i} {
        exec ftkeypar "${infile}+1" "TTYPE${i}"
        set val_ [ exec pget ftkeypar value ]
        set colname_ [ string toupper [ string trim $val_ {' } ] ]
        lappend colnames $colname_
    }
    if { [ lsearch $colnames {COUNTS} ] != -1 } {
        # Input spectrum is already in "COUNTS" format
        return 0
    } elseif { [ lsearch $colnames {RATE} ] != -1 } {
        # Get exposure time
        exec ftkeypar "${infile}+1" EXPOSURE
        set exposure_ [ exec pget ftkeypar value ]
        # Convert column "RATE"
        set tmpfile "${outfile}_tmp[pid]"
        exec cp -fv ${infile} ${tmpfile}
        # COUNTS = RATE * EXPOSURE
        set fcmd_ "ftcalc \"${tmpfile}\" \"${outfile}\" column=RATE expression=\"RATE * $exposure_\" history=yes"
        puts $fcmd_
        exec sh -c $fcmd_
        exec rm -fv ${tmpfile}
        # Update column name from "RATE" to "COUNTS", and column units
        set field_idx_ [ expr { [ lsearch $colnames {RATE} ] + 1 } ]
        set fcmd_ "fthedit \"${outfile}+1\" keyword=\"TTYPE${field_idx_}\" operation=add value=\"COUNTS\""
        puts $fcmd_
        exec sh -c $fcmd_
        set fcmd_ "fthedit \"${outfile}+1\" keyword=\"TUNIT${field_idx_}\" operation=add value=\"count\""
        puts $fcmd_
        exec sh -c $fcmd_
        # Convert column "STAT_ERR"
        if { [ lsearch $colnames {STAT_ERR} ] != -1 } {
            exec mv -fv ${outfile} ${tmpfile}
            # STAT_ERR = STAT_ERR * EXPOSURE
            set fcmd_ "ftcalc \"${tmpfile}\" \"${outfile}\" column=STAT_ERR expression=\"STAT_ERR * $exposure_\" history=yes"
            puts $fcmd_
            exec sh -c $fcmd_
            exec rm -fv ${tmpfile}
        }
        return 1
    } else {
        # Invalid input spectrum
        return -1
    }
}

# Scale the spectrum "COUNTS" and "STAT_ERR" (if exists) columns by a factor.
# NOTE:
# This procedure is mainly used to scale the FWC background, within which
# many channels have "counts" less than one.  When using "mathpha" to combine
# this FWC background and other faked spectra in "COUNT" mode, the channels
# with "counts" less than one (especially the higher energy regime) may be
# truncated/rounded to integer, therefore the higher energy regime of the
# combined spectrum may be greatly underestimated, and will cause the
# following fitting very bad in the higher energy regime.
# See also the description about the "SCALE_FACTOR" above.
proc scale_spectrum {outfile infile factor} {
    if {[ file exists ${outfile} ]} {
        exec mv -fv ${outfile} ${outfile}_bak
    }
    exec cp -fv ${infile} ${outfile}
    # Get number of columns/fields
    exec ftkeypar "${outfile}+1" TFIELDS
    set fields_ [ exec pget ftkeypar value ]
    for {set i 1} {$i <= $fields_} {incr i} {
        exec ftkeypar "${outfile}+1" "TTYPE${i}"
        set val_ [ exec pget ftkeypar value ]
        set colname_ [ string toupper [ string trim $val_ {' } ] ]
        # Scale column "COUNTS"
        if {$colname_ == "COUNTS"} {
            set tmpfile "${outfile}_tmp[pid]"
            exec mv -fv ${outfile} ${tmpfile}
            # COUNTS = COUNTS * factor
            set fcmd_ "ftcalc \"${tmpfile}\" \"${outfile}\" column=COUNTS expression=\"COUNTS * $factor\" history=yes"
            puts $fcmd_
            exec sh -c $fcmd_
            exec rm -fv ${tmpfile}
        }
        # Scale column "STAT_ERR"
        if {$colname_ == "STAT_ERR"} {
            set tmpfile "${outfile}_tmp[pid]"
            exec mv -fv ${outfile} ${tmpfile}
            # STAT_ERR = STAT_ERR * factor
            set fcmd_ "ftcalc \"${tmpfile}\" \"${outfile}\" column=STAT_ERR expression=\"STAT_ERR * $factor\" history=yes"
            puts $fcmd_
            exec sh -c $fcmd_
            exec rm -fv ${tmpfile}
        }
    }
}

# Combine faked spectra to original FWC background spectrum with "mathpha"
proc combine_spectra {outfile fwc_bkg cxb nxb exposure backscale} {
    set combine_expr_ "$fwc_bkg + $cxb + $nxb"
    set comment1_ "Corrected background spectrum; FWC based"
    set combine_cmd_ "mathpha expr='${combine_expr_}' outfil=${outfile} exposure=${exposure} backscal=${backscale} units=C areascal=% properr=yes ncomments=1 comment1='${comment1_}'"
    if {[ file exists ${outfile} ]} {
        exec mv -fv ${outfile} ${outfile}_bak
    }
    puts $combine_cmd_
    exec sh -c $combine_cmd_
}

# Copy header keywords from original background spectrum to the
# newly combined background spectrum
proc update_header {newfile origfile} {
    global KEYWORDS
    for {set i 0} {$i < [ llength $KEYWORDS ]} {incr i} {
        set key_ [ lindex $KEYWORDS $i ]
        exec ftkeypar "${origfile}+1" $key_
        set val_ [ exec pget ftkeypar value ]
        #set fcmd_ "fparkey value=\"$val_\" fitsfile=\"${newfile}+1\" keyword=\"$key_\" add=yes"
        set fcmd_ "fthedit \"${newfile}+1\" keyword=\"$key_\" operation=add value=\"$val_\""
        puts $fcmd_
        exec sh -c $fcmd_
    }
}


# Save current fit results
save_xspec_fit

# Get the current model components, which is the same for all data groups
get_modelcomp

for {set dg 1} {$dg <= $datagrp_total} {incr dg} {
    get_filenames          $dg
    get_exposure_backscale $dg
    get_modelpars          $dg
    get_backmodel          $dg
}

puts "modelcomp: $modelcomp"
puts "rootname: $rootname"
puts "rmf: $rmf"
puts "arf: $arf"
puts "diag_rsp: $diag_rsp"
puts "src_spec: $src_spec"
puts "bkg_spec: $bkg_spec"
puts "src_exposure: $src_exposure"
puts "bkg_exposure: $bkg_exposure"
puts "src_backscale: $src_backscale"
puts "bkg_backscale: $bkg_backscale"
puts "modelpars: $modelpars"
puts "back_modelname: $back_modelname"
puts "back_modelcomp: $back_modelcomp"
puts "back_modelpars: $back_modelpars"
puts "SCALE_FACTOR: $SCALE_FACTOR"

# DEBUG
set DEBUG 0
if {$DEBUG != 1} {

# Clear all loaded model data and models
data none
model clear

for {set idg 0} {$idg < $datagrp_total} {incr idg} {
    set rootname_       [ lindex $rootname       $idg ]
    set bkg_spec_       [ lindex $bkg_spec       $idg ]
    set rmf_            [ lindex $rmf            $idg ]
    set arf_            [ lindex $arf            $idg ]
    set diag_rsp_       [ lindex $diag_rsp       $idg ]
    set bkg_exposure_   [ lindex $bkg_exposure   $idg ]
    set bkg_backscale_  [ lindex $bkg_backscale  $idg ]
    set modelpars_      [ lindex $modelpars      $idg ]
    set back_modelcomp_ [ lindex $back_modelcomp $idg ]
    set back_modelpars_ [ lindex $back_modelpars $idg ]
    set fake_exposure   [ expr {$bkg_exposure_  * $SCALE_FACTOR} ]
    set fake_backscale  [ expr {$bkg_backscale_ * $SCALE_FACTOR} ]
    set fake_cxb        "fake_cxb_${rootname_}.pi"
    set fake_nxb        "fake_nxb_${rootname_}.pi"
    set bkgspec_cnts    [ make_filename $bkg_spec_ "cnts" ]
    set bkgcorr_outfile "bkgcorr_${rootname_}.pi"
    # Fake CXB and NXB
    load_model    $modelcomp $modelpars_
    set_icm_norm
    fake_spectrum $fake_cxb $rmf_ $arf_ $fake_exposure
    load_model    $back_modelcomp_ $back_modelpars_
    fake_spectrum $fake_nxb $diag_rsp_ "" $fake_exposure
    # Convert FWC background spectrum from "RATE" to "COUNTS" for PN
    set ret [ spectrum_rate2counts $bkgspec_cnts $bkg_spec_ ]
    if { $ret == 0 } {
        set bkgspec_cnts $bkg_spec_
    } elseif { $ret == 1 } {
        puts "Converted RATE spectrum to COUNTS: $bkgspec_cnts"
    } else {
        return -code error "ERROR: invalid spectrum for 'spectrum_rate2counts'"
    }
    set bkgspec_scaled [ make_filename $bkgspec_cnts "sf${SCALE_FACTOR}" ]
    # Scale original FWC background before combination
    scale_spectrum  $bkgspec_scaled $bkgspec_cnts $SCALE_FACTOR
    # Combine faked spectra with original FWC background
    combine_spectra $bkgcorr_outfile $bkgspec_scaled $fake_cxb $fake_nxb $bkg_exposure_ $fake_backscale
    update_header   $bkgcorr_outfile $bkg_spec_
}

}

# vim: set ts=8 sw=4 tw=0 fenc=utf-8 ft=tcl: #