diff options
author | Aaron LI <aly@aaronly.me> | 2017-11-16 23:40:30 +0800 |
---|---|---|
committer | Aaron LI <aly@aaronly.me> | 2017-11-16 23:40:30 +0800 |
commit | 4037655221275dd8ff1f5f4afa342cf73b68161a (patch) | |
tree | 0229f1440cddcb44766a68f5885ee10a71a2f529 /astro/xmm/xspec_bkgcorr_xmm.tcl | |
parent | 5dbb18fcc00813bfbbc0e87fc6f95a9b2c58b5fe (diff) | |
download | atoolbox-4037655221275dd8ff1f5f4afa342cf73b68161a.tar.bz2 |
astro/xmm: Add several previously developed scripts
Diffstat (limited to 'astro/xmm/xspec_bkgcorr_xmm.tcl')
-rw-r--r-- | astro/xmm/xspec_bkgcorr_xmm.tcl | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/astro/xmm/xspec_bkgcorr_xmm.tcl b/astro/xmm/xspec_bkgcorr_xmm.tcl new file mode 100644 index 0000000..240d4a2 --- /dev/null +++ b/astro/xmm/xspec_bkgcorr_xmm.tcl @@ -0,0 +1,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: # |