aboutsummaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authorAaron LI <aaronly.me@outlook.com>2017-02-23 22:23:56 +0800
committerAaron LI <aaronly.me@outlook.com>2017-02-23 22:23:56 +0800
commit2c9c830d277c180ccbd0934059dbf87dfa972e27 (patch)
tree03f5d457e91b474793f440687d2de950760b7433 /bin
parent26d46a1c14a7a84910e9fa6fc16e524521afe26e (diff)
downloadchandra-acis-analysis-2c9c830d277c180ccbd0934059dbf87dfa972e27.tar.bz2
Add make_expmap.py: Make spectral-weighted exposure map
Diffstat (limited to 'bin')
-rwxr-xr-xbin/make_expmap.py247
1 files changed, 247 insertions, 0 deletions
diff --git a/bin/make_expmap.py b/bin/make_expmap.py
new file mode 100755
index 0000000..ff1fbb0
--- /dev/null
+++ b/bin/make_expmap.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
+# MIT license
+
+"""
+Create the spectral-weighted exposure map, which will be used to produce
+the exposure-corrected images and to extract the surface brightness profiles.
+
+NOTE
+----
+We do not use the CIAO tool ``fluximage`` to create the exposure map,
+because it requires an event file (other than an image) as input file.
+However, we want to create the exposure map directly based on the
+(previously created) image file.
+(But an event file is required to create the aspect histograms for each
+chip.)
+
+NOTE/CAVEAT
+-----------
+To compute an image which gives the integrated flux over the full energy
+range, it may be best to first compute flux-corrected images in several
+narrow energy bands (where the ARF is nearly flat) and then sum those
+fluxed images together. Weighted exposure maps work well for an energy
+band where the ARF variation isn't very large, but for a full-band
+0.5-10 keV image, it may not be a good idea to compute the flux by
+dividing the counts image by a single number. This is especially true
+for cases where the source spectrum varies significantly within the image;
+in that case, there is no general way to compute a single set of weights
+which will be sensible for every part of the image.
+
+References
+----------
+* Chandra Memo: An Introduction to Exposure Map
+ http://cxc.harvard.edu/ciao/download/doc/expmap_intro.ps
+* CIAO: Single Chip ACIS Exposure Map and Exposure-corrected Image
+ http://cxc.harvard.edu/ciao/threads/expmap_acis_single/
+* CIAO: Multiple Chip ACIS Exposure Map and Exposure-corrected Image
+ http://cxc.harvard.edu/ciao/threads/expmap_acis_multi/
+* CIAO: Calculating Spectral Weights for mkinstmap
+ http://cxc.harvard.edu/ciao/threads/spectral_weights/
+"""
+
+import os
+import argparse
+import subprocess
+import logging
+
+from _context import acispy
+from acispy.manifest import get_manifest
+from acispy.pfiles import setup_pfiles
+from acispy.acis import ACIS
+from acispy.header import write_keyword
+
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+def get_xygrid(image):
+ """
+ Get the ``xygrid`` of the input image for later ``mkexpmap``'s use.
+ """
+ logger.info("Get the 'xygrid' of image: %s" % image)
+ subprocess.check_call(["punlearn", "get_sky_limits"])
+ subprocess.check_call([
+ "get_sky_limits", "image=%s" % image, "verbose=0"
+ ])
+ xygrid = subprocess.check_output([
+ "pget", "get_sky_limits", "xygrid"
+ ]).decode("utf-8").strip()
+ logger.info("xygrid: %s" % xygrid)
+ return xygrid
+
+
+def make_aspect_histogram(outfile, asol, evtfile, chip, clobber=False):
+ """
+ Create the aspect histogram for each chip, detailing the aspect
+ history of the observation.
+ """
+ clobber = "yes" if clobber else "no"
+ logger.info("Make aspect histogram for chip: %s" % chip)
+ subprocess.check_call(["punlearn", "asphist"])
+ subprocess.check_call([
+ "asphist", "infile=%s" % asol, "outfile=%s" % outfile,
+ "evtfile=%s[ccd_id=%s]" % (evtfile, chip),
+ "clobber=%s" % clobber
+ ])
+
+
+def make_instrument_map(outfile, spectrumfile, chip, obsfile,
+ maskfile, badpixfile, clobber=False):
+ """
+ Make the spectral-weighted instrument map (effective area vs.
+ detector position).
+
+ NOTE
+ ----
+ The ``obsfile`` should be a FITS file containing keywords which specify
+ the mission, detector, SIM offsets, observation date, etc. This should
+ be an event file; can also be a binned image file (tested OK).
+ """
+ logger.info("Set bad pixel file: %s" % badpixfile)
+ subprocess.check_call(["punlearn", "ardlib"])
+ subprocess.check_call(["punlearn", "acis_set_ardlib"])
+ subprocess.check_call([
+ "acis_set_ardlib", "badpixfile=%s" % badpixfile, "verbose=0"
+ ])
+
+ clobber = "yes" if clobber else "no"
+ pixelgrid = "1:1024:#1024,1:1024:#1024"
+ logger.info("Make spectral-weighted instrument map for chip: %s" % chip)
+ subprocess.check_call(["punlearn", "mkinstmap"])
+ subprocess.check_call([
+ "mkinstmap", "outfile=%s" % outfile,
+ "spectrumfile=%s" % spectrumfile,
+ "pixelgrid=%s" % pixelgrid,
+ "detsubsys=ACIS-%s" % chip,
+ "obsfile=%s" % obsfile,
+ "maskfile=%s" % maskfile,
+ "clobber=%s" % clobber, "mode=h"
+ ])
+
+
+def make_exposure_map(outfile, asphistfile, instmapfile, xygrid,
+ clobber=False):
+ """
+ Create the spectral-weighted exposure map by projecting the instrument
+ map onto the sky with the aspect information.
+ """
+ clobber = "yes" if clobber else "no"
+ logger.info("Make exposure map by projecting instrument map ...")
+ subprocess.check_call(["punlearn", "mkexpmap"])
+ subprocess.check_call([
+ "mkexpmap", "outfile=%s" % outfile,
+ "asphistfile=%s" % asphistfile,
+ "instmapfile=%s" % instmapfile,
+ "xygrid=%s" % xygrid,
+ "normalize=no", "useavgaspect=no",
+ "clobber=%s" % clobber, "mode=h"
+ ])
+
+
+def combine_expmaps(outfile, expmaps, clobber=False):
+ """
+ Combine multiple exposure maps of each chip into a single one
+ for ACIS-I (i.e., chips 0123).
+ """
+ if len(expmaps) > 1:
+ logger.info("Combine exposure maps: {0}".format(", ".join(expmaps)))
+ operation = "imgout=%s" % "+".join([
+ "img%d" % (i+1) for i in range(len(expmaps))
+ ])
+ clobber = "yes" if clobber else "no"
+ subprocess.check_call(["punlearn", "dmimgcalc"])
+ subprocess.check_call([
+ "dmimgcalc", "infile=%s" % ",".join(expmaps),
+ "infile2=none", "outfile=%s" % outfile,
+ "operation=%s" % operation,
+ "clobber=%s" % clobber
+ ])
+ for f in expmaps:
+ os.remove(f)
+ else:
+ logger.info("No need to combine exposure maps.")
+ if os.path.exists(outfile) and (not clobber):
+ raise OSError("File already exists: %s" % outfile)
+ os.rename(expmaps[0], outfile)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="Make spectral-weighted exposure map")
+ parser.add_argument("-C", "--clobber", dest="clobber", action="store_true",
+ help="overwrite existing file")
+ parser.add_argument("-w", "--weights", dest="weights",
+ help="spectral weights file (default: " +
+ "'spec_weights' from manifest)")
+ parser.add_argument("-e", "--evtfile", dest="evtfile",
+ help="event file for aspect histogram creation " +
+ "(default: 'evt2_clean' from manifest)")
+ parser.add_argument("-i", "--infile", dest="infile", required=True,
+ help="input image file")
+ parser.add_argument("-o", "--outfile", dest="outfile", required=True,
+ help="filename of output exposure map")
+ args = parser.parse_args()
+
+ setup_pfiles(["get_sky_limits", "asphist", "mkinstmap", "ardlib",
+ "acis_set_ardlib", "mkexpmap", "dmimgcalc"])
+
+ manifest = get_manifest()
+ if args.weights:
+ weights = args.weights
+ else:
+ weights = manifest.getpath("spec_weights", relative=True)
+ if args.evtfile:
+ evtfile = args.evtfile
+ else:
+ evtfile = manifest.getpath("evt2_clean", relative=True)
+ asol = manifest.getpath("asol", relative=True, sep=",")
+ bpix = manifest.getpath("bpix", relative=True)
+ msk = manifest.getpath("msk", relative=True)
+ chips = ACIS.get_chips_str(args.infile)
+ logger.info("infile: %s" % args.infile)
+ logger.info("output expmap: %s" % args.outfile)
+ logger.info("weights: %s" % weights)
+ logger.info("evtfile: %s" % evtfile)
+ logger.info("chips: %s" % chips)
+ logger.info("bpix: %s" % bpix)
+ logger.info("asol: %s" % asol)
+ logger.info("msk: %s" % msk)
+
+ xygrid = get_xygrid(args.infile)
+ expmaps = []
+
+ for c in chips:
+ logger.info("Processing chip %s ..." % c)
+ asphist = "asphist_c{chip}.fits".format(chip=c)
+ instmap = "instmap_c{chip}.fits".format(chip=c)
+ expmap = "expmap_c{chip}.fits".format(chip=c)
+ make_aspect_histogram(outfile=asphist, asol=asol, evtfile=evtfile,
+ chip=c, clobber=args.clobber)
+ make_instrument_map(outfile=instmap, spectrumfile=weights,
+ chip=c, obsfile=args.infile, maskfile=msk,
+ badpixfile=bpix, clobber=args.clobber)
+ make_exposure_map(outfile=expmap, asphistfile=asphist,
+ instmapfile=instmap, xygrid=xygrid,
+ clobber=args.clobber)
+ expmaps.append(expmap)
+ # Remove intermediate files
+ os.remove(asphist)
+ os.remove(instmap)
+
+ combine_expmaps(outfile=args.outfile, expmaps=expmaps,
+ clobber=args.clobber)
+ detnam = "ACIS-{0}".format(chips)
+ logger.info("Update keyword 'DETNAM' to %s" % detnam)
+ write_keyword(args.outfile, keyword="DETNAM", value=detnam)
+
+ # Add created exposure map and exposure-corrected image to manifest
+ key = "expmap"
+ manifest.setpath(key, args.outfile)
+ logger.info("Added '%s' to manifest: %s" % (key, manifest.get(key)))
+
+
+if __name__ == "__main__":
+ main()